Move ChannelManager to Channel's new block data API

This also moves the scanning of the block for commitment
transactions into channel, unifying the error path.
This commit is contained in:
Matt Corallo 2021-03-18 20:32:30 -04:00
parent 871f414367
commit 60b962a18e
2 changed files with 69 additions and 99 deletions

View file

@ -7,7 +7,6 @@
// You may not use this file except in accordance with one or both of these // You may not use this file except in accordance with one or both of these
// licenses. // licenses.
use bitcoin::blockdata::block::BlockHeader;
use bitcoin::blockdata::script::{Script,Builder}; use bitcoin::blockdata::script::{Script,Builder};
use bitcoin::blockdata::transaction::{TxIn, TxOut, Transaction, SigHashType}; use bitcoin::blockdata::transaction::{TxIn, TxOut, Transaction, SigHashType};
use bitcoin::blockdata::opcodes; use bitcoin::blockdata::opcodes;
@ -3502,47 +3501,63 @@ impl<Signer: Sign> Channel<Signer> {
self.network_sync == UpdateStatus::DisabledMarked self.network_sync == UpdateStatus::DisabledMarked
} }
pub fn transactions_confirmed(&mut self, block_hash: &BlockHash, height: u32, txdata: &TransactionData) -> Result<(), msgs::ErrorMessage> { /// When a transaction is confirmed, we check whether it is or spends the funding transaction
/// In the first case, we store the confirmation height and calculating the short channel id.
/// In the second, we simply return an Err indicating we need to be force-closed now.
pub fn transactions_confirmed<L: Deref>(&mut self, block_hash: &BlockHash, height: u32, txdata: &TransactionData, logger: &L)
-> Result<(), msgs::ErrorMessage> where L::Target: Logger {
let non_shutdown_state = self.channel_state & (!MULTI_STATE_FLAGS); let non_shutdown_state = self.channel_state & (!MULTI_STATE_FLAGS);
if non_shutdown_state & !(ChannelState::TheirFundingLocked as u32) == ChannelState::FundingSent as u32 { for &(index_in_block, tx) in txdata.iter() {
for &(index_in_block, tx) in txdata.iter() { if let Some(funding_txo) = self.get_funding_txo() {
let funding_txo = self.get_funding_txo().unwrap(); // If we haven't yet sent a funding_locked, but are in FundingSent (ignoring
if tx.txid() == funding_txo.txid { // whether they've sent a funding_locked or not), check if we should send one.
let txo_idx = funding_txo.index as usize; if non_shutdown_state & !(ChannelState::TheirFundingLocked as u32) == ChannelState::FundingSent as u32 {
if txo_idx >= tx.output.len() || tx.output[txo_idx].script_pubkey != self.get_funding_redeemscript().to_v0_p2wsh() || if tx.txid() == funding_txo.txid {
tx.output[txo_idx].value != self.channel_value_satoshis { let txo_idx = funding_txo.index as usize;
if self.is_outbound() { if txo_idx >= tx.output.len() || tx.output[txo_idx].script_pubkey != self.get_funding_redeemscript().to_v0_p2wsh() ||
// If we generated the funding transaction and it doesn't match what it tx.output[txo_idx].value != self.channel_value_satoshis {
// should, the client is really broken and we should just panic and if self.is_outbound() {
// tell them off. That said, because hash collisions happen with high // If we generated the funding transaction and it doesn't match what it
// probability in fuzztarget mode, if we're fuzzing we just close the // should, the client is really broken and we should just panic and
// channel and move on. // tell them off. That said, because hash collisions happen with high
#[cfg(not(feature = "fuzztarget"))] // probability in fuzztarget mode, if we're fuzzing we just close the
panic!("Client called ChannelManager::funding_transaction_generated with bogus transaction!"); // channel and move on.
} #[cfg(not(feature = "fuzztarget"))]
self.channel_state = ChannelState::ShutdownComplete as u32; panic!("Client called ChannelManager::funding_transaction_generated with bogus transaction!");
self.update_time_counter += 1; }
return Err(msgs::ErrorMessage { self.channel_state = ChannelState::ShutdownComplete as u32;
channel_id: self.channel_id(), self.update_time_counter += 1;
data: "funding tx had wrong script/value".to_owned() return Err(msgs::ErrorMessage {
}); channel_id: self.channel_id(),
} else { data: "funding tx had wrong script/value or output index".to_owned()
if self.is_outbound() { });
for input in tx.input.iter() { } else {
if input.witness.is_empty() { if self.is_outbound() {
// We generated a malleable funding transaction, implying we've for input in tx.input.iter() {
// just exposed ourselves to funds loss to our counterparty. if input.witness.is_empty() {
#[cfg(not(feature = "fuzztarget"))] // We generated a malleable funding transaction, implying we've
panic!("Client called ChannelManager::funding_transaction_generated with bogus transaction!"); // just exposed ourselves to funds loss to our counterparty.
#[cfg(not(feature = "fuzztarget"))]
panic!("Client called ChannelManager::funding_transaction_generated with bogus transaction!");
}
} }
} }
self.funding_tx_confirmation_height = height as u64;
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),
Err(_) => panic!("Block was bogus - either height was > 16 million, had > 16 million transactions, or had > 65k outputs"),
}
} }
self.funding_tx_confirmation_height = height as u64; }
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) { for inp in tx.input.iter() {
Ok(scid) => Some(scid), if inp.previous_output == funding_txo.into_bitcoin_outpoint() {
Err(_) => panic!("Block was bogus - either height was > 16 million, had > 16 million transactions, or had > 65k outputs"), log_trace!(logger, "Detected channel-closing tx {} spending {}:{}, closing channel {}", tx.txid(), inp.previous_output.txid, inp.previous_output.vout, log_bytes!(self.channel_id()));
} return Err(msgs::ErrorMessage {
channel_id: self.channel_id(),
data: "Commitment or closing transaction was confirmed on chain.".to_owned()
});
} }
} }
} }
@ -3635,35 +3650,6 @@ impl<Signer: Sign> Channel<Signer> {
Ok((None, timed_out_htlcs)) Ok((None, timed_out_htlcs))
} }
/// When we receive a new block, we (a) check whether the block contains the funding
/// transaction (which would start us counting blocks until we send the funding_signed), and
/// (b) check the height of the block against outbound holding cell HTLCs in case we need to
/// give up on them prematurely and time them out. Everything else (e.g. commitment
/// transaction broadcasts, channel closure detection, HTLC transaction broadcasting, etc) is
/// handled by the ChannelMonitor.
///
/// If we return Err, the channel may have been closed, at which point the standard
/// requirements apply - no calls may be made except those explicitly stated to be allowed
/// post-shutdown.
/// Only returns an ErrorAction of DisconnectPeer, if Err.
///
/// May return some HTLCs (and their payment_hash) which have timed out and should be failed
/// back.
pub fn block_connected(&mut self, header: &BlockHeader, txdata: &TransactionData, height: u32) -> Result<(Option<msgs::FundingLocked>, Vec<(HTLCSource, PaymentHash)>), msgs::ErrorMessage> {
self.transactions_confirmed(&header.block_hash(), height, txdata)?;
self.update_best_block(height, header.time)
}
/// Called by channelmanager based on chain blocks being disconnected.
/// Returns true if we need to close the channel now due to funding transaction
/// unconfirmation/reorg.
pub fn block_disconnected(&mut self, header: &BlockHeader, new_height: u32) -> bool {
if self.update_best_block(new_height, header.time).is_err() {
return true;
}
false
}
// Methods to get unprompted messages to send to the remote end (or where we already returned // 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): // something in the handler for the message that prompted this message):

View file

@ -3334,7 +3334,8 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
let short_to_id = &mut channel_state.short_to_id; let short_to_id = &mut channel_state.short_to_id;
let pending_msg_events = &mut channel_state.pending_msg_events; let pending_msg_events = &mut channel_state.pending_msg_events;
channel_state.by_id.retain(|_, channel| { channel_state.by_id.retain(|_, channel| {
let res = channel.block_connected(header, txdata, height); let res = channel.transactions_confirmed(&block_hash, height, txdata, &self.logger)
.and_then(|_| channel.update_best_block(height, header.time));
if let Ok((chan_res, mut timed_out_pending_htlcs)) = res { if let Ok((chan_res, mut timed_out_pending_htlcs)) = res {
for (source, payment_hash) in timed_out_pending_htlcs.drain(..) { 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 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
@ -3360,38 +3361,23 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
short_to_id.insert(channel.get_short_channel_id().unwrap(), channel.channel_id()); short_to_id.insert(channel.get_short_channel_id().unwrap(), channel.channel_id());
} }
} else if let Err(e) = res { } 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 { pending_msg_events.push(events::MessageSendEvent::HandleError {
node_id: channel.get_counterparty_node_id(), node_id: channel.get_counterparty_node_id(),
action: msgs::ErrorAction::SendErrorMessage { msg: e }, action: msgs::ErrorAction::SendErrorMessage { msg: e },
}); });
return false; 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
});
}
pending_msg_events.push(events::MessageSendEvent::HandleError {
node_id: channel.get_counterparty_node_id(),
action: msgs::ErrorAction::SendErrorMessage {
msg: msgs::ErrorMessage { channel_id: channel.channel_id(), data: "Commitment or closing transaction was confirmed on chain.".to_owned() }
},
});
return false;
}
}
}
}
true true
}); });
@ -3456,8 +3442,8 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
let channel_state = &mut *channel_lock; let channel_state = &mut *channel_lock;
let short_to_id = &mut channel_state.short_to_id; let short_to_id = &mut channel_state.short_to_id;
let pending_msg_events = &mut channel_state.pending_msg_events; let pending_msg_events = &mut channel_state.pending_msg_events;
channel_state.by_id.retain(|channel_id, v| { channel_state.by_id.retain(|_, v| {
if v.block_disconnected(header, new_height) { if let Err(err_msg) = v.update_best_block(new_height, header.time) {
if let Some(short_id) = v.get_short_channel_id() { if let Some(short_id) = v.get_short_channel_id() {
short_to_id.remove(&short_id); short_to_id.remove(&short_id);
} }
@ -3469,9 +3455,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
} }
pending_msg_events.push(events::MessageSendEvent::HandleError { pending_msg_events.push(events::MessageSendEvent::HandleError {
node_id: v.get_counterparty_node_id(), node_id: v.get_counterparty_node_id(),
action: msgs::ErrorAction::SendErrorMessage { action: msgs::ErrorAction::SendErrorMessage { msg: err_msg },
msg: msgs::ErrorMessage { channel_id: *channel_id, data: "Funding transaction was un-confirmed.".to_owned() }
},
}); });
false false
} else { } else {