/// spending CSV for revocable outputs).
htlcs_resolved_on_chain: Vec<IrrevocablyResolvedHTLC>,
+ /// The set of `SpendableOutput` events which we have already passed upstream to be claimed.
+ /// These are tracked explicitly to ensure that we don't generate the same events redundantly
+ /// if users duplicatively confirm old transactions. Specifically for transactions claiming a
+ /// revoked remote outpoint we otherwise have no tracking at all once they've reached
+ /// [`ANTI_REORG_DELAY`], so we have to track them here.
+ spendable_txids_confirmed: Vec<Txid>,
+
// We simply modify best_block in Channel's block_connected so that serialization is
// consistent but hopefully the users' copy handles block_connected in a consistent way.
// (we do *not*, however, update them in update_monitor to ensure any local user copies keep
(7, self.funding_spend_seen, required),
(9, self.counterparty_node_id, option),
(11, self.confirmed_commitment_tx_counterparty_output, option),
+ (13, self.spendable_txids_confirmed, vec_type),
});
Ok(())
funding_spend_confirmed: None,
confirmed_commitment_tx_counterparty_output: None,
htlcs_resolved_on_chain: Vec::new(),
+ spendable_txids_confirmed: Vec::new(),
best_block,
counterparty_node_id: Some(counterparty_node_id),
let mut watch_outputs = Vec::new();
let mut claimable_outpoints = Vec::new();
- for tx in &txn_matched {
+ 'tx_iter: for tx in &txn_matched {
+ let txid = tx.txid();
+ // If a transaction has already been confirmed, ensure we don't bother processing it duplicatively.
+ if Some(txid) == self.funding_spend_confirmed {
+ log_debug!(logger, "Skipping redundant processing of funding-spend tx {} as it was previously confirmed", txid);
+ continue 'tx_iter;
+ }
+ for ev in self.onchain_events_awaiting_threshold_conf.iter() {
+ if ev.txid == txid {
+ if let Some(conf_hash) = ev.block_hash {
+ assert_eq!(header.block_hash(), conf_hash,
+ "Transaction {} was already confirmed and is being re-confirmed in a different block.\n\
+ This indicates a severe bug in the transaction connection logic - a reorg should have been processed first!", ev.txid);
+ }
+ log_debug!(logger, "Skipping redundant processing of confirming tx {} as it was previously confirmed", txid);
+ continue 'tx_iter;
+ }
+ }
+ for htlc in self.htlcs_resolved_on_chain.iter() {
+ if Some(txid) == htlc.resolving_txid {
+ log_debug!(logger, "Skipping redundant processing of HTLC resolution tx {} as it was previously confirmed", txid);
+ continue 'tx_iter;
+ }
+ }
+ for spendable_txid in self.spendable_txids_confirmed.iter() {
+ if txid == *spendable_txid {
+ log_debug!(logger, "Skipping redundant processing of spendable tx {} as it was previously confirmed", txid);
+ continue 'tx_iter;
+ }
+ }
+
if tx.input.len() == 1 {
// Assuming our keys were not leaked (in which case we're screwed no matter what),
// commitment transactions and HTLC transactions will all only ever have one input,
if prevout.txid == self.funding_info.0.txid && prevout.vout == self.funding_info.0.index as u32 {
let mut balance_spendable_csv = None;
log_info!(logger, "Channel {} closed by funding output spend in txid {}.",
- log_bytes!(self.funding_info.0.to_channel_id()), tx.txid());
+ log_bytes!(self.funding_info.0.to_channel_id()), txid);
self.funding_spend_seen = true;
let mut commitment_tx_to_counterparty_output = None;
if (tx.input[0].sequence.0 >> 8*3) as u8 == 0x80 && (tx.lock_time.0 >> 8*3) as u8 == 0x20 {
}
}
}
- let txid = tx.txid();
self.onchain_events_awaiting_threshold_conf.push(OnchainEventEntry {
txid,
transaction: Some((*tx).clone()),
self.pending_events.push(Event::SpendableOutputs {
outputs: vec![descriptor]
});
+ self.spendable_txids_confirmed.push(entry.txid);
},
OnchainEvent::HTLCSpendConfirmation { commitment_tx_output_idx, preimage, .. } => {
self.htlcs_resolved_on_chain.push(IrrevocablyResolvedHTLC {
F::Target: FeeEstimator,
L::Target: Logger,
{
- self.onchain_events_awaiting_threshold_conf.retain(|ref entry| if entry.txid == *txid {
- log_info!(logger, "Removing onchain event with txid {}", txid);
- false
- } else { true });
+ let mut removed_height = None;
+ for entry in self.onchain_events_awaiting_threshold_conf.iter() {
+ if entry.txid == *txid {
+ removed_height = Some(entry.height);
+ break;
+ }
+ }
+
+ if let Some(removed_height) = removed_height {
+ log_info!(logger, "transaction_unconfirmed of txid {} implies height {} was reorg'd out", txid, removed_height);
+ self.onchain_events_awaiting_threshold_conf.retain(|ref entry| if entry.height >= removed_height {
+ log_info!(logger, "Transaction {} reorg'd out", entry.txid);
+ false
+ } else { true });
+ }
+
+ debug_assert!(!self.onchain_events_awaiting_threshold_conf.iter().any(|ref entry| entry.txid == *txid));
+
self.onchain_tx_handler.transaction_unconfirmed(txid, broadcaster, fee_estimator, logger);
}
let mut funding_spend_seen = Some(false);
let mut counterparty_node_id = None;
let mut confirmed_commitment_tx_counterparty_output = None;
+ let mut spendable_txids_confirmed = Some(Vec::new());
read_tlv_fields!(reader, {
(1, funding_spend_confirmed, option),
(3, htlcs_resolved_on_chain, vec_type),
(7, funding_spend_seen, option),
(9, counterparty_node_id, option),
(11, confirmed_commitment_tx_counterparty_output, option),
+ (13, spendable_txids_confirmed, vec_type),
});
let mut secp_ctx = Secp256k1::new();
funding_spend_confirmed,
confirmed_commitment_tx_counterparty_output,
htlcs_resolved_on_chain: htlcs_resolved_on_chain.unwrap(),
+ spendable_txids_confirmed: spendable_txids_confirmed.unwrap(),
best_block,
counterparty_node_id,
SecretKey::from_slice(&[41; 32]).unwrap(),
[41; 32],
0,
- [0; 32]
+ [0; 32],
);
let counterparty_pubkeys = ChannelPublicKeys {
}),
funding_outpoint: Some(funding_outpoint),
opt_anchors: None,
+ opt_non_zero_fee_anchors: None,
};
// Prune with one old state and a holder commitment tx holding a few overlaps with the
// old state.