Merge pull request #2403 from wpaulino/bump-transaction-event-handler-tests

Integrate BumpTransactionEventHandler into existing anchor tests
This commit is contained in:
Matt Corallo 2023-07-17 17:18:20 +00:00 committed by GitHub
commit ba8af2280d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 342 additions and 213 deletions

View file

@ -14,9 +14,18 @@
//! disconnections, transaction broadcasting, and feerate information requests. //! disconnections, transaction broadcasting, and feerate information requests.
use core::{cmp, ops::Deref}; use core::{cmp, ops::Deref};
use core::convert::TryInto;
use bitcoin::blockdata::transaction::Transaction; use bitcoin::blockdata::transaction::Transaction;
// TODO: Define typed abstraction over feerates to handle their conversions.
pub(crate) fn compute_feerate_sat_per_1000_weight(fee_sat: u64, weight: u64) -> u32 {
(fee_sat * 1000 / weight).try_into().unwrap_or(u32::max_value())
}
pub(crate) const fn fee_for_weight(feerate_sat_per_1000_weight: u32, weight: u64) -> u64 {
((feerate_sat_per_1000_weight as u64 * weight) + 1000 - 1) / 1000
}
/// An interface to send a transaction to the Bitcoin network. /// An interface to send a transaction to the Bitcoin network.
pub trait BroadcasterInterface { pub trait BroadcasterInterface {
/// Sends a list of transactions out to (hopefully) be mined. /// Sends a list of transactions out to (hopefully) be mined.

View file

@ -22,6 +22,7 @@ use bitcoin::hash_types::{Txid, BlockHash};
use bitcoin::secp256k1::{Secp256k1, ecdsa::Signature}; use bitcoin::secp256k1::{Secp256k1, ecdsa::Signature};
use bitcoin::secp256k1; use bitcoin::secp256k1;
use crate::chain::chaininterface::compute_feerate_sat_per_1000_weight;
use crate::sign::{ChannelSigner, EntropySource, SignerProvider}; use crate::sign::{ChannelSigner, EntropySource, SignerProvider};
use crate::ln::msgs::DecodeError; use crate::ln::msgs::DecodeError;
use crate::ln::PaymentPreimage; use crate::ln::PaymentPreimage;
@ -623,9 +624,24 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
return inputs.find_map(|input| match input { return inputs.find_map(|input| match input {
// Commitment inputs with anchors support are the only untractable inputs supported // Commitment inputs with anchors support are the only untractable inputs supported
// thus far that require external funding. // thus far that require external funding.
PackageSolvingData::HolderFundingOutput(..) => { PackageSolvingData::HolderFundingOutput(output) => {
debug_assert_eq!(tx.txid(), self.holder_commitment.trust().txid(), debug_assert_eq!(tx.txid(), self.holder_commitment.trust().txid(),
"Holder commitment transaction mismatch"); "Holder commitment transaction mismatch");
let conf_target = ConfirmationTarget::HighPriority;
let package_target_feerate_sat_per_1000_weight = cached_request
.compute_package_feerate(fee_estimator, conf_target, force_feerate_bump);
if let Some(input_amount_sat) = output.funding_amount {
let fee_sat = input_amount_sat - tx.output.iter().map(|output| output.value).sum::<u64>();
if compute_feerate_sat_per_1000_weight(fee_sat, tx.weight() as u64) >=
package_target_feerate_sat_per_1000_weight
{
log_debug!(logger, "Commitment transaction {} already meets required feerate {} sat/kW",
tx.txid(), package_target_feerate_sat_per_1000_weight);
return Some((new_timer, 0, OnchainClaim::Tx(tx.clone())));
}
}
// We'll locate an anchor output we can spend within the commitment transaction. // We'll locate an anchor output we can spend within the commitment transaction.
let funding_pubkey = &self.channel_transaction_parameters.holder_pubkeys.funding_pubkey; let funding_pubkey = &self.channel_transaction_parameters.holder_pubkeys.funding_pubkey;
match chan_utils::get_anchor_output(&tx, funding_pubkey) { match chan_utils::get_anchor_output(&tx, funding_pubkey) {
@ -633,9 +649,6 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
Some((idx, _)) => { Some((idx, _)) => {
// TODO: Use a lower confirmation target when both our and the // TODO: Use a lower confirmation target when both our and the
// counterparty's latest commitment don't have any HTLCs present. // counterparty's latest commitment don't have any HTLCs present.
let conf_target = ConfirmationTarget::HighPriority;
let package_target_feerate_sat_per_1000_weight = cached_request
.compute_package_feerate(fee_estimator, conf_target, force_feerate_bump);
Some(( Some((
new_timer, new_timer,
package_target_feerate_sat_per_1000_weight as u64, package_target_feerate_sat_per_1000_weight as u64,
@ -739,6 +752,9 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
) { ) {
req.set_timer(new_timer); req.set_timer(new_timer);
req.set_feerate(new_feerate); req.set_feerate(new_feerate);
// Once a pending claim has an id assigned, it remains fixed until the claim is
// satisfied, regardless of whether the claim switches between different variants of
// `OnchainClaim`.
let claim_id = match claim { let claim_id = match claim {
OnchainClaim::Tx(tx) => { OnchainClaim::Tx(tx) => {
log_info!(logger, "Broadcasting onchain {}", log_tx!(tx)); log_info!(logger, "Broadcasting onchain {}", log_tx!(tx));

View file

@ -429,7 +429,7 @@ impl Readable for HolderHTLCOutput {
#[derive(Clone, PartialEq, Eq)] #[derive(Clone, PartialEq, Eq)]
pub(crate) struct HolderFundingOutput { pub(crate) struct HolderFundingOutput {
funding_redeemscript: Script, funding_redeemscript: Script,
funding_amount: Option<u64>, pub(crate) funding_amount: Option<u64>,
channel_type_features: ChannelTypeFeatures, channel_type_features: ChannelTypeFeatures,
} }

View file

@ -12,10 +12,9 @@
//! [`Event`]: crate::events::Event //! [`Event`]: crate::events::Event
use alloc::collections::BTreeMap; use alloc::collections::BTreeMap;
use core::convert::TryInto;
use core::ops::Deref; use core::ops::Deref;
use crate::chain::chaininterface::BroadcasterInterface; use crate::chain::chaininterface::{BroadcasterInterface, compute_feerate_sat_per_1000_weight, fee_for_weight, FEERATE_FLOOR_SATS_PER_KW};
use crate::chain::ClaimId; use crate::chain::ClaimId;
use crate::io_extras::sink; use crate::io_extras::sink;
use crate::ln::channel::ANCHOR_OUTPUT_VALUE_SATOSHI; use crate::ln::channel::ANCHOR_OUTPUT_VALUE_SATOSHI;
@ -44,14 +43,6 @@ const BASE_INPUT_SIZE: u64 = 32 /* txid */ + 4 /* vout */ + 4 /* sequence */;
const BASE_INPUT_WEIGHT: u64 = BASE_INPUT_SIZE * WITNESS_SCALE_FACTOR as u64; const BASE_INPUT_WEIGHT: u64 = BASE_INPUT_SIZE * WITNESS_SCALE_FACTOR as u64;
// TODO: Define typed abstraction over feerates to handle their conversions.
fn compute_feerate_sat_per_1000_weight(fee_sat: u64, weight: u64) -> u32 {
(fee_sat * 1000 / weight).try_into().unwrap_or(u32::max_value())
}
const fn fee_for_weight(feerate_sat_per_1000_weight: u32, weight: u64) -> u64 {
((feerate_sat_per_1000_weight as u64 * weight) + 1000 - 1) / 1000
}
/// The parameters required to derive a channel signer via [`SignerProvider`]. /// The parameters required to derive a channel signer via [`SignerProvider`].
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub struct ChannelDerivationParameters { pub struct ChannelDerivationParameters {
@ -149,6 +140,15 @@ pub struct HTLCDescriptor {
} }
impl HTLCDescriptor { impl HTLCDescriptor {
/// Returns the outpoint of the HTLC output in the commitment transaction. This is the outpoint
/// being spent by the HTLC input in the HTLC transaction.
pub fn outpoint(&self) -> OutPoint {
OutPoint {
txid: self.commitment_txid,
vout: self.htlc.transaction_output_index.unwrap(),
}
}
/// Returns the UTXO to be spent by the HTLC input, which can be obtained via /// Returns the UTXO to be spent by the HTLC input, which can be obtained via
/// [`Self::unsigned_tx_input`]. /// [`Self::unsigned_tx_input`].
pub fn previous_utxo<C: secp256k1::Signing + secp256k1::Verification>(&self, secp: &Secp256k1<C>) -> TxOut { pub fn previous_utxo<C: secp256k1::Signing + secp256k1::Verification>(&self, secp: &Secp256k1<C>) -> TxOut {
@ -489,19 +489,28 @@ pub trait WalletSource {
/// A wrapper over [`WalletSource`] that implements [`CoinSelection`] by preferring UTXOs that would /// A wrapper over [`WalletSource`] that implements [`CoinSelection`] by preferring UTXOs that would
/// avoid conflicting double spends. If not enough UTXOs are available to do so, conflicting double /// avoid conflicting double spends. If not enough UTXOs are available to do so, conflicting double
/// spends may happen. /// spends may happen.
pub struct Wallet<W: Deref> where W::Target: WalletSource { pub struct Wallet<W: Deref, L: Deref>
where
W::Target: WalletSource,
L::Target: Logger
{
source: W, source: W,
logger: L,
// TODO: Do we care about cleaning this up once the UTXOs have a confirmed spend? We can do so // TODO: Do we care about cleaning this up once the UTXOs have a confirmed spend? We can do so
// by checking whether any UTXOs that exist in the map are no longer returned in // by checking whether any UTXOs that exist in the map are no longer returned in
// `list_confirmed_utxos`. // `list_confirmed_utxos`.
locked_utxos: Mutex<HashMap<OutPoint, ClaimId>>, locked_utxos: Mutex<HashMap<OutPoint, ClaimId>>,
} }
impl<W: Deref> Wallet<W> where W::Target: WalletSource { impl<W: Deref, L: Deref> Wallet<W, L>
where
W::Target: WalletSource,
L::Target: Logger
{
/// Returns a new instance backed by the given [`WalletSource`] that serves as an implementation /// Returns a new instance backed by the given [`WalletSource`] that serves as an implementation
/// of [`CoinSelectionSource`]. /// of [`CoinSelectionSource`].
pub fn new(source: W) -> Self { pub fn new(source: W, logger: L) -> Self {
Self { source, locked_utxos: Mutex::new(HashMap::new()) } Self { source, logger, locked_utxos: Mutex::new(HashMap::new()) }
} }
/// Performs coin selection on the set of UTXOs obtained from /// Performs coin selection on the set of UTXOs obtained from
@ -521,6 +530,7 @@ impl<W: Deref> Wallet<W> where W::Target: WalletSource {
let mut eligible_utxos = utxos.iter().filter_map(|utxo| { let mut eligible_utxos = utxos.iter().filter_map(|utxo| {
if let Some(utxo_claim_id) = locked_utxos.get(&utxo.outpoint) { if let Some(utxo_claim_id) = locked_utxos.get(&utxo.outpoint) {
if *utxo_claim_id != claim_id && !force_conflicting_utxo_spend { if *utxo_claim_id != claim_id && !force_conflicting_utxo_spend {
log_trace!(self.logger, "Skipping UTXO {} to prevent conflicting spend", utxo.outpoint);
return None; return None;
} }
} }
@ -535,6 +545,7 @@ impl<W: Deref> Wallet<W> where W::Target: WalletSource {
if should_spend { if should_spend {
Some((utxo, fee_to_spend_utxo)) Some((utxo, fee_to_spend_utxo))
} else { } else {
log_trace!(self.logger, "Skipping UTXO {} due to dust proximity after spend", utxo.outpoint);
None None
} }
}).collect::<Vec<_>>(); }).collect::<Vec<_>>();
@ -552,6 +563,8 @@ impl<W: Deref> Wallet<W> where W::Target: WalletSource {
selected_utxos.push(utxo.clone()); selected_utxos.push(utxo.clone());
} }
if selected_amount < target_amount_sat + total_fees { if selected_amount < target_amount_sat + total_fees {
log_debug!(self.logger, "Insufficient funds to meet target feerate {} sat/kW",
target_feerate_sat_per_1000_weight);
return Err(()); return Err(());
} }
for utxo in &selected_utxos { for utxo in &selected_utxos {
@ -568,6 +581,7 @@ impl<W: Deref> Wallet<W> where W::Target: WalletSource {
); );
let change_output_amount = remaining_amount.saturating_sub(change_output_fee); let change_output_amount = remaining_amount.saturating_sub(change_output_fee);
let change_output = if change_output_amount < change_script.dust_value().to_sat() { let change_output = if change_output_amount < change_script.dust_value().to_sat() {
log_debug!(self.logger, "Coin selection attempt did not yield change output");
None None
} else { } else {
Some(TxOut { script_pubkey: change_script, value: change_output_amount }) Some(TxOut { script_pubkey: change_script, value: change_output_amount })
@ -580,7 +594,11 @@ impl<W: Deref> Wallet<W> where W::Target: WalletSource {
} }
} }
impl<W: Deref> CoinSelectionSource for Wallet<W> where W::Target: WalletSource { impl<W: Deref, L: Deref> CoinSelectionSource for Wallet<W, L>
where
W::Target: WalletSource,
L::Target: Logger
{
fn select_confirmed_utxos( fn select_confirmed_utxos(
&self, claim_id: ClaimId, must_spend: &[Input], must_pay_to: &[TxOut], &self, claim_id: ClaimId, must_spend: &[Input], must_pay_to: &[TxOut],
target_feerate_sat_per_1000_weight: u32, target_feerate_sat_per_1000_weight: u32,
@ -598,6 +616,8 @@ impl<W: Deref> CoinSelectionSource for Wallet<W> where W::Target: WalletSource {
((BASE_TX_SIZE + total_output_size) * WITNESS_SCALE_FACTOR as u64); ((BASE_TX_SIZE + total_output_size) * WITNESS_SCALE_FACTOR as u64);
let target_amount_sat = must_pay_to.iter().map(|output| output.value).sum(); let target_amount_sat = must_pay_to.iter().map(|output| output.value).sum();
let do_coin_selection = |force_conflicting_utxo_spend: bool, tolerate_high_network_feerates: bool| { let do_coin_selection = |force_conflicting_utxo_spend: bool, tolerate_high_network_feerates: bool| {
log_debug!(self.logger, "Attempting coin selection targeting {} sat/kW (force_conflicting_utxo_spend = {}, tolerate_high_network_feerates = {})",
target_feerate_sat_per_1000_weight, force_conflicting_utxo_spend, tolerate_high_network_feerates);
self.select_confirmed_utxos_internal( self.select_confirmed_utxos_internal(
&utxos, claim_id, force_conflicting_utxo_spend, tolerate_high_network_feerates, &utxos, claim_id, force_conflicting_utxo_spend, tolerate_high_network_feerates,
target_feerate_sat_per_1000_weight, preexisting_tx_weight, target_amount_sat, target_feerate_sat_per_1000_weight, preexisting_tx_weight, target_amount_sat,
@ -670,6 +690,7 @@ where
// match, but we still need to have at least one output in the transaction for it to be // match, but we still need to have at least one output in the transaction for it to be
// considered standard. We choose to go with an empty OP_RETURN as it is the cheapest // considered standard. We choose to go with an empty OP_RETURN as it is the cheapest
// way to include a dummy output. // way to include a dummy output.
log_debug!(self.logger, "Including dummy OP_RETURN output since an output is needed and a change output was not provided");
tx.output.push(TxOut { tx.output.push(TxOut {
value: 0, value: 0,
script_pubkey: Script::new_op_return(&[]), script_pubkey: Script::new_op_return(&[]),
@ -677,31 +698,6 @@ where
} }
} }
/// Returns an unsigned transaction spending an anchor output of the commitment transaction, and
/// any additional UTXOs sourced, to bump the commitment transaction's fee.
fn build_anchor_tx(
&self, claim_id: ClaimId, target_feerate_sat_per_1000_weight: u32,
commitment_tx: &Transaction, anchor_descriptor: &AnchorDescriptor,
) -> Result<Transaction, ()> {
let must_spend = vec![Input {
outpoint: anchor_descriptor.outpoint,
previous_utxo: anchor_descriptor.previous_utxo(),
satisfaction_weight: commitment_tx.weight() as u64 + ANCHOR_INPUT_WITNESS_WEIGHT + EMPTY_SCRIPT_SIG_WEIGHT,
}];
let coin_selection = self.utxo_source.select_confirmed_utxos(
claim_id, &must_spend, &[], target_feerate_sat_per_1000_weight,
)?;
let mut tx = Transaction {
version: 2,
lock_time: PackedLockTime::ZERO, // TODO: Use next best height.
input: vec![anchor_descriptor.unsigned_tx_input()],
output: vec![],
};
self.process_coin_selection(&mut tx, coin_selection);
Ok(tx)
}
/// Handles a [`BumpTransactionEvent::ChannelClose`] event variant by producing a fully-signed /// Handles a [`BumpTransactionEvent::ChannelClose`] event variant by producing a fully-signed
/// transaction spending an anchor output of the commitment transaction to bump its fee and /// transaction spending an anchor output of the commitment transaction to bump its fee and
/// broadcasts them to the network as a package. /// broadcasts them to the network as a package.
@ -709,39 +705,76 @@ where
&self, claim_id: ClaimId, package_target_feerate_sat_per_1000_weight: u32, &self, claim_id: ClaimId, package_target_feerate_sat_per_1000_weight: u32,
commitment_tx: &Transaction, commitment_tx_fee_sat: u64, anchor_descriptor: &AnchorDescriptor, commitment_tx: &Transaction, commitment_tx_fee_sat: u64, anchor_descriptor: &AnchorDescriptor,
) -> Result<(), ()> { ) -> Result<(), ()> {
// Compute the feerate the anchor transaction must meet to meet the overall feerate for the // Our commitment transaction already has fees allocated to it, so we should take them into
// package (commitment + anchor transactions). // account. We compute its feerate and subtract it from the package target, using the result
// as the target feerate for our anchor transaction. Unfortunately, this results in users
// overpaying by a small margin since we don't yet know the anchor transaction size, and
// avoiding the small overpayment only makes our API even more complex.
let commitment_tx_sat_per_1000_weight: u32 = compute_feerate_sat_per_1000_weight( let commitment_tx_sat_per_1000_weight: u32 = compute_feerate_sat_per_1000_weight(
commitment_tx_fee_sat, commitment_tx.weight() as u64, commitment_tx_fee_sat, commitment_tx.weight() as u64,
); );
if commitment_tx_sat_per_1000_weight >= package_target_feerate_sat_per_1000_weight { let anchor_target_feerate_sat_per_1000_weight = core::cmp::max(
// If the commitment transaction already has a feerate high enough on its own, broadcast package_target_feerate_sat_per_1000_weight - commitment_tx_sat_per_1000_weight,
// it as is without a child. FEERATE_FLOOR_SATS_PER_KW,
self.broadcaster.broadcast_transactions(&[&commitment_tx]); );
return Ok(());
}
let mut anchor_tx = self.build_anchor_tx( log_debug!(self.logger, "Peforming coin selection for anchor transaction targeting {} sat/kW",
claim_id, package_target_feerate_sat_per_1000_weight, commitment_tx, anchor_descriptor, anchor_target_feerate_sat_per_1000_weight);
let must_spend = vec![Input {
outpoint: anchor_descriptor.outpoint,
previous_utxo: anchor_descriptor.previous_utxo(),
satisfaction_weight: commitment_tx.weight() as u64 + ANCHOR_INPUT_WITNESS_WEIGHT + EMPTY_SCRIPT_SIG_WEIGHT,
}];
let coin_selection = self.utxo_source.select_confirmed_utxos(
claim_id, &must_spend, &[], anchor_target_feerate_sat_per_1000_weight,
)?; )?;
debug_assert_eq!(anchor_tx.output.len(), 1);
let mut anchor_tx = Transaction {
version: 2,
lock_time: PackedLockTime::ZERO, // TODO: Use next best height.
input: vec![anchor_descriptor.unsigned_tx_input()],
output: vec![],
};
#[cfg(debug_assertions)]
let total_satisfaction_weight =
coin_selection.confirmed_utxos.iter().map(|utxo| utxo.satisfaction_weight).sum::<u64>() +
ANCHOR_INPUT_WITNESS_WEIGHT + EMPTY_SCRIPT_SIG_WEIGHT;
self.process_coin_selection(&mut anchor_tx, coin_selection);
let anchor_txid = anchor_tx.txid();
debug_assert_eq!(anchor_tx.output.len(), 1);
#[cfg(debug_assertions)]
let unsigned_tx_weight = anchor_tx.weight() as u64 - (anchor_tx.input.len() as u64 * EMPTY_SCRIPT_SIG_WEIGHT);
log_debug!(self.logger, "Signing anchor transaction {}", anchor_txid);
self.utxo_source.sign_tx(&mut anchor_tx)?; self.utxo_source.sign_tx(&mut anchor_tx)?;
let signer = anchor_descriptor.derive_channel_signer(&self.signer_provider); let signer = anchor_descriptor.derive_channel_signer(&self.signer_provider);
let anchor_sig = signer.sign_holder_anchor_input(&anchor_tx, 0, &self.secp)?; let anchor_sig = signer.sign_holder_anchor_input(&anchor_tx, 0, &self.secp)?;
anchor_tx.input[0].witness = anchor_descriptor.tx_input_witness(&anchor_sig); anchor_tx.input[0].witness = anchor_descriptor.tx_input_witness(&anchor_sig);
#[cfg(debug_assertions)] {
let signed_tx_weight = anchor_tx.weight() as u64;
let expected_signed_tx_weight = unsigned_tx_weight + total_satisfaction_weight;
// Our estimate should be within a 1% error margin of the actual weight and we should
// never underestimate.
assert!(expected_signed_tx_weight >= signed_tx_weight &&
expected_signed_tx_weight - (expected_signed_tx_weight / 100) <= signed_tx_weight);
}
log_info!(self.logger, "Broadcasting anchor transaction {} to bump channel close with txid {}",
anchor_txid, commitment_tx.txid());
self.broadcaster.broadcast_transactions(&[&commitment_tx, &anchor_tx]); self.broadcaster.broadcast_transactions(&[&commitment_tx, &anchor_tx]);
Ok(()) Ok(())
} }
/// Returns an unsigned, fee-bumped HTLC transaction, along with the set of signers required to /// Handles a [`BumpTransactionEvent::HTLCResolution`] event variant by producing a
/// fulfill the witness for each HTLC input within it. /// fully-signed, fee-bumped HTLC transaction that is broadcast to the network.
fn build_htlc_tx( fn handle_htlc_resolution(
&self, claim_id: ClaimId, target_feerate_sat_per_1000_weight: u32, &self, claim_id: ClaimId, target_feerate_sat_per_1000_weight: u32,
htlc_descriptors: &[HTLCDescriptor], tx_lock_time: PackedLockTime, htlc_descriptors: &[HTLCDescriptor], tx_lock_time: PackedLockTime,
) -> Result<Transaction, ()> { ) -> Result<(), ()> {
let mut tx = Transaction { let mut htlc_tx = Transaction {
version: 2, version: 2,
lock_time: tx_lock_time, lock_time: tx_lock_time,
input: vec![], input: vec![],
@ -759,28 +792,26 @@ where
HTLC_TIMEOUT_INPUT_ANCHOR_WITNESS_WEIGHT HTLC_TIMEOUT_INPUT_ANCHOR_WITNESS_WEIGHT
}, },
}); });
tx.input.push(htlc_input); htlc_tx.input.push(htlc_input);
let htlc_output = htlc_descriptor.tx_output(&self.secp); let htlc_output = htlc_descriptor.tx_output(&self.secp);
tx.output.push(htlc_output); htlc_tx.output.push(htlc_output);
} }
log_debug!(self.logger, "Peforming coin selection for HTLC transaction targeting {} sat/kW",
target_feerate_sat_per_1000_weight);
let coin_selection = self.utxo_source.select_confirmed_utxos( let coin_selection = self.utxo_source.select_confirmed_utxos(
claim_id, &must_spend, &tx.output, target_feerate_sat_per_1000_weight, claim_id, &must_spend, &htlc_tx.output, target_feerate_sat_per_1000_weight,
)?; )?;
self.process_coin_selection(&mut tx, coin_selection); #[cfg(debug_assertions)]
Ok(tx) let total_satisfaction_weight =
} coin_selection.confirmed_utxos.iter().map(|utxo| utxo.satisfaction_weight).sum::<u64>() +
must_spend.iter().map(|input| input.satisfaction_weight).sum::<u64>();
self.process_coin_selection(&mut htlc_tx, coin_selection);
/// Handles a [`BumpTransactionEvent::HTLCResolution`] event variant by producing a #[cfg(debug_assertions)]
/// fully-signed, fee-bumped HTLC transaction that is broadcast to the network. let unsigned_tx_weight = htlc_tx.weight() as u64 - (htlc_tx.input.len() as u64 * EMPTY_SCRIPT_SIG_WEIGHT);
fn handle_htlc_resolution(
&self, claim_id: ClaimId, target_feerate_sat_per_1000_weight: u32,
htlc_descriptors: &[HTLCDescriptor], tx_lock_time: PackedLockTime,
) -> Result<(), ()> {
let mut htlc_tx = self.build_htlc_tx(
claim_id, target_feerate_sat_per_1000_weight, htlc_descriptors, tx_lock_time,
)?;
log_debug!(self.logger, "Signing HTLC transaction {}", htlc_tx.txid());
self.utxo_source.sign_tx(&mut htlc_tx)?; self.utxo_source.sign_tx(&mut htlc_tx)?;
let mut signers = BTreeMap::new(); let mut signers = BTreeMap::new();
for (idx, htlc_descriptor) in htlc_descriptors.iter().enumerate() { for (idx, htlc_descriptor) in htlc_descriptors.iter().enumerate() {
@ -791,6 +822,16 @@ where
htlc_tx.input[idx].witness = htlc_descriptor.tx_input_witness(&htlc_sig, &witness_script); htlc_tx.input[idx].witness = htlc_descriptor.tx_input_witness(&htlc_sig, &witness_script);
} }
#[cfg(debug_assertions)] {
let signed_tx_weight = htlc_tx.weight() as u64;
let expected_signed_tx_weight = unsigned_tx_weight + total_satisfaction_weight;
// Our estimate should be within a 1% error margin of the actual weight and we should
// never underestimate.
assert!(expected_signed_tx_weight >= signed_tx_weight &&
expected_signed_tx_weight - (expected_signed_tx_weight / 100) <= signed_tx_weight);
}
log_info!(self.logger, "Broadcasting {}", log_tx!(htlc_tx));
self.broadcaster.broadcast_transactions(&[&htlc_tx]); self.broadcaster.broadcast_transactions(&[&htlc_tx]);
Ok(()) Ok(())
} }
@ -800,8 +841,10 @@ where
match event { match event {
BumpTransactionEvent::ChannelClose { BumpTransactionEvent::ChannelClose {
claim_id, package_target_feerate_sat_per_1000_weight, commitment_tx, claim_id, package_target_feerate_sat_per_1000_weight, commitment_tx,
anchor_descriptor, commitment_tx_fee_satoshis, .. commitment_tx_fee_satoshis, anchor_descriptor, ..
} => { } => {
log_info!(self.logger, "Handling channel close bump (claim_id = {}, commitment_txid = {})",
log_bytes!(claim_id.0), commitment_tx.txid());
if let Err(_) = self.handle_channel_close( if let Err(_) = self.handle_channel_close(
*claim_id, *package_target_feerate_sat_per_1000_weight, commitment_tx, *claim_id, *package_target_feerate_sat_per_1000_weight, commitment_tx,
*commitment_tx_fee_satoshis, anchor_descriptor, *commitment_tx_fee_satoshis, anchor_descriptor,
@ -813,6 +856,8 @@ where
BumpTransactionEvent::HTLCResolution { BumpTransactionEvent::HTLCResolution {
claim_id, target_feerate_sat_per_1000_weight, htlc_descriptors, tx_lock_time, claim_id, target_feerate_sat_per_1000_weight, htlc_descriptors, tx_lock_time,
} => { } => {
log_info!(self.logger, "Handling HTLC bump (claim_id = {}, htlcs_to_claim = {})",
log_bytes!(claim_id.0), log_iter!(htlc_descriptors.iter().map(|d| d.outpoint())));
if let Err(_) = self.handle_htlc_resolution( if let Err(_) = self.handle_htlc_resolution(
*claim_id, *target_feerate_sat_per_1000_weight, htlc_descriptors, *tx_lock_time, *claim_id, *target_feerate_sat_per_1000_weight, htlc_descriptors, *tx_lock_time,
) { ) {

View file

@ -15,6 +15,7 @@ use crate::sign::EntropySource;
use crate::chain::channelmonitor::ChannelMonitor; use crate::chain::channelmonitor::ChannelMonitor;
use crate::chain::transaction::OutPoint; use crate::chain::transaction::OutPoint;
use crate::events::{ClosureReason, Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider, PathFailure, PaymentPurpose, PaymentFailureReason}; use crate::events::{ClosureReason, Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider, PathFailure, PaymentPurpose, PaymentFailureReason};
use crate::events::bump_transaction::{BumpTransactionEventHandler, Wallet, WalletSource};
use crate::ln::{PaymentPreimage, PaymentHash, PaymentSecret}; use crate::ln::{PaymentPreimage, PaymentHash, PaymentSecret};
use crate::ln::channelmanager::{AChannelManager, ChainParameters, ChannelManager, ChannelManagerReadArgs, RAACommitmentOrder, PaymentSendFailure, RecipientOnionFields, PaymentId, MIN_CLTV_EXPIRY_DELTA}; use crate::ln::channelmanager::{AChannelManager, ChainParameters, ChannelManager, ChannelManagerReadArgs, RAACommitmentOrder, PaymentSendFailure, RecipientOnionFields, PaymentId, MIN_CLTV_EXPIRY_DELTA};
use crate::routing::gossip::{P2PGossipSync, NetworkGraph, NetworkUpdate}; use crate::routing::gossip::{P2PGossipSync, NetworkGraph, NetworkUpdate};
@ -32,13 +33,11 @@ use crate::util::ser::{ReadableArgs, Writeable};
use bitcoin::blockdata::block::{Block, BlockHeader}; use bitcoin::blockdata::block::{Block, BlockHeader};
use bitcoin::blockdata::transaction::{Transaction, TxOut}; use bitcoin::blockdata::transaction::{Transaction, TxOut};
use bitcoin::network::constants::Network;
use bitcoin::hash_types::BlockHash; use bitcoin::hash_types::BlockHash;
use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::hashes::sha256::Hash as Sha256;
use bitcoin::hashes::Hash as _; use bitcoin::hashes::Hash as _;
use bitcoin::network::constants::Network;
use bitcoin::secp256k1::PublicKey; use bitcoin::secp256k1::{PublicKey, SecretKey};
use crate::io; use crate::io;
use crate::prelude::*; use crate::prelude::*;
@ -289,6 +288,19 @@ fn do_connect_block<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, block: Block, sk
} }
call_claimable_balances(node); call_claimable_balances(node);
node.node.test_process_background_events(); node.node.test_process_background_events();
for tx in &block.txdata {
for input in &tx.input {
node.wallet_source.remove_utxo(input.previous_output);
}
let wallet_script = node.wallet_source.get_change_script().unwrap();
for (idx, output) in tx.output.iter().enumerate() {
if output.script_pubkey == wallet_script {
let outpoint = bitcoin::OutPoint { txid: tx.txid(), vout: idx as u32 };
node.wallet_source.add_utxo(outpoint, output.value);
}
}
}
} }
pub fn disconnect_blocks<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, count: u32) { pub fn disconnect_blocks<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, count: u32) {
@ -375,6 +387,13 @@ pub struct Node<'a, 'b: 'a, 'c: 'b> {
pub blocks: Arc<Mutex<Vec<(Block, u32)>>>, pub blocks: Arc<Mutex<Vec<(Block, u32)>>>,
pub connect_style: Rc<RefCell<ConnectStyle>>, pub connect_style: Rc<RefCell<ConnectStyle>>,
pub override_init_features: Rc<RefCell<Option<InitFeatures>>>, pub override_init_features: Rc<RefCell<Option<InitFeatures>>>,
pub wallet_source: Arc<test_utils::TestWalletSource>,
pub bump_tx_handler: BumpTransactionEventHandler<
&'c test_utils::TestBroadcaster,
Arc<Wallet<Arc<test_utils::TestWalletSource>, &'c test_utils::TestLogger>>,
&'b test_utils::TestKeysInterface,
&'c test_utils::TestLogger,
>,
} }
impl<'a, 'b, 'c> Node<'a, 'b, 'c> { impl<'a, 'b, 'c> Node<'a, 'b, 'c> {
pub fn best_block_hash(&self) -> BlockHash { pub fn best_block_hash(&self) -> BlockHash {
@ -2622,6 +2641,7 @@ pub fn create_network<'a, 'b: 'a, 'c: 'b>(node_count: usize, cfgs: &'b Vec<NodeC
for i in 0..node_count { for i in 0..node_count {
let gossip_sync = P2PGossipSync::new(cfgs[i].network_graph.as_ref(), None, cfgs[i].logger); let gossip_sync = P2PGossipSync::new(cfgs[i].network_graph.as_ref(), None, cfgs[i].logger);
let wallet_source = Arc::new(test_utils::TestWalletSource::new(SecretKey::from_slice(&[i as u8 + 1; 32]).unwrap()));
nodes.push(Node{ nodes.push(Node{
chain_source: cfgs[i].chain_source, tx_broadcaster: cfgs[i].tx_broadcaster, chain_source: cfgs[i].chain_source, tx_broadcaster: cfgs[i].tx_broadcaster,
fee_estimator: cfgs[i].fee_estimator, router: &cfgs[i].router, fee_estimator: cfgs[i].fee_estimator, router: &cfgs[i].router,
@ -2632,6 +2652,11 @@ pub fn create_network<'a, 'b: 'a, 'c: 'b>(node_count: usize, cfgs: &'b Vec<NodeC
blocks: Arc::clone(&cfgs[i].tx_broadcaster.blocks), blocks: Arc::clone(&cfgs[i].tx_broadcaster.blocks),
connect_style: Rc::clone(&connect_style), connect_style: Rc::clone(&connect_style),
override_init_features: Rc::clone(&cfgs[i].override_init_features), override_init_features: Rc::clone(&cfgs[i].override_init_features),
wallet_source: Arc::clone(&wallet_source),
bump_tx_handler: BumpTransactionEventHandler::new(
cfgs[i].tx_broadcaster, Arc::new(Wallet::new(Arc::clone(&wallet_source), cfgs[i].logger)),
&cfgs[i].keys_manager, cfgs[i].logger,
),
}) })
} }

View file

@ -9,14 +9,13 @@
//! Further functional tests which test blockchain reorganizations. //! Further functional tests which test blockchain reorganizations.
use crate::sign::{ChannelSigner, EcdsaChannelSigner}; use crate::sign::EcdsaChannelSigner;
use crate::chain::channelmonitor::{ANTI_REORG_DELAY, LATENCY_GRACE_PERIOD_BLOCKS, Balance}; use crate::chain::channelmonitor::{ANTI_REORG_DELAY, LATENCY_GRACE_PERIOD_BLOCKS, Balance};
use crate::chain::transaction::OutPoint; use crate::chain::transaction::OutPoint;
use crate::chain::chaininterface::LowerBoundedFeeEstimator; use crate::chain::chaininterface::{LowerBoundedFeeEstimator, compute_feerate_sat_per_1000_weight};
use crate::events::bump_transaction::BumpTransactionEvent; use crate::events::bump_transaction::{BumpTransactionEvent, WalletSource};
use crate::events::{Event, MessageSendEvent, MessageSendEventsProvider, ClosureReason, HTLCDestination}; use crate::events::{Event, MessageSendEvent, MessageSendEventsProvider, ClosureReason, HTLCDestination};
use crate::ln::channel; use crate::ln::channel;
use crate::ln::chan_utils;
use crate::ln::channelmanager::{BREAKDOWN_TIMEOUT, ChannelManager, PaymentId, RecipientOnionFields}; use crate::ln::channelmanager::{BREAKDOWN_TIMEOUT, ChannelManager, PaymentId, RecipientOnionFields};
use crate::ln::msgs::ChannelMessageHandler; use crate::ln::msgs::ChannelMessageHandler;
use crate::util::config::UserConfig; use crate::util::config::UserConfig;
@ -1743,6 +1742,17 @@ fn do_test_monitor_rebroadcast_pending_claims(anchors: bool) {
check_closed_event(&nodes[0], 1, ClosureReason::CommitmentTxConfirmed, false); check_closed_event(&nodes[0], 1, ClosureReason::CommitmentTxConfirmed, false);
check_added_monitors(&nodes[0], 1); check_added_monitors(&nodes[0], 1);
let coinbase_tx = Transaction {
version: 2,
lock_time: PackedLockTime::ZERO,
input: vec![TxIn { ..Default::default() }],
output: vec![TxOut { // UTXO to attach fees to `htlc_tx` on anchors
value: Amount::ONE_BTC.to_sat(),
script_pubkey: nodes[0].wallet_source.get_change_script().unwrap(),
}],
};
nodes[0].wallet_source.add_utxo(bitcoin::OutPoint { txid: coinbase_tx.txid(), vout: 0 }, coinbase_tx.output[0].value);
// Set up a helper closure we'll use throughout our test. We should only expect retries without // Set up a helper closure we'll use throughout our test. We should only expect retries without
// bumps if fees have not increased after a block has been connected (assuming the height timer // bumps if fees have not increased after a block has been connected (assuming the height timer
// re-evaluates at every block) or after `ChainMonitor::rebroadcast_pending_claims` is called. // re-evaluates at every block) or after `ChainMonitor::rebroadcast_pending_claims` is called.
@ -1750,42 +1760,25 @@ fn do_test_monitor_rebroadcast_pending_claims(anchors: bool) {
let mut check_htlc_retry = |should_retry: bool, should_bump: bool| -> Option<Transaction> { let mut check_htlc_retry = |should_retry: bool, should_bump: bool| -> Option<Transaction> {
let (htlc_tx, htlc_tx_feerate) = if anchors { let (htlc_tx, htlc_tx_feerate) = if anchors {
assert!(nodes[0].tx_broadcaster.txn_broadcast().is_empty()); assert!(nodes[0].tx_broadcaster.txn_broadcast().is_empty());
let mut events = nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events(); let events = nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events();
assert_eq!(events.len(), if should_retry { 1 } else { 0 }); assert_eq!(events.len(), if should_retry { 1 } else { 0 });
if !should_retry { if !should_retry {
return None; return None;
} }
#[allow(unused_assignments)] match &events[0] {
let mut tx = Transaction { Event::BumpTransaction(event) => {
version: 2, nodes[0].bump_tx_handler.handle_event(&event);
lock_time: bitcoin::PackedLockTime::ZERO, let mut txn = nodes[0].tx_broadcaster.unique_txn_broadcast();
input: vec![], assert_eq!(txn.len(), 1);
output: vec![], let htlc_tx = txn.pop().unwrap();
}; check_spends!(&htlc_tx, &commitment_txn[0], &coinbase_tx);
#[allow(unused_assignments)] let htlc_tx_fee = HTLC_AMT_SAT + coinbase_tx.output[0].value -
let mut feerate = 0; htlc_tx.output.iter().map(|output| output.value).sum::<u64>();
feerate = if let Event::BumpTransaction(BumpTransactionEvent::HTLCResolution { let htlc_tx_weight = htlc_tx.weight() as u64;
target_feerate_sat_per_1000_weight, mut htlc_descriptors, tx_lock_time, .. (htlc_tx, compute_feerate_sat_per_1000_weight(htlc_tx_fee, htlc_tx_weight))
}) = events.pop().unwrap() { }
let secp = Secp256k1::new(); _ => panic!("Unexpected event"),
assert_eq!(htlc_descriptors.len(), 1); }
let descriptor = htlc_descriptors.pop().unwrap();
assert_eq!(descriptor.commitment_txid, commitment_txn[0].txid());
let htlc_output_idx = descriptor.htlc.transaction_output_index.unwrap() as usize;
assert!(htlc_output_idx < commitment_txn[0].output.len());
tx.lock_time = tx_lock_time;
// Note that we don't care about actually making the HTLC transaction meet the
// feerate for the test, we just want to make sure the feerates we receive from
// the events never decrease.
tx.input.push(descriptor.unsigned_tx_input());
tx.output.push(descriptor.tx_output(&secp));
let signer = descriptor.derive_channel_signer(&nodes[0].keys_manager);
let our_sig = signer.sign_holder_htlc_transaction(&mut tx, 0, &descriptor, &secp).unwrap();
let witness_script = descriptor.witness_script(&secp);
tx.input[0].witness = descriptor.tx_input_witness(&our_sig, &witness_script);
target_feerate_sat_per_1000_weight as u64
} else { panic!("unexpected event"); };
(tx, feerate)
} else { } else {
assert!(nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events().is_empty()); assert!(nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events().is_empty());
let mut txn = nodes[0].tx_broadcaster.txn_broadcast(); let mut txn = nodes[0].tx_broadcaster.txn_broadcast();
@ -1796,8 +1789,8 @@ fn do_test_monitor_rebroadcast_pending_claims(anchors: bool) {
let htlc_tx = txn.pop().unwrap(); let htlc_tx = txn.pop().unwrap();
check_spends!(htlc_tx, commitment_txn[0]); check_spends!(htlc_tx, commitment_txn[0]);
let htlc_tx_fee = HTLC_AMT_SAT - htlc_tx.output[0].value; let htlc_tx_fee = HTLC_AMT_SAT - htlc_tx.output[0].value;
let htlc_tx_feerate = htlc_tx_fee * 1000 / htlc_tx.weight() as u64; let htlc_tx_weight = htlc_tx.weight() as u64;
(htlc_tx, htlc_tx_feerate) (htlc_tx, compute_feerate_sat_per_1000_weight(htlc_tx_fee, htlc_tx_weight))
}; };
if should_bump { if should_bump {
assert!(htlc_tx_feerate > prev_htlc_tx_feerate.take().unwrap()); assert!(htlc_tx_feerate > prev_htlc_tx_feerate.take().unwrap());
@ -1837,9 +1830,11 @@ fn do_test_monitor_rebroadcast_pending_claims(anchors: bool) {
// Mine the HTLC transaction to ensure we don't retry claims while they're confirmed. // Mine the HTLC transaction to ensure we don't retry claims while they're confirmed.
mine_transaction(&nodes[0], &htlc_tx); mine_transaction(&nodes[0], &htlc_tx);
// If we have a `ConnectStyle` that advertises the new block first without the transasctions, // If we have a `ConnectStyle` that advertises the new block first without the transactions,
// we'll receive an extra bumped claim. // we'll receive an extra bumped claim.
if nodes[0].connect_style.borrow().updates_best_block_first() { if nodes[0].connect_style.borrow().updates_best_block_first() {
nodes[0].wallet_source.add_utxo(bitcoin::OutPoint { txid: coinbase_tx.txid(), vout: 0 }, coinbase_tx.output[0].value);
nodes[0].wallet_source.remove_utxo(bitcoin::OutPoint { txid: htlc_tx.txid(), vout: 1 });
check_htlc_retry(true, anchors); check_htlc_retry(true, anchors);
} }
nodes[0].chain_monitor.chain_monitor.rebroadcast_pending_claims(); nodes[0].chain_monitor.chain_monitor.rebroadcast_pending_claims();
@ -1860,7 +1855,6 @@ fn test_yield_anchors_events() {
// allowing the consumer to provide additional fees to the commitment transaction to be // allowing the consumer to provide additional fees to the commitment transaction to be
// broadcast. Once the commitment transaction confirms, events for the HTLC resolution should be // broadcast. Once the commitment transaction confirms, events for the HTLC resolution should be
// emitted by LDK, such that the consumer can attach fees to the zero fee HTLC transactions. // emitted by LDK, such that the consumer can attach fees to the zero fee HTLC transactions.
let secp = Secp256k1::new();
let mut chanmon_cfgs = create_chanmon_cfgs(2); let mut chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
let mut anchors_config = UserConfig::default(); let mut anchors_config = UserConfig::default();
@ -1878,6 +1872,7 @@ fn test_yield_anchors_events() {
assert!(nodes[0].node.get_and_clear_pending_events().is_empty()); assert!(nodes[0].node.get_and_clear_pending_events().is_empty());
*nodes[0].fee_estimator.sat_per_kw.lock().unwrap() *= 2;
connect_blocks(&nodes[0], TEST_FINAL_CLTV + LATENCY_GRACE_PERIOD_BLOCKS + 1); connect_blocks(&nodes[0], TEST_FINAL_CLTV + LATENCY_GRACE_PERIOD_BLOCKS + 1);
check_closed_broadcast!(&nodes[0], true); check_closed_broadcast!(&nodes[0], true);
assert!(nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().is_empty()); assert!(nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().is_empty());
@ -1890,26 +1885,23 @@ fn test_yield_anchors_events() {
let mut holder_events = nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events(); let mut holder_events = nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events();
assert_eq!(holder_events.len(), 1); assert_eq!(holder_events.len(), 1);
let (commitment_tx, anchor_tx) = match holder_events.pop().unwrap() { let (commitment_tx, anchor_tx) = match holder_events.pop().unwrap() {
Event::BumpTransaction(BumpTransactionEvent::ChannelClose { commitment_tx, anchor_descriptor, .. }) => { Event::BumpTransaction(event) => {
assert_eq!(commitment_tx.input.len(), 1); let coinbase_tx = Transaction {
assert_eq!(commitment_tx.output.len(), 6);
let mut anchor_tx = Transaction {
version: 2, version: 2,
lock_time: PackedLockTime::ZERO, lock_time: PackedLockTime::ZERO,
input: vec![ input: vec![TxIn { ..Default::default() }],
TxIn { previous_output: anchor_descriptor.outpoint, ..Default::default() }, output: vec![TxOut { // UTXO to attach fees to `anchor_tx`
TxIn { ..Default::default() },
],
output: vec![TxOut {
value: Amount::ONE_BTC.to_sat(), value: Amount::ONE_BTC.to_sat(),
script_pubkey: Script::new_op_return(&[]), script_pubkey: nodes[0].wallet_source.get_change_script().unwrap(),
}], }],
}; };
let signer = anchor_descriptor.derive_channel_signer(&nodes[0].keys_manager); nodes[0].wallet_source.add_utxo(bitcoin::OutPoint { txid: coinbase_tx.txid(), vout: 0 }, coinbase_tx.output[0].value);
let funding_sig = signer.sign_holder_anchor_input(&mut anchor_tx, 0, &secp).unwrap(); nodes[0].bump_tx_handler.handle_event(&event);
anchor_tx.input[0].witness = chan_utils::build_anchor_input_witness( let mut txn = nodes[0].tx_broadcaster.unique_txn_broadcast();
&signer.pubkeys().funding_pubkey, &funding_sig assert_eq!(txn.len(), 2);
); let anchor_tx = txn.pop().unwrap();
let commitment_tx = txn.pop().unwrap();
check_spends!(anchor_tx, coinbase_tx, commitment_tx);
(commitment_tx, anchor_tx) (commitment_tx, anchor_tx)
}, },
_ => panic!("Unexpected event"), _ => panic!("Unexpected event"),
@ -1933,28 +1925,12 @@ fn test_yield_anchors_events() {
let mut htlc_txs = Vec::with_capacity(2); let mut htlc_txs = Vec::with_capacity(2);
for event in holder_events { for event in holder_events {
match event { match event {
Event::BumpTransaction(BumpTransactionEvent::HTLCResolution { htlc_descriptors, tx_lock_time, .. }) => { Event::BumpTransaction(event) => {
assert_eq!(htlc_descriptors.len(), 1); nodes[0].bump_tx_handler.handle_event(&event);
let htlc_descriptor = &htlc_descriptors[0]; let mut txn = nodes[0].tx_broadcaster.unique_txn_broadcast();
let mut htlc_tx = Transaction { assert_eq!(txn.len(), 1);
version: 2, let htlc_tx = txn.pop().unwrap();
lock_time: tx_lock_time, check_spends!(htlc_tx, commitment_tx, anchor_tx);
input: vec![
htlc_descriptor.unsigned_tx_input(), // HTLC input
TxIn { ..Default::default() } // Fee input
],
output: vec![
htlc_descriptor.tx_output(&secp), // HTLC output
TxOut { // Fee input change
value: Amount::ONE_BTC.to_sat(),
script_pubkey: Script::new_op_return(&[]),
}
]
};
let signer = htlc_descriptor.derive_channel_signer(&nodes[0].keys_manager);
let our_sig = signer.sign_holder_htlc_transaction(&mut htlc_tx, 0, htlc_descriptor, &secp).unwrap();
let witness_script = htlc_descriptor.witness_script(&secp);
htlc_tx.input[0].witness = htlc_descriptor.tx_input_witness(&our_sig, &witness_script);
htlc_txs.push(htlc_tx); htlc_txs.push(htlc_tx);
}, },
_ => panic!("Unexpected event"), _ => panic!("Unexpected event"),
@ -2054,11 +2030,12 @@ fn test_anchors_aggregated_revoked_htlc_tx() {
// Bob force closes by restarting with the outdated state, prompting the ChannelMonitors to // Bob force closes by restarting with the outdated state, prompting the ChannelMonitors to
// broadcast the latest commitment transaction known to them, which in our case is the one with // broadcast the latest commitment transaction known to them, which in our case is the one with
// the HTLCs still pending. // the HTLCs still pending.
*nodes[1].fee_estimator.sat_per_kw.lock().unwrap() *= 2;
nodes[1].node.timer_tick_occurred(); nodes[1].node.timer_tick_occurred();
check_added_monitors(&nodes[1], 2); check_added_monitors(&nodes[1], 2);
check_closed_event!(&nodes[1], 2, ClosureReason::OutdatedChannelManager); check_closed_event!(&nodes[1], 2, ClosureReason::OutdatedChannelManager);
let (revoked_commitment_a, revoked_commitment_b) = { let (revoked_commitment_a, revoked_commitment_b) = {
let txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0); let txn = nodes[1].tx_broadcaster.unique_txn_broadcast();
assert_eq!(txn.len(), 2); assert_eq!(txn.len(), 2);
assert_eq!(txn[0].output.len(), 6); // 2 HTLC outputs + 1 to_self output + 1 to_remote output + 2 anchor outputs assert_eq!(txn[0].output.len(), 6); // 2 HTLC outputs + 1 to_self output + 1 to_remote output + 2 anchor outputs
assert_eq!(txn[1].output.len(), 6); // 2 HTLC outputs + 1 to_self output + 1 to_remote output + 2 anchor outputs assert_eq!(txn[1].output.len(), 6); // 2 HTLC outputs + 1 to_self output + 1 to_remote output + 2 anchor outputs
@ -2077,71 +2054,32 @@ fn test_anchors_aggregated_revoked_htlc_tx() {
assert!(nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events().is_empty()); assert!(nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events().is_empty());
let events = nodes[1].chain_monitor.chain_monitor.get_and_clear_pending_events(); let events = nodes[1].chain_monitor.chain_monitor.get_and_clear_pending_events();
assert_eq!(events.len(), 2); assert_eq!(events.len(), 2);
let anchor_tx = { let mut anchor_txs = Vec::with_capacity(events.len());
let secret_key = SecretKey::from_slice(&[1; 32]).unwrap(); for (idx, event) in events.into_iter().enumerate() {
let public_key = PublicKey::new(secret_key.public_key(&secp)); let utxo_value = Amount::ONE_BTC.to_sat() * (idx + 1) as u64;
let fee_utxo_script = Script::new_v0_p2wpkh(&public_key.wpubkey_hash().unwrap());
let coinbase_tx = Transaction { let coinbase_tx = Transaction {
version: 2, version: 2,
lock_time: PackedLockTime::ZERO, lock_time: PackedLockTime::ZERO,
input: vec![TxIn { ..Default::default() }], input: vec![TxIn { ..Default::default() }],
output: vec![TxOut { // UTXO to attach fees to `anchor_tx` output: vec![TxOut { // UTXO to attach fees to `anchor_tx`
value: Amount::ONE_BTC.to_sat(), value: utxo_value,
script_pubkey: fee_utxo_script.clone(), script_pubkey: nodes[1].wallet_source.get_change_script().unwrap(),
}], }],
}; };
let mut anchor_tx = Transaction { nodes[1].wallet_source.add_utxo(bitcoin::OutPoint { txid: coinbase_tx.txid(), vout: 0 }, utxo_value);
version: 2,
lock_time: PackedLockTime::ZERO,
input: vec![
TxIn { // Fee input
previous_output: bitcoin::OutPoint { txid: coinbase_tx.txid(), vout: 0 },
..Default::default()
},
],
output: vec![TxOut { // Fee input change
value: coinbase_tx.output[0].value / 2 ,
script_pubkey: Script::new_op_return(&[]),
}],
};
let mut signers = Vec::with_capacity(2);
for event in events {
match event { match event {
Event::BumpTransaction(BumpTransactionEvent::ChannelClose { anchor_descriptor, .. }) => { Event::BumpTransaction(event) => nodes[1].bump_tx_handler.handle_event(&event),
anchor_tx.input.push(TxIn {
previous_output: anchor_descriptor.outpoint,
..Default::default()
});
let signer = anchor_descriptor.derive_channel_signer(&nodes[1].keys_manager);
signers.push(signer);
},
_ => panic!("Unexpected event"), _ => panic!("Unexpected event"),
}
}
for (i, signer) in signers.into_iter().enumerate() {
let anchor_idx = i + 1;
let funding_sig = signer.sign_holder_anchor_input(&mut anchor_tx, anchor_idx, &secp).unwrap();
anchor_tx.input[anchor_idx].witness = chan_utils::build_anchor_input_witness(
&signer.pubkeys().funding_pubkey, &funding_sig
);
}
let fee_utxo_sig = {
let witness_script = Script::new_p2pkh(&public_key.pubkey_hash());
let sighash = hash_to_message!(&SighashCache::new(&anchor_tx).segwit_signature_hash(
0, &witness_script, coinbase_tx.output[0].value, EcdsaSighashType::All
).unwrap()[..]);
let sig = sign(&secp, &sighash, &secret_key);
let mut sig = sig.serialize_der().to_vec();
sig.push(EcdsaSighashType::All as u8);
sig
}; };
anchor_tx.input[0].witness = Witness::from_vec(vec![fee_utxo_sig, public_key.to_bytes()]); let txn = nodes[1].tx_broadcaster.txn_broadcast();
check_spends!(anchor_tx, coinbase_tx, revoked_commitment_a, revoked_commitment_b); assert_eq!(txn.len(), 2);
anchor_tx let (commitment_tx, anchor_tx) = (&txn[0], &txn[1]);
check_spends!(anchor_tx, coinbase_tx, commitment_tx);
anchor_txs.push(anchor_tx.clone());
}; };
for node in &nodes { for node in &nodes {
mine_transactions(node, &[&revoked_commitment_a, &revoked_commitment_b, &anchor_tx]); mine_transactions(node, &[&revoked_commitment_a, &anchor_txs[0], &revoked_commitment_b, &anchor_txs[1]]);
} }
check_added_monitors!(&nodes[0], 2); check_added_monitors!(&nodes[0], 2);
check_closed_broadcast(&nodes[0], 2, true); check_closed_broadcast(&nodes[0], 2, true);
@ -2211,6 +2149,8 @@ fn test_anchors_aggregated_revoked_htlc_tx() {
}; };
let mut descriptors = Vec::with_capacity(4); let mut descriptors = Vec::with_capacity(4);
for event in events { for event in events {
// We don't use the `BumpTransactionEventHandler` here because it does not support
// creating one transaction from multiple `HTLCResolution` events.
if let Event::BumpTransaction(BumpTransactionEvent::HTLCResolution { mut htlc_descriptors, tx_lock_time, .. }) = event { if let Event::BumpTransaction(BumpTransactionEvent::HTLCResolution { mut htlc_descriptors, tx_lock_time, .. }) = event {
assert_eq!(htlc_descriptors.len(), 2); assert_eq!(htlc_descriptors.len(), 2);
for htlc_descriptor in &htlc_descriptors { for htlc_descriptor in &htlc_descriptors {

View file

@ -169,6 +169,29 @@ impl<'a> core::fmt::Display for DebugBytes<'a> {
} }
} }
/// Wrapper for logging `Iterator`s.
///
/// This is not exported to bindings users as fmt can't be used in C
#[doc(hidden)]
pub struct DebugIter<T: fmt::Display, I: core::iter::Iterator<Item = T> + Clone>(pub core::cell::RefCell<I>);
impl<T: fmt::Display, I: core::iter::Iterator<Item = T> + Clone> fmt::Display for DebugIter<T, I> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
use core::ops::DerefMut;
write!(f, "[")?;
let iter_ref = self.0.clone();
let mut iter = iter_ref.borrow_mut();
for item in iter.deref_mut() {
write!(f, "{}", item)?;
break;
}
for item in iter.deref_mut() {
write!(f, ", {}", item)?;
}
write!(f, "]")?;
Ok(())
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::util::logger::{Logger, Level}; use crate::util::logger::{Logger, Level};

View file

@ -17,6 +17,12 @@ use crate::routing::router::Route;
use crate::ln::chan_utils::HTLCClaim; use crate::ln::chan_utils::HTLCClaim;
use crate::util::logger::DebugBytes; use crate::util::logger::DebugBytes;
macro_rules! log_iter {
($obj: expr) => {
$crate::util::logger::DebugIter(core::cell::RefCell::new($obj))
}
}
/// Logs a pubkey in hex format. /// Logs a pubkey in hex format.
#[macro_export] #[macro_export]
macro_rules! log_pubkey { macro_rules! log_pubkey {

View file

@ -18,6 +18,7 @@ use crate::chain::channelmonitor::MonitorEvent;
use crate::chain::transaction::OutPoint; use crate::chain::transaction::OutPoint;
use crate::sign; use crate::sign;
use crate::events; use crate::events;
use crate::events::bump_transaction::{WalletSource, Utxo};
use crate::ln::channelmanager; use crate::ln::channelmanager;
use crate::ln::features::{ChannelFeatures, InitFeatures, NodeFeatures}; use crate::ln::features::{ChannelFeatures, InitFeatures, NodeFeatures};
use crate::ln::{msgs, wire}; use crate::ln::{msgs, wire};
@ -32,6 +33,7 @@ use crate::util::enforcing_trait_impls::{EnforcingSigner, EnforcementState};
use crate::util::logger::{Logger, Level, Record}; use crate::util::logger::{Logger, Level, Record};
use crate::util::ser::{Readable, ReadableArgs, Writer, Writeable}; use crate::util::ser::{Readable, ReadableArgs, Writer, Writeable};
use bitcoin::EcdsaSighashType;
use bitcoin::blockdata::constants::ChainHash; use bitcoin::blockdata::constants::ChainHash;
use bitcoin::blockdata::constants::genesis_block; use bitcoin::blockdata::constants::genesis_block;
use bitcoin::blockdata::transaction::{Transaction, TxOut}; use bitcoin::blockdata::transaction::{Transaction, TxOut};
@ -40,6 +42,7 @@ use bitcoin::blockdata::opcodes;
use bitcoin::blockdata::block::Block; use bitcoin::blockdata::block::Block;
use bitcoin::network::constants::Network; use bitcoin::network::constants::Network;
use bitcoin::hash_types::{BlockHash, Txid}; use bitcoin::hash_types::{BlockHash, Txid};
use bitcoin::util::sighash::SighashCache;
use bitcoin::secp256k1::{SecretKey, PublicKey, Secp256k1, ecdsa::Signature, Scalar}; use bitcoin::secp256k1::{SecretKey, PublicKey, Secp256k1, ecdsa::Signature, Scalar};
use bitcoin::secp256k1::ecdh::SharedSecret; use bitcoin::secp256k1::ecdh::SharedSecret;
@ -1067,3 +1070,65 @@ impl Drop for TestScorer {
} }
} }
} }
pub struct TestWalletSource {
secret_key: SecretKey,
utxos: RefCell<Vec<Utxo>>,
secp: Secp256k1<bitcoin::secp256k1::All>,
}
impl TestWalletSource {
pub fn new(secret_key: SecretKey) -> Self {
Self {
secret_key,
utxos: RefCell::new(Vec::new()),
secp: Secp256k1::new(),
}
}
pub fn add_utxo(&self, outpoint: bitcoin::OutPoint, value: u64) -> TxOut {
let public_key = bitcoin::PublicKey::new(self.secret_key.public_key(&self.secp));
let utxo = Utxo::new_p2pkh(outpoint, value, &public_key.pubkey_hash());
self.utxos.borrow_mut().push(utxo.clone());
utxo.output
}
pub fn add_custom_utxo(&self, utxo: Utxo) -> TxOut {
let output = utxo.output.clone();
self.utxos.borrow_mut().push(utxo);
output
}
pub fn remove_utxo(&self, outpoint: bitcoin::OutPoint) {
self.utxos.borrow_mut().retain(|utxo| utxo.outpoint != outpoint);
}
}
impl WalletSource for TestWalletSource {
fn list_confirmed_utxos(&self) -> Result<Vec<Utxo>, ()> {
Ok(self.utxos.borrow().clone())
}
fn get_change_script(&self) -> Result<Script, ()> {
let public_key = bitcoin::PublicKey::new(self.secret_key.public_key(&self.secp));
Ok(Script::new_p2pkh(&public_key.pubkey_hash()))
}
fn sign_tx(&self, tx: &mut Transaction) -> Result<(), ()> {
let utxos = self.utxos.borrow();
for i in 0..tx.input.len() {
if let Some(utxo) = utxos.iter().find(|utxo| utxo.outpoint == tx.input[i].previous_output) {
let sighash = SighashCache::new(&*tx)
.legacy_signature_hash(i, &utxo.output.script_pubkey, EcdsaSighashType::All as u32)
.map_err(|_| ())?;
let sig = self.secp.sign_ecdsa(&sighash.as_hash().into(), &self.secret_key);
let bitcoin_sig = bitcoin::EcdsaSig { sig, hash_ty: EcdsaSighashType::All }.to_vec();
tx.input[i].script_sig = Builder::new()
.push_slice(&bitcoin_sig)
.push_slice(&self.secret_key.public_key(&self.secp).serialize())
.into_script();
}
}
Ok(())
}
}