/// The hash of the block in which the funding transaction was included.
funding_tx_confirmed_in: Option<BlockHash>,
- funding_tx_confirmation_height: u64,
+ funding_tx_confirmation_height: u32,
short_channel_id: Option<u64>,
counterparty_dust_limit_satoshis: u64,
}
}
}
- self.funding_tx_confirmation_height = height as u64;
+ self.funding_tx_confirmation_height = height;
self.funding_tx_confirmed_in = Some(*block_hash);
self.short_channel_id = match scid_from_parts(height as u64, index_in_block as u64, txo_idx as u64) {
Ok(scid) => Some(scid),
Ok((None, timed_out_htlcs))
}
+ /// Indicates the funding transaction is no longer confirmed in the main chain. This may
+ /// force-close the channel, but may also indicate a harmless reorganization of a block or two
+ /// before the channel has reached funding_locked and we can just wait for more blocks.
+ pub fn funding_transaction_unconfirmed(&mut self) -> Result<(), msgs::ErrorMessage> {
+ if self.funding_tx_confirmation_height != 0 {
+ // We handle the funding disconnection by calling update_best_block with a height one
+ // below where our funding was connected, implying a reorg back to conf_height - 1.
+ let reorg_height = self.funding_tx_confirmation_height - 1;
+ // We use the time field to bump the current time we set on channel updates if its
+ // larger. If we don't know that time has moved forward, we can just set it to the last
+ // time we saw and it will be ignored.
+ let best_time = self.update_time_counter;
+ match self.update_best_block(reorg_height, best_time) {
+ Ok((funding_locked, timed_out_htlcs)) => {
+ assert!(funding_locked.is_none(), "We can't generate a funding with 0 confirmations?");
+ assert!(timed_out_htlcs.is_empty(), "We can't have accepted HTLCs with a timeout before our funding confirmation?");
+ Ok(())
+ },
+ Err(e) => Err(e)
+ }
+ } else {
+ // We never learned about the funding confirmation anyway, just ignore
+ Ok(())
+ }
+ }
+
// Methods to get unprompted messages to send to the remote end (or where we already returned
// something in the handler for the message that prompted this message):
use bitcoin::hashes::sha256::Hash as Sha256;
use bitcoin::hashes::sha256d::Hash as Sha256dHash;
use bitcoin::hashes::cmp::fixed_time_eq;
-use bitcoin::hash_types::BlockHash;
+use bitcoin::hash_types::{BlockHash, Txid};
use bitcoin::secp256k1::key::{SecretKey,PublicKey};
use bitcoin::secp256k1::Secp256k1;
"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));
+ self.do_chain_event(Some(new_height), |channel| channel.update_best_block(new_height, header.time));
}
}
F::Target: FeeEstimator,
L::Target: Logger,
{
+ /// Calls a function which handles an on-chain event (blocks dis/connected, transactions
+ /// un/confirmed, etc) on each channel, handling any resulting errors or messages generated by
+ /// the function.
fn do_chain_event<FN: Fn(&mut Channel<Signer>) -> Result<(Option<msgs::FundingLocked>, Vec<(HTLCSource, PaymentHash)>), msgs::ErrorMessage>>
- (&self, height: u32, f: FN) {
+ (&self, height_opt: Option<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.
true
});
- channel_state.claimable_htlcs.retain(|&(ref payment_hash, _), htlcs| {
- htlcs.retain(|htlc| {
- // If height is approaching the number of blocks we think it takes us to get
- // our commitment transaction confirmed before the HTLC expires, plus the
- // number of blocks we generally consider it to take to do a commitment update,
- // just give up on it and fail the HTLC.
- if height >= htlc.cltv_expiry - HTLC_FAIL_BACK_BUFFER {
- let mut htlc_msat_height_data = byte_utils::be64_to_array(htlc.value).to_vec();
- htlc_msat_height_data.extend_from_slice(&byte_utils::be32_to_array(height));
- timed_out_htlcs.push((HTLCSource::PreviousHopData(htlc.prev_hop.clone()), payment_hash.clone(), HTLCFailReason::Reason {
- failure_code: 0x4000 | 15,
- data: htlc_msat_height_data
- }));
- false
- } else { true }
+ if let Some(height) = height_opt {
+ channel_state.claimable_htlcs.retain(|&(ref payment_hash, _), htlcs| {
+ htlcs.retain(|htlc| {
+ // If height is approaching the number of blocks we think it takes us to get
+ // our commitment transaction confirmed before the HTLC expires, plus the
+ // number of blocks we generally consider it to take to do a commitment update,
+ // just give up on it and fail the HTLC.
+ if height >= htlc.cltv_expiry - HTLC_FAIL_BACK_BUFFER {
+ let mut htlc_msat_height_data = byte_utils::be64_to_array(htlc.value).to_vec();
+ htlc_msat_height_data.extend_from_slice(&byte_utils::be32_to_array(height));
+ timed_out_htlcs.push((HTLCSource::PreviousHopData(htlc.prev_hop.clone()), payment_hash.clone(), HTLCFailReason::Reason {
+ failure_code: 0x4000 | 15,
+ data: htlc_msat_height_data
+ }));
+ false
+ } else { true }
+ });
+ !htlcs.is_empty() // Only retain this entry if htlcs has at least one entry.
});
- !htlcs.is_empty() // Only retain this entry if htlcs has at least one entry.
- });
+ }
}
self.handle_init_event_channel_failures(failed_channels);
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())));
+ self.do_chain_event(Some(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
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));
+ self.do_chain_event(Some(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
}
}
+ /// Gets the set of txids which should be monitored for their confirmation state.
+ ///
+ /// If you're providing information about reorganizations via [`transaction_unconfirmed`], this
+ /// is the set of transactions which you may need to call [`transaction_unconfirmed`] for.
+ ///
+ /// This may be useful to poll to determine the set of transactions which must be registered
+ /// with an Electrum server or for which an Electrum server needs to be polled to determine
+ /// transaction confirmation state.
+ ///
+ /// This may update after any [`transactions_confirmed`] or [`block_connected`] call.
+ ///
+ /// Note that this is NOT the set of transactions which must be included in calls to
+ /// [`transactions_confirmed`] if they are confirmed, but a small subset of it.
+ ///
+ /// [`transactions_confirmed`]: Self::transactions_confirmed
+ /// [`transaction_unconfirmed`]: Self::transaction_unconfirmed
+ /// [`block_connected`]: chain::Listen::block_connected
+ pub fn get_relevant_txids(&self) -> Vec<Txid> {
+ let channel_state = self.channel_state.lock().unwrap();
+ let mut res = Vec::with_capacity(channel_state.short_to_id.len());
+ for chan in channel_state.by_id.values() {
+ if let Some(funding_txo) = chan.get_funding_txo() {
+ res.push(funding_txo.txid);
+ }
+ }
+ res
+ }
+
+ /// Marks a transaction as having been reorganized out of the blockchain.
+ ///
+ /// If a transaction is included in [`get_relevant_txids`], and is no longer in the main branch
+ /// of the blockchain, this function should be called to indicate that the transaction should
+ /// be considered reorganized out.
+ ///
+ /// Once this is called, the given transaction will no longer appear on [`get_relevant_txids`],
+ /// though this may be called repeatedly for a given transaction without issue.
+ ///
+ /// Note that if the transaction is confirmed on the main chain in a different block (indicated
+ /// via a call to [`transactions_confirmed`]), it may re-appear in [`get_relevant_txids`], thus
+ /// be very wary of race-conditions wherein the final state of a transaction indicated via
+ /// these APIs is not the same as its state on the blockchain.
+ ///
+ /// [`transactions_confirmed`]: Self::transactions_confirmed
+ /// [`get_relevant_txids`]: Self::get_relevant_txids
+ pub fn transaction_unconfirmed(&self, txid: &Txid) {
+ let _persistence_guard = PersistenceNotifierGuard::new(&self.total_consistency_lock, &self.persistence_notifier);
+ self.do_chain_event(None, |channel| {
+ if let Some(funding_txo) = channel.get_funding_txo() {
+ if funding_txo.txid == *txid {
+ channel.funding_transaction_unconfirmed().map(|_| (None, Vec::new()))
+ } else { Ok((None, Vec::new())) }
+ } else { Ok((None, Vec::new())) }
+ });
+ }
+
/// 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