//!
use bitcoin::blockdata::block::{Block, BlockHeader};
+use bitcoin::blockdata::transaction::Transaction;
use bitcoin::blockdata::constants::genesis_block;
use bitcoin::network::constants::Network;
}
/// Events which we process internally but cannot be procsesed immediately at the generation site
-/// for some reason. They are handled in timer_chan_freshness_every_min, so may be processed with
+/// for some reason. They are handled in timer_tick_occurred, so may be processed with
/// quite some time lag.
enum BackgroundEvent {
/// Handle a ChannelMonitorUpdate that closes a channel, broadcasting its current latest holder
/// ChannelUpdate messages informing peers that the channel is temporarily disabled. To avoid
/// spam due to quick disconnection/reconnection, updates are not sent until the channel has been
/// offline for a full minute. In order to track this, you must call
-/// timer_chan_freshness_every_min roughly once per minute, though it doesn't have to be perfect.
+/// timer_tick_occurred roughly once per minute, though it doesn't have to be perfect.
///
/// Rather than using a plain ChannelManager, it is preferable to use either a SimpleArcChannelManager
/// a SimpleRefChannelManager, for conciseness. See their documentation for more details, but
#[cfg(not(any(test, feature = "_test_utils")))]
channel_state: Mutex<ChannelHolder<Signer>>,
our_network_key: SecretKey,
+ our_network_pubkey: PublicKey,
/// Used to track the last value sent in a node_announcement "timestamp" field. We ensure this
/// value increases strictly since we don't assume access to a time source.
/// The amount of time in blocks we require our counterparty wait to claim their money (ie time
/// between when we, or our watchtower, must check for them having broadcast a theft transaction).
-pub(crate) const BREAKDOWN_TIMEOUT: u16 = 6 * 24;
+///
+/// This can be increased (but not decreased) through [`ChannelHandshakeConfig::our_to_self_delay`]
+///
+/// [`ChannelHandshakeConfig::our_to_self_delay`]: crate::util::config::ChannelHandshakeConfig::our_to_self_delay
+pub const BREAKDOWN_TIMEOUT: u16 = 6 * 24;
/// The amount of time in blocks we're willing to wait to claim money back to us. This matches
/// the maximum required amount in lnd as of March 2021.
pub(crate) const MAX_LOCAL_BREAKDOWN_TIMEOUT: u16 = 2 * 6 * 24 * 7;
/// The minimum number of blocks between an inbound HTLC's CLTV and the corresponding outbound
-/// HTLC's CLTV. This should always be a few blocks greater than channelmonitor::CLTV_CLAIM_BUFFER,
-/// ie the node we forwarded the payment on to should always have enough room to reliably time out
-/// the HTLC via a full update_fail_htlc/commitment_signed dance before we hit the
-/// CLTV_CLAIM_BUFFER point (we static assert that it's at least 3 blocks more).
-const CLTV_EXPIRY_DELTA: u16 = 6 * 12; //TODO?
+/// HTLC's CLTV. The current default represents roughly six hours of blocks at six blocks/hour.
+///
+/// This can be increased (but not decreased) through [`ChannelConfig::cltv_expiry_delta`]
+///
+/// [`ChannelConfig::cltv_expiry_delta`]: crate::util::config::ChannelConfig::cltv_expiry_delta
+// This should always be a few blocks greater than channelmonitor::CLTV_CLAIM_BUFFER,
+// i.e. the node we forwarded the payment on to should always have enough room to reliably time out
+// the HTLC via a full update_fail_htlc/commitment_signed dance before we hit the
+// CLTV_CLAIM_BUFFER point (we static assert that it's at least 3 blocks more).
+pub const MIN_CLTV_EXPIRY_DELTA: u16 = 6 * 6;
pub(super) const CLTV_FAR_FAR_AWAY: u32 = 6 * 24 * 7; //TODO?
// Check that our CLTV_EXPIRY is at least CLTV_CLAIM_BUFFER + ANTI_REORG_DELAY + LATENCY_GRACE_PERIOD_BLOCKS,
// LATENCY_GRACE_PERIOD_BLOCKS.
#[deny(const_err)]
#[allow(dead_code)]
-const CHECK_CLTV_EXPIRY_SANITY: u32 = CLTV_EXPIRY_DELTA as u32 - LATENCY_GRACE_PERIOD_BLOCKS - CLTV_CLAIM_BUFFER - ANTI_REORG_DELAY - LATENCY_GRACE_PERIOD_BLOCKS;
+const CHECK_CLTV_EXPIRY_SANITY: u32 = MIN_CLTV_EXPIRY_DELTA as u32 - LATENCY_GRACE_PERIOD_BLOCKS - CLTV_CLAIM_BUFFER - ANTI_REORG_DELAY - LATENCY_GRACE_PERIOD_BLOCKS;
// Check for ability of an attacker to make us fail on-chain by delaying inbound claim. See
// ChannelMontior::would_broadcast_at_height for a description of why this is needed.
#[deny(const_err)]
#[allow(dead_code)]
-const CHECK_CLTV_EXPIRY_SANITY_2: u32 = CLTV_EXPIRY_DELTA as u32 - LATENCY_GRACE_PERIOD_BLOCKS - 2*CLTV_CLAIM_BUFFER;
+const CHECK_CLTV_EXPIRY_SANITY_2: u32 = MIN_CLTV_EXPIRY_DELTA as u32 - LATENCY_GRACE_PERIOD_BLOCKS - 2*CLTV_CLAIM_BUFFER;
/// Details of a channel, as returned by ChannelManager::list_channels and ChannelManager::list_usable_channels
#[derive(Clone)]
latest_block_height: AtomicUsize::new(params.latest_height),
last_block_hash: RwLock::new(params.latest_hash),
- secp_ctx,
channel_state: Mutex::new(ChannelHolder{
by_id: HashMap::new(),
pending_msg_events: Vec::new(),
}),
our_network_key: keys_manager.get_node_secret(),
+ our_network_pubkey: PublicKey::from_secret_key(&secp_ctx, &keys_manager.get_node_secret()),
+ secp_ctx,
last_node_announcement_serial: AtomicUsize::new(0),
/// Creates a new outbound channel to the given remote node and with the given value.
///
- /// user_id will be provided back as user_channel_id in FundingGenerationReady and
- /// FundingBroadcastSafe events to allow tracking of which events correspond with which
- /// create_channel call. Note that user_channel_id defaults to 0 for inbound channels, so you
- /// may wish to avoid using 0 for user_id here.
+ /// user_id will be provided back as user_channel_id in FundingGenerationReady events to allow
+ /// tracking of which events correspond with which create_channel call. Note that the
+ /// user_channel_id defaults to 0 for inbound channels, so you may wish to avoid using 0 for
+ /// user_id here. user_id has no meaning inside of LDK, it is simply copied to events and
+ /// otherwise ignored.
///
/// If successful, will generate a SendOpenChannel message event, so you should probably poll
/// PeerManager::process_events afterwards.
}
}
- fn force_close_channel_with_peer(&self, channel_id: &[u8; 32], peer_node_id: Option<&PublicKey>) -> Result<(), APIError> {
+ fn force_close_channel_with_peer(&self, channel_id: &[u8; 32], peer_node_id: Option<&PublicKey>) -> Result<PublicKey, APIError> {
let mut chan = {
let mut channel_state_lock = self.channel_state.lock().unwrap();
let channel_state = &mut *channel_state_lock;
if let hash_map::Entry::Occupied(chan) = channel_state.by_id.entry(channel_id.clone()) {
if let Some(node_id) = peer_node_id {
if chan.get().get_counterparty_node_id() != *node_id {
- // Error or Ok here doesn't matter - the result is only exposed publicly
- // when peer_node_id is None anyway.
- return Ok(());
+ return Err(APIError::ChannelUnavailable{err: "No such channel".to_owned()});
}
}
if let Some(short_id) = chan.get().get_short_channel_id() {
});
}
- Ok(())
+ Ok(chan.get_counterparty_node_id())
}
/// Force closes a channel, immediately broadcasting the latest local commitment transaction to
/// the chain and rejecting new HTLCs on the given channel. Fails if channel_id is unknown to the manager.
pub fn force_close_channel(&self, channel_id: &[u8; 32]) -> Result<(), APIError> {
let _persistence_guard = PersistenceNotifierGuard::new(&self.total_consistency_lock, &self.persistence_notifier);
- self.force_close_channel_with_peer(channel_id, None)
+ match self.force_close_channel_with_peer(channel_id, None) {
+ Ok(counterparty_node_id) => {
+ self.channel_state.lock().unwrap().pending_msg_events.push(
+ events::MessageSendEvent::HandleError {
+ node_id: counterparty_node_id,
+ action: msgs::ErrorAction::SendErrorMessage {
+ msg: msgs::ErrorMessage { channel_id: *channel_id, data: "Channel force-closed".to_owned() }
+ },
+ }
+ );
+ Ok(())
+ },
+ Err(e) => Err(e)
+ }
}
/// Force close all channels, immediately broadcasting the latest local commitment transaction
if fee.is_none() || msg.amount_msat < fee.unwrap() || (msg.amount_msat - fee.unwrap()) < *amt_to_forward { // fee_insufficient
break Some(("Prior hop has deviated from specified fees parameters or origin node has obsolete ones", 0x1000 | 12, Some(self.get_channel_update(chan).unwrap())));
}
- if (msg.cltv_expiry as u64) < (*outgoing_cltv_value) as u64 + CLTV_EXPIRY_DELTA as u64 { // incorrect_cltv_expiry
+ if (msg.cltv_expiry as u64) < (*outgoing_cltv_value) as u64 + chan.get_cltv_expiry_delta() as u64 { // incorrect_cltv_expiry
break Some(("Forwarding node has tampered with the intended HTLC values or origin node has an obsolete cltv_expiry_delta", 0x1000 | 13, Some(self.get_channel_update(chan).unwrap())));
}
let cur_height = self.latest_block_height.load(Ordering::Acquire) as u32 + 1;
short_channel_id,
timestamp: chan.get_update_time_counter(),
flags: (!were_node_one) as u8 | ((!chan.is_live() as u8) << 1),
- cltv_expiry_delta: CLTV_EXPIRY_DELTA,
+ cltv_expiry_delta: chan.get_cltv_expiry_delta(),
htlc_minimum_msat: chan.get_counterparty_htlc_minimum_msat(),
htlc_maximum_msat: OptionalField::Present(chan.get_announced_htlc_max_msat()),
fee_base_msat: chan.get_holder_fee_base_msat(&self.fee_estimator),
/// Call this upon creation of a funding transaction for the given channel.
///
- /// Note that ALL inputs in the transaction pointed to by funding_txo MUST spend SegWit outputs
- /// or your counterparty can steal your funds!
+ /// Returns an [`APIError::APIMisuseError`] if the funding_transaction spent non-SegWit outputs
+ /// or if no output was found which matches the parameters in [`Event::FundingGenerationReady`].
///
/// Panics if a funding transaction has already been provided for this channel.
///
- /// May panic if the funding_txo is duplicative with some other channel (note that this should
- /// be trivially prevented by using unique funding transaction keys per-channel).
- pub fn funding_transaction_generated(&self, temporary_channel_id: &[u8; 32], funding_txo: OutPoint) {
+ /// May panic if the output found in the funding transaction is duplicative with some other
+ /// channel (note that this should be trivially prevented by using unique funding transaction
+ /// keys per-channel).
+ ///
+ /// Do NOT broadcast the funding transaction yourself. When we have safely received our
+ /// counterparty's signature the funding transaction will automatically be broadcast via the
+ /// [`BroadcasterInterface`] provided when this `ChannelManager` was constructed.
+ ///
+ /// Note that this includes RBF or similar transaction replacement strategies - lightning does
+ /// not currently support replacing a funding transaction on an existing channel. Instead,
+ /// create a new channel with a conflicting funding transaction.
+ pub fn funding_transaction_generated(&self, temporary_channel_id: &[u8; 32], funding_transaction: Transaction) -> Result<(), APIError> {
let _persistence_guard = PersistenceNotifierGuard::new(&self.total_consistency_lock, &self.persistence_notifier);
+ for inp in funding_transaction.input.iter() {
+ if inp.witness.is_empty() {
+ return Err(APIError::APIMisuseError {
+ err: "Funding transaction must be fully signed and spend Segwit outputs".to_owned()
+ });
+ }
+ }
+
let (chan, msg) = {
let (res, chan) = match self.channel_state.lock().unwrap().by_id.remove(temporary_channel_id) {
Some(mut chan) => {
- (chan.get_outbound_funding_created(funding_txo, &self.logger)
+ let mut output_index = None;
+ let expected_spk = chan.get_funding_redeemscript().to_v0_p2wsh();
+ for (idx, outp) in funding_transaction.output.iter().enumerate() {
+ if outp.script_pubkey == expected_spk && outp.value == chan.get_value_satoshis() {
+ if output_index.is_some() {
+ return Err(APIError::APIMisuseError {
+ err: "Multiple outputs matched the expected script and value".to_owned()
+ });
+ }
+ if idx > u16::max_value() as usize {
+ return Err(APIError::APIMisuseError {
+ err: "Transaction had more than 2^16 outputs, which is not supported".to_owned()
+ });
+ }
+ output_index = Some(idx as u16);
+ }
+ }
+ if output_index.is_none() {
+ return Err(APIError::APIMisuseError {
+ err: "No output matched the script_pubkey and value in the FundingGenerationReady event".to_owned()
+ });
+ }
+ let funding_txo = OutPoint { txid: funding_transaction.txid(), index: output_index.unwrap() };
+
+ (chan.get_outbound_funding_created(funding_transaction, funding_txo, &self.logger)
.map_err(|e| if let ChannelError::Close(msg) = e {
MsgHandleErrInternal::from_finish_shutdown(msg, chan.channel_id(), chan.force_shutdown(true), None)
} else { unreachable!(); })
, chan)
},
- None => return
+ None => { return Err(APIError::ChannelUnavailable { err: "No such channel".to_owned() }) },
};
match handle_error!(self, res, chan.get_counterparty_node_id()) {
Ok(funding_msg) => {
(chan, funding_msg)
},
- Err(_) => { return; }
+ Err(_) => { return Err(APIError::ChannelUnavailable {
+ err: "Error deriving keys or signing initial commitment transactions - either our RNG or our counterparty's RNG is broken or the Signer refused to sign".to_owned()
+ }) },
}
};
e.insert(chan);
}
}
+ Ok(())
}
fn get_announcement_sigs(&self, chan: &Channel<Signer>) -> Option<msgs::AnnouncementSignatures> {
events.append(&mut new_events);
}
- /// Free the background events, generally called from timer_chan_freshness_every_min.
+ /// Free the background events, generally called from timer_tick_occurred.
///
/// Exposed for testing to allow us to process events quickly without generating accidental
- /// BroadcastChannelUpdate events in timer_chan_freshness_every_min.
+ /// BroadcastChannelUpdate events in timer_tick_occurred.
///
/// Expects the caller to have a total_consistency_lock read lock.
fn process_background_events(&self) {
/// This method handles all the details, and must be called roughly once per minute.
///
/// Note that in some rare cases this may generate a `chain::Watch::update_channel` call.
- pub fn timer_chan_freshness_every_min(&self) {
+ pub fn timer_tick_occurred(&self) {
let _persistence_guard = PersistenceNotifierGuard::new(&self.total_consistency_lock, &self.persistence_notifier);
self.process_background_events();
/// Gets the node_id held by this ChannelManager
pub fn get_our_node_id(&self) -> PublicKey {
- PublicKey::from_secret_key(&self.secp_ctx, &self.our_network_key)
+ self.our_network_pubkey.clone()
}
/// Restores a single, given channel to normal operation after a
return;
}
- let (raa, commitment_update, order, pending_forwards, mut pending_failures, needs_broadcast_safe, funding_locked) = channel.monitor_updating_restored(&self.logger);
+ let (raa, commitment_update, order, pending_forwards, mut pending_failures, funding_broadcastable, funding_locked) = channel.monitor_updating_restored(&self.logger);
if !pending_forwards.is_empty() {
htlc_forwards.push((channel.get_short_channel_id().expect("We can't have pending forwards before funding confirmation"), funding_txo.clone(), pending_forwards));
}
handle_cs!();
},
}
- if needs_broadcast_safe {
- pending_events.push(events::Event::FundingBroadcastSafe {
- funding_txo: channel.get_funding_txo().unwrap(),
- user_channel_id: channel.get_user_id(),
- });
+ if let Some(tx) = funding_broadcastable {
+ self.tx_broadcaster.broadcast_transaction(&tx);
}
if let Some(msg) = funding_locked {
pending_msg_events.push(events::MessageSendEvent::SendFundingLocked {
}
fn internal_funding_signed(&self, counterparty_node_id: &PublicKey, msg: &msgs::FundingSigned) -> Result<(), MsgHandleErrInternal> {
- let (funding_txo, user_id) = {
+ let funding_tx = {
let last_block_hash = *self.last_block_hash.read().unwrap();
let mut channel_lock = self.channel_state.lock().unwrap();
let channel_state = &mut *channel_lock;
if chan.get().get_counterparty_node_id() != *counterparty_node_id {
return Err(MsgHandleErrInternal::send_err_msg_no_close("Got a message for a channel from the wrong node!".to_owned(), msg.channel_id));
}
- let monitor = match chan.get_mut().funding_signed(&msg, last_block_hash, &self.logger) {
+ let (monitor, funding_tx) = match chan.get_mut().funding_signed(&msg, last_block_hash, &self.logger) {
Ok(update) => update,
Err(e) => try_chan_entry!(self, Err(e), channel_state, chan),
};
if let Err(e) = self.chain_monitor.watch_channel(chan.get().get_funding_txo().unwrap(), monitor) {
return_monitor_err!(self, e, channel_state, chan, RAACommitmentOrder::RevokeAndACKFirst, false, false);
}
- (chan.get().get_funding_txo().unwrap(), chan.get().get_user_id())
+ funding_tx
},
hash_map::Entry::Vacant(_) => return Err(MsgHandleErrInternal::send_err_msg_no_close("Failed to find corresponding channel".to_owned(), msg.channel_id))
}
};
- let mut pending_events = self.pending_events.lock().unwrap();
- pending_events.push(events::Event::FundingBroadcastSafe {
- funding_txo,
- user_channel_id: user_id,
- });
+ self.tx_broadcaster.broadcast_transaction(&funding_tx);
Ok(())
}
msg: update
});
}
+ pending_msg_events.push(events::MessageSendEvent::HandleError {
+ node_id: chan.get_counterparty_node_id(),
+ action: msgs::ErrorAction::SendErrorMessage {
+ msg: msgs::ErrorMessage { channel_id: chan.channel_id(), data: "Channel force-closed".to_owned() }
+ },
+ });
}
},
}
// We cannot broadcast our latest local state via monitor update (as
// Channel::force_shutdown tries to make us do) as we may still be in initialization,
// so we track the update internally and handle it when the user next calls
- // timer_chan_freshness_every_min, guaranteeing we're running normally.
+ // timer_tick_occurred, guaranteeing we're running normally.
if let Some((funding_txo, update)) = failure.0.take() {
assert_eq!(update.updates.len(), 1);
if let ChannelMonitorUpdateStep::ChannelForceClosed { should_broadcast } = update.updates[0] {
L::Target: Logger,
{
fn block_connected(&self, block: &Block, height: u32) {
+ assert_eq!(*self.last_block_hash.read().unwrap(), block.header.prev_blockhash,
+ "Blocks must be connected in chain-order - the connected header must build on the last connected header");
+ assert_eq!(self.latest_block_height.load(Ordering::Acquire) as u64, height as u64 - 1,
+ "Blocks must be connected in chain-order - the connected block height must be one greater than the previous height");
let txdata: Vec<_> = block.txdata.iter().enumerate().collect();
- ChannelManager::block_connected(self, &block.header, &txdata, height);
+ self.transactions_confirmed(&block.header, height, &txdata);
+ self.update_best_block(&block.header, height);
}
- fn block_disconnected(&self, header: &BlockHeader, _height: u32) {
- ChannelManager::block_disconnected(self, header);
+ fn block_disconnected(&self, header: &BlockHeader, height: u32) {
+ assert_eq!(*self.last_block_hash.read().unwrap(), header.block_hash(),
+ "Blocks must be disconnected in chain-order - the disconnected header must be the last connected header");
+
+ let _persistence_guard = PersistenceNotifierGuard::new(&self.total_consistency_lock, &self.persistence_notifier);
+ let new_height = self.latest_block_height.fetch_sub(1, Ordering::AcqRel) as u32 - 1;
+ assert_eq!(new_height, height - 1,
+ "Blocks must be disconnected in chain-order - the disconnected block must have the correct height");
+ *self.last_block_hash.write().unwrap() = header.prev_blockhash;
+
+ self.do_chain_event(new_height, |channel| channel.update_best_block(new_height, header.time));
}
}
F::Target: FeeEstimator,
L::Target: Logger,
{
- /// Updates channel state based on transactions seen in a connected block.
- pub fn block_connected(&self, header: &BlockHeader, txdata: &TransactionData, height: u32) {
+ fn do_chain_event<FN: Fn(&mut Channel<Signer>) -> Result<(Option<msgs::FundingLocked>, Vec<(HTLCSource, PaymentHash)>), msgs::ErrorMessage>>
+ (&self, height: u32, f: FN) {
// Note that we MUST NOT end up calling methods on self.chain_monitor here - we're called
// during initialization prior to the chain_monitor being fully configured in some cases.
// See the docs for `ChannelManagerReadArgs` for more.
- let block_hash = header.block_hash();
- log_trace!(self.logger, "Block {} at height {} connected", block_hash, height);
-
- let _persistence_guard = PersistenceNotifierGuard::new(&self.total_consistency_lock, &self.persistence_notifier);
-
- self.latest_block_height.store(height as usize, Ordering::Release);
- *self.last_block_hash.write().unwrap() = block_hash;
let mut failed_channels = Vec::new();
let mut timed_out_htlcs = Vec::new();
let short_to_id = &mut channel_state.short_to_id;
let pending_msg_events = &mut channel_state.pending_msg_events;
channel_state.by_id.retain(|_, channel| {
- let res = channel.block_connected(header, txdata, height);
+ let res = f(channel);
if let Ok((chan_res, mut timed_out_pending_htlcs)) = res {
for (source, payment_hash) in timed_out_pending_htlcs.drain(..) {
let chan_update = self.get_channel_update(&channel).map(|u| u.encode_with_len()).unwrap(); // Cannot add/recv HTLCs before we have a short_id so unwrap is safe
short_to_id.insert(channel.get_short_channel_id().unwrap(), channel.channel_id());
}
} else if let Err(e) = res {
+ if let Some(short_id) = channel.get_short_channel_id() {
+ short_to_id.remove(&short_id);
+ }
+ // It looks like our counterparty went on-chain or funding transaction was
+ // reorged out of the main chain. Close the channel.
+ failed_channels.push(channel.force_shutdown(true));
+ if let Ok(update) = self.get_channel_update(&channel) {
+ pending_msg_events.push(events::MessageSendEvent::BroadcastChannelUpdate {
+ msg: update
+ });
+ }
pending_msg_events.push(events::MessageSendEvent::HandleError {
node_id: channel.get_counterparty_node_id(),
action: msgs::ErrorAction::SendErrorMessage { msg: e },
});
return false;
}
- if let Some(funding_txo) = channel.get_funding_txo() {
- for &(_, tx) in txdata.iter() {
- for inp in tx.input.iter() {
- if inp.previous_output == funding_txo.into_bitcoin_outpoint() {
- log_trace!(self.logger, "Detected channel-closing tx {} spending {}:{}, closing channel {}", tx.txid(), inp.previous_output.txid, inp.previous_output.vout, log_bytes!(channel.channel_id()));
- if let Some(short_id) = channel.get_short_channel_id() {
- short_to_id.remove(&short_id);
- }
- // It looks like our counterparty went on-chain. Close the channel.
- failed_channels.push(channel.force_shutdown(true));
- if let Ok(update) = self.get_channel_update(&channel) {
- pending_msg_events.push(events::MessageSendEvent::BroadcastChannelUpdate {
- msg: update
- });
- }
- return false;
- }
- }
- }
- }
true
});
for (source, payment_hash, reason) in timed_out_htlcs.drain(..) {
self.fail_htlc_backwards_internal(self.channel_state.lock().unwrap(), source, &payment_hash, reason);
}
+ }
+
+ /// Updates channel state to take note of transactions which were confirmed in the given block
+ /// at the given height.
+ ///
+ /// Note that you must still call (or have called) [`update_best_block`] with the block
+ /// information which is included here.
+ ///
+ /// This method may be called before or after [`update_best_block`] for a given block's
+ /// transaction data and may be called multiple times with additional transaction data for a
+ /// given block.
+ ///
+ /// This method may be called for a previous block after an [`update_best_block`] call has
+ /// been made for a later block, however it must *not* be called with transaction data from a
+ /// block which is no longer in the best chain (ie where [`update_best_block`] has already
+ /// been informed about a blockchain reorganization which no longer includes the block which
+ /// corresponds to `header`).
+ ///
+ /// [`update_best_block`]: `Self::update_best_block`
+ pub fn transactions_confirmed(&self, header: &BlockHeader, height: u32, txdata: &TransactionData) {
+ // Note that we MUST NOT end up calling methods on self.chain_monitor here - we're called
+ // during initialization prior to the chain_monitor being fully configured in some cases.
+ // See the docs for `ChannelManagerReadArgs` for more.
+
+ let block_hash = header.block_hash();
+ log_trace!(self.logger, "{} transactions included in block {} at height {} provided", txdata.len(), block_hash, height);
+
+ let _persistence_guard = PersistenceNotifierGuard::new(&self.total_consistency_lock, &self.persistence_notifier);
+ self.do_chain_event(height, |channel| channel.transactions_confirmed(&block_hash, height, txdata, &self.logger).map(|a| (a, Vec::new())));
+ }
+
+ /// Updates channel state with the current best blockchain tip. You should attempt to call this
+ /// quickly after a new block becomes available, however if multiple new blocks become
+ /// available at the same time, only a single `update_best_block()` call needs to be made.
+ ///
+ /// This method should also be called immediately after any block disconnections, once at the
+ /// reorganization fork point, and once with the new chain tip. Calling this method at the
+ /// blockchain reorganization fork point ensures we learn when a funding transaction which was
+ /// previously confirmed is reorganized out of the blockchain, ensuring we do not continue to
+ /// accept payments which cannot be enforced on-chain.
+ ///
+ /// In both the block-connection and block-disconnection case, this method may be called either
+ /// once per block connected or disconnected, or simply at the fork point and new tip(s),
+ /// skipping any intermediary blocks.
+ pub fn update_best_block(&self, header: &BlockHeader, height: u32) {
+ // Note that we MUST NOT end up calling methods on self.chain_monitor here - we're called
+ // during initialization prior to the chain_monitor being fully configured in some cases.
+ // See the docs for `ChannelManagerReadArgs` for more.
+
+ let block_hash = header.block_hash();
+ log_trace!(self.logger, "New best block: {} at height {}", block_hash, height);
+
+ let _persistence_guard = PersistenceNotifierGuard::new(&self.total_consistency_lock, &self.persistence_notifier);
+
+ self.latest_block_height.store(height as usize, Ordering::Release);
+ *self.last_block_hash.write().unwrap() = block_hash;
+
+ self.do_chain_event(height, |channel| channel.update_best_block(height, header.time));
loop {
// Update last_node_announcement_serial to be the max of its current value and the
}
}
- /// Updates channel state based on a disconnected block.
- ///
- /// If necessary, the channel may be force-closed without letting the counterparty participate
- /// in the shutdown.
- pub fn block_disconnected(&self, header: &BlockHeader) {
- // Note that we MUST NOT end up calling methods on self.chain_monitor here - we're called
- // during initialization prior to the chain_monitor being fully configured in some cases.
- // See the docs for `ChannelManagerReadArgs` for more.
- let _persistence_guard = PersistenceNotifierGuard::new(&self.total_consistency_lock, &self.persistence_notifier);
-
- self.latest_block_height.fetch_sub(1, Ordering::AcqRel);
- *self.last_block_hash.write().unwrap() = header.prev_blockhash;
-
- let mut failed_channels = Vec::new();
- {
- let mut channel_lock = self.channel_state.lock().unwrap();
- let channel_state = &mut *channel_lock;
- let short_to_id = &mut channel_state.short_to_id;
- let pending_msg_events = &mut channel_state.pending_msg_events;
- channel_state.by_id.retain(|_, v| {
- if v.block_disconnected(header) {
- if let Some(short_id) = v.get_short_channel_id() {
- short_to_id.remove(&short_id);
- }
- failed_channels.push(v.force_shutdown(true));
- if let Ok(update) = self.get_channel_update(&v) {
- pending_msg_events.push(events::MessageSendEvent::BroadcastChannelUpdate {
- msg: update
- });
- }
- false
- } else {
- true
- }
- });
- }
-
- self.handle_init_event_channel_failures(failed_channels);
- }
-
/// Blocks until ChannelManager needs to be persisted or a timeout is reached. It returns a bool
/// indicating whether persistence is necessary. Only one listener on
/// `await_persistable_update` or `await_persistable_update_timeout` is guaranteed to be woken
&events::MessageSendEvent::PaymentFailureNetworkUpdate { .. } => true,
&events::MessageSendEvent::SendChannelRangeQuery { .. } => false,
&events::MessageSendEvent::SendShortIdsQuery { .. } => false,
+ &events::MessageSendEvent::SendReplyChannelRange { .. } => false,
}
});
}
latest_block_height: AtomicUsize::new(latest_block_height as usize),
last_block_hash: RwLock::new(last_block_hash),
- secp_ctx,
channel_state: Mutex::new(ChannelHolder {
by_id,
pending_msg_events: Vec::new(),
}),
our_network_key: args.keys_manager.get_node_secret(),
+ our_network_pubkey: PublicKey::from_secret_key(&secp_ctx, &args.keys_manager.get_node_secret()),
+ secp_ctx,
last_node_announcement_serial: AtomicUsize::new(last_node_announcement_serial as usize),
}
}
}
+
+#[cfg(all(any(test, feature = "_test_utils"), feature = "unstable"))]
+pub mod bench {
+ use chain::Listen;
+ use chain::chainmonitor::ChainMonitor;
+ use chain::channelmonitor::Persist;
+ use chain::keysinterface::{KeysManager, InMemorySigner};
+ use ln::channelmanager::{ChainParameters, ChannelManager, PaymentHash, PaymentPreimage};
+ use ln::features::InitFeatures;
+ use ln::functional_test_utils::*;
+ use ln::msgs::ChannelMessageHandler;
+ use routing::network_graph::NetworkGraph;
+ use routing::router::get_route;
+ use util::test_utils;
+ use util::config::UserConfig;
+ use util::events::{Event, EventsProvider, MessageSendEvent, MessageSendEventsProvider};
+
+ use bitcoin::hashes::Hash;
+ use bitcoin::hashes::sha256::Hash as Sha256;
+ use bitcoin::{Block, BlockHeader, Transaction, TxOut};
+
+ use std::sync::Mutex;
+
+ use test::Bencher;
+
+ struct NodeHolder<'a, P: Persist<InMemorySigner>> {
+ node: &'a ChannelManager<InMemorySigner,
+ &'a ChainMonitor<InMemorySigner, &'a test_utils::TestChainSource,
+ &'a test_utils::TestBroadcaster, &'a test_utils::TestFeeEstimator,
+ &'a test_utils::TestLogger, &'a P>,
+ &'a test_utils::TestBroadcaster, &'a KeysManager,
+ &'a test_utils::TestFeeEstimator, &'a test_utils::TestLogger>
+ }
+
+ #[cfg(test)]
+ #[bench]
+ fn bench_sends(bench: &mut Bencher) {
+ bench_two_sends(bench, test_utils::TestPersister::new(), test_utils::TestPersister::new());
+ }
+
+ pub fn bench_two_sends<P: Persist<InMemorySigner>>(bench: &mut Bencher, persister_a: P, persister_b: P) {
+ // Do a simple benchmark of sending a payment back and forth between two nodes.
+ // Note that this is unrealistic as each payment send will require at least two fsync
+ // calls per node.
+ let network = bitcoin::Network::Testnet;
+ let genesis_hash = bitcoin::blockdata::constants::genesis_block(network).header.block_hash();
+
+ let tx_broadcaster = test_utils::TestBroadcaster{txn_broadcasted: Mutex::new(Vec::new())};
+ let fee_estimator = test_utils::TestFeeEstimator { sat_per_kw: 253 };
+
+ let mut config: UserConfig = Default::default();
+ config.own_channel_config.minimum_depth = 1;
+
+ let logger_a = test_utils::TestLogger::with_id("node a".to_owned());
+ let chain_monitor_a = ChainMonitor::new(None, &tx_broadcaster, &logger_a, &fee_estimator, &persister_a);
+ let seed_a = [1u8; 32];
+ let keys_manager_a = KeysManager::new(&seed_a, 42, 42);
+ let node_a = ChannelManager::new(&fee_estimator, &chain_monitor_a, &tx_broadcaster, &logger_a, &keys_manager_a, config.clone(), ChainParameters {
+ network,
+ latest_hash: genesis_hash,
+ latest_height: 0,
+ });
+ let node_a_holder = NodeHolder { node: &node_a };
+
+ let logger_b = test_utils::TestLogger::with_id("node a".to_owned());
+ let chain_monitor_b = ChainMonitor::new(None, &tx_broadcaster, &logger_a, &fee_estimator, &persister_b);
+ let seed_b = [2u8; 32];
+ let keys_manager_b = KeysManager::new(&seed_b, 42, 42);
+ let node_b = ChannelManager::new(&fee_estimator, &chain_monitor_b, &tx_broadcaster, &logger_b, &keys_manager_b, config.clone(), ChainParameters {
+ network,
+ latest_hash: genesis_hash,
+ latest_height: 0,
+ });
+ let node_b_holder = NodeHolder { node: &node_b };
+
+ node_a.create_channel(node_b.get_our_node_id(), 8_000_000, 100_000_000, 42, None).unwrap();
+ node_b.handle_open_channel(&node_a.get_our_node_id(), InitFeatures::known(), &get_event_msg!(node_a_holder, MessageSendEvent::SendOpenChannel, node_b.get_our_node_id()));
+ node_a.handle_accept_channel(&node_b.get_our_node_id(), InitFeatures::known(), &get_event_msg!(node_b_holder, MessageSendEvent::SendAcceptChannel, node_a.get_our_node_id()));
+
+ let tx;
+ if let Event::FundingGenerationReady { temporary_channel_id, output_script, .. } = get_event!(node_a_holder, Event::FundingGenerationReady) {
+ tx = Transaction { version: 2, lock_time: 0, input: Vec::new(), output: vec![TxOut {
+ value: 8_000_000, script_pubkey: output_script,
+ }]};
+ node_a.funding_transaction_generated(&temporary_channel_id, tx.clone()).unwrap();
+ } else { panic!(); }
+
+ node_b.handle_funding_created(&node_a.get_our_node_id(), &get_event_msg!(node_a_holder, MessageSendEvent::SendFundingCreated, node_b.get_our_node_id()));
+ node_a.handle_funding_signed(&node_b.get_our_node_id(), &get_event_msg!(node_b_holder, MessageSendEvent::SendFundingSigned, node_a.get_our_node_id()));
+
+ assert_eq!(&tx_broadcaster.txn_broadcasted.lock().unwrap()[..], &[tx.clone()]);
+
+ let block = Block {
+ header: BlockHeader { version: 0x20000000, prev_blockhash: genesis_hash, merkle_root: Default::default(), time: 42, bits: 42, nonce: 42 },
+ txdata: vec![tx],
+ };
+ Listen::block_connected(&node_a, &block, 1);
+ Listen::block_connected(&node_b, &block, 1);
+
+ node_a.handle_funding_locked(&node_b.get_our_node_id(), &get_event_msg!(node_b_holder, MessageSendEvent::SendFundingLocked, node_a.get_our_node_id()));
+ node_b.handle_funding_locked(&node_a.get_our_node_id(), &get_event_msg!(node_a_holder, MessageSendEvent::SendFundingLocked, node_b.get_our_node_id()));
+
+ let dummy_graph = NetworkGraph::new(genesis_hash);
+
+ macro_rules! send_payment {
+ ($node_a: expr, $node_b: expr) => {
+ let usable_channels = $node_a.list_usable_channels();
+ let route = get_route(&$node_a.get_our_node_id(), &dummy_graph, &$node_b.get_our_node_id(), None, Some(&usable_channels.iter().map(|r| r).collect::<Vec<_>>()), &[], 10_000, TEST_FINAL_CLTV, &logger_a).unwrap();
+
+ let payment_preimage = PaymentPreimage([0; 32]);
+ let payment_hash = PaymentHash(Sha256::hash(&payment_preimage.0[..]).into_inner());
+
+ $node_a.send_payment(&route, payment_hash, &None).unwrap();
+ let payment_event = SendEvent::from_event($node_a.get_and_clear_pending_msg_events().pop().unwrap());
+ $node_b.handle_update_add_htlc(&$node_a.get_our_node_id(), &payment_event.msgs[0]);
+ $node_b.handle_commitment_signed(&$node_a.get_our_node_id(), &payment_event.commitment_msg);
+ let (raa, cs) = get_revoke_commit_msgs!(NodeHolder { node: &$node_b }, $node_a.get_our_node_id());
+ $node_a.handle_revoke_and_ack(&$node_b.get_our_node_id(), &raa);
+ $node_a.handle_commitment_signed(&$node_b.get_our_node_id(), &cs);
+ $node_b.handle_revoke_and_ack(&$node_a.get_our_node_id(), &get_event_msg!(NodeHolder { node: &$node_a }, MessageSendEvent::SendRevokeAndACK, $node_b.get_our_node_id()));
+
+ expect_pending_htlcs_forwardable!(NodeHolder { node: &$node_b });
+ expect_payment_received!(NodeHolder { node: &$node_b }, payment_hash, 10_000);
+ assert!($node_b.claim_funds(payment_preimage, &None, 10_000));
+
+ match $node_b.get_and_clear_pending_msg_events().pop().unwrap() {
+ MessageSendEvent::UpdateHTLCs { node_id, updates } => {
+ assert_eq!(node_id, $node_a.get_our_node_id());
+ $node_a.handle_update_fulfill_htlc(&$node_b.get_our_node_id(), &updates.update_fulfill_htlcs[0]);
+ $node_a.handle_commitment_signed(&$node_b.get_our_node_id(), &updates.commitment_signed);
+ },
+ _ => panic!("Failed to generate claim event"),
+ }
+
+ let (raa, cs) = get_revoke_commit_msgs!(NodeHolder { node: &$node_a }, $node_b.get_our_node_id());
+ $node_b.handle_revoke_and_ack(&$node_a.get_our_node_id(), &raa);
+ $node_b.handle_commitment_signed(&$node_a.get_our_node_id(), &cs);
+ $node_a.handle_revoke_and_ack(&$node_b.get_our_node_id(), &get_event_msg!(NodeHolder { node: &$node_b }, MessageSendEvent::SendRevokeAndACK, $node_a.get_our_node_id()));
+
+ expect_payment_sent!(NodeHolder { node: &$node_a }, payment_preimage);
+ }
+ }
+
+ bench.iter(|| {
+ send_payment!(node_a, node_b);
+ send_payment!(node_b, node_a);
+ });
+ }
+}