// Background feerate which is <= the minimum Normal feerate.
match conf_target {
ConfirmationTarget::HighPriority => MAX_FEE,
- ConfirmationTarget::Background => 253,
+ ConfirmationTarget::Background|ConfirmationTarget::MempoolMinimum => 253,
ConfirmationTarget::Normal => cmp::min(self.ret_val.load(atomic::Ordering::Acquire), MAX_FEE),
}
}
//! disconnections, transaction broadcasting, and feerate information requests.
use core::{cmp, ops::Deref};
+use core::convert::TryInto;
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.
pub trait BroadcasterInterface {
/// Sends a list of transactions out to (hopefully) be mined.
fn broadcast_transactions(&self, txs: &[&Transaction]);
}
-/// An enum that represents the speed at which we want a transaction to confirm used for feerate
+/// An enum that represents the priority at which we want a transaction to confirm used for feerate
/// estimation.
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub enum ConfirmationTarget {
- /// We are happy with this transaction confirming slowly when feerate drops some.
+ /// We'd like a transaction to confirm in the future, but don't want to commit most of the fees
+ /// required to do so yet. The remaining fees will come via a Child-Pays-For-Parent (CPFP) fee
+ /// bump of the transaction.
+ ///
+ /// The feerate returned should be the absolute minimum feerate required to enter most node
+ /// mempools across the network. Note that if you are not able to obtain this feerate estimate,
+ /// you should likely use the furthest-out estimate allowed by your fee estimator.
+ MempoolMinimum,
+ /// We are happy with a transaction confirming slowly, at least within a day or so worth of
+ /// blocks.
Background,
- /// We'd like this transaction to confirm without major delay, but 12-18 blocks is fine.
+ /// We'd like a transaction to confirm without major delayed, i.e., within the next 12-24 blocks.
Normal,
- /// We'd like this transaction to confirm in the next few blocks.
+ /// We'd like a transaction to confirm in the next few blocks.
HighPriority,
}
use bitcoin::secp256k1::{Secp256k1, ecdsa::Signature};
use bitcoin::secp256k1;
+use crate::chain::chaininterface::compute_feerate_sat_per_1000_weight;
use crate::sign::{ChannelSigner, EntropySource, SignerProvider};
use crate::ln::msgs::DecodeError;
use crate::ln::PaymentPreimage;
return inputs.find_map(|input| match input {
// Commitment inputs with anchors support are the only untractable inputs supported
// thus far that require external funding.
- PackageSolvingData::HolderFundingOutput(..) => {
+ PackageSolvingData::HolderFundingOutput(output) => {
debug_assert_eq!(tx.txid(), self.holder_commitment.trust().txid(),
"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.
let funding_pubkey = &self.channel_transaction_parameters.holder_pubkeys.funding_pubkey;
match chan_utils::get_anchor_output(&tx, funding_pubkey) {
Some((idx, _)) => {
// TODO: Use a lower confirmation target when both our and the
// 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((
new_timer,
package_target_feerate_sat_per_1000_weight as u64,
) {
req.set_timer(new_timer);
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 {
OnchainClaim::Tx(tx) => {
log_info!(logger, "Broadcasting onchain {}", log_tx!(tx));
#[derive(Clone, PartialEq, Eq)]
pub(crate) struct HolderFundingOutput {
funding_redeemscript: Script,
- funding_amount: Option<u64>,
+ pub(crate) funding_amount: Option<u64>,
channel_type_features: ChannelTypeFeatures,
}
//! [`Event`]: crate::events::Event
use alloc::collections::BTreeMap;
-use core::convert::TryInto;
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::io_extras::sink;
use crate::ln::channel::ANCHOR_OUTPUT_VALUE_SATOSHI;
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`].
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ChannelDerivationParameters {
}
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
/// [`Self::unsigned_tx_input`].
pub fn previous_utxo<C: secp256k1::Signing + secp256k1::Verification>(&self, secp: &Secp256k1<C>) -> TxOut {
/// 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
/// 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,
+ logger: L,
// 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
// `list_confirmed_utxos`.
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
/// of [`CoinSelectionSource`].
- pub fn new(source: W) -> Self {
- Self { source, locked_utxos: Mutex::new(HashMap::new()) }
+ pub fn new(source: W, logger: L) -> Self {
+ Self { source, logger, locked_utxos: Mutex::new(HashMap::new()) }
}
/// Performs coin selection on the set of UTXOs obtained from
let mut eligible_utxos = utxos.iter().filter_map(|utxo| {
if let Some(utxo_claim_id) = locked_utxos.get(&utxo.outpoint) {
if *utxo_claim_id != claim_id && !force_conflicting_utxo_spend {
+ log_trace!(self.logger, "Skipping UTXO {} to prevent conflicting spend", utxo.outpoint);
return None;
}
}
if should_spend {
Some((utxo, fee_to_spend_utxo))
} else {
+ log_trace!(self.logger, "Skipping UTXO {} due to dust proximity after spend", utxo.outpoint);
None
}
}).collect::<Vec<_>>();
selected_utxos.push(utxo.clone());
}
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(());
}
for utxo in &selected_utxos {
);
let change_output_amount = remaining_amount.saturating_sub(change_output_fee);
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
} else {
Some(TxOut { script_pubkey: change_script, value: change_output_amount })
}
}
-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(
&self, claim_id: ClaimId, must_spend: &[Input], must_pay_to: &[TxOut],
target_feerate_sat_per_1000_weight: u32,
((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 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(
&utxos, claim_id, force_conflicting_utxo_spend, tolerate_high_network_feerates,
target_feerate_sat_per_1000_weight, preexisting_tx_weight, target_amount_sat,
// 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
// 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 {
value: 0,
script_pubkey: Script::new_op_return(&[]),
}
}
- /// 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, ()> {
+ /// 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
+ /// broadcasts them to the network as a package.
+ fn handle_channel_close(
+ &self, claim_id: ClaimId, package_target_feerate_sat_per_1000_weight: u32,
+ commitment_tx: &Transaction, commitment_tx_fee_sat: u64, anchor_descriptor: &AnchorDescriptor,
+ ) -> Result<(), ()> {
+ // Our commitment transaction already has fees allocated to it, so we should take them into
+ // 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(
+ commitment_tx_fee_sat, commitment_tx.weight() as u64,
+ );
+ let anchor_target_feerate_sat_per_1000_weight = core::cmp::max(
+ package_target_feerate_sat_per_1000_weight - commitment_tx_sat_per_1000_weight,
+ FEERATE_FLOOR_SATS_PER_KW,
+ );
+
+ log_debug!(self.logger, "Peforming coin selection for anchor transaction targeting {} sat/kW",
+ 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, &[], target_feerate_sat_per_1000_weight,
+ claim_id, &must_spend, &[], anchor_target_feerate_sat_per_1000_weight,
)?;
- let mut tx = Transaction {
+ 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![],
};
- self.process_coin_selection(&mut tx, coin_selection);
- Ok(tx)
- }
+ #[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;
- /// 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
- /// broadcasts them to the network as a package.
- fn handle_channel_close(
- &self, claim_id: ClaimId, package_target_feerate_sat_per_1000_weight: u32,
- commitment_tx: &Transaction, commitment_tx_fee_sat: u64, anchor_descriptor: &AnchorDescriptor,
- ) -> Result<(), ()> {
- // Compute the feerate the anchor transaction must meet to meet the overall feerate for the
- // package (commitment + anchor transactions).
- let commitment_tx_sat_per_1000_weight: u32 = compute_feerate_sat_per_1000_weight(
- commitment_tx_fee_sat, commitment_tx.weight() as u64,
- );
- if commitment_tx_sat_per_1000_weight >= package_target_feerate_sat_per_1000_weight {
- // If the commitment transaction already has a feerate high enough on its own, broadcast
- // it as is without a child.
- self.broadcaster.broadcast_transactions(&[&commitment_tx]);
- return Ok(());
- }
+ self.process_coin_selection(&mut anchor_tx, coin_selection);
+ let anchor_txid = anchor_tx.txid();
- let mut anchor_tx = self.build_anchor_tx(
- claim_id, package_target_feerate_sat_per_1000_weight, commitment_tx, anchor_descriptor,
- )?;
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)?;
let signer = anchor_descriptor.derive_channel_signer(&self.signer_provider);
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);
+ #[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]);
Ok(())
}
- /// Returns an unsigned, fee-bumped HTLC transaction, along with the set of signers required to
- /// fulfill the witness for each HTLC input within it.
- fn build_htlc_tx(
+ /// Handles a [`BumpTransactionEvent::HTLCResolution`] event variant by producing a
+ /// fully-signed, fee-bumped HTLC transaction that is broadcast to the network.
+ fn handle_htlc_resolution(
&self, claim_id: ClaimId, target_feerate_sat_per_1000_weight: u32,
htlc_descriptors: &[HTLCDescriptor], tx_lock_time: PackedLockTime,
- ) -> Result<Transaction, ()> {
- let mut tx = Transaction {
+ ) -> Result<(), ()> {
+ let mut htlc_tx = Transaction {
version: 2,
lock_time: tx_lock_time,
input: vec![],
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);
- 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(
- 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);
- Ok(tx)
- }
+ #[cfg(debug_assertions)]
+ 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
- /// fully-signed, fee-bumped HTLC transaction that is broadcast to the network.
- 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,
- )?;
+ #[cfg(debug_assertions)]
+ let unsigned_tx_weight = htlc_tx.weight() as u64 - (htlc_tx.input.len() as u64 * EMPTY_SCRIPT_SIG_WEIGHT);
+ log_debug!(self.logger, "Signing HTLC transaction {}", htlc_tx.txid());
self.utxo_source.sign_tx(&mut htlc_tx)?;
let mut signers = BTreeMap::new();
for (idx, htlc_descriptor) in htlc_descriptors.iter().enumerate() {
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]);
Ok(())
}
match event {
BumpTransactionEvent::ChannelClose {
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(
*claim_id, *package_target_feerate_sat_per_1000_weight, commitment_tx,
*commitment_tx_fee_satoshis, anchor_descriptor,
BumpTransactionEvent::HTLCResolution {
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(
*claim_id, *target_feerate_sat_per_1000_weight, htlc_descriptors, *tx_lock_time,
) {
}
impl<Signer: WriteableEcdsaChannelSigner> Channel<Signer> {
- fn check_remote_fee<F: Deref, L: Deref>(fee_estimator: &LowerBoundedFeeEstimator<F>,
- feerate_per_kw: u32, cur_feerate_per_kw: Option<u32>, logger: &L)
- -> Result<(), ChannelError> where F::Target: FeeEstimator, L::Target: Logger,
+ fn check_remote_fee<F: Deref, L: Deref>(
+ channel_type: &ChannelTypeFeatures, fee_estimator: &LowerBoundedFeeEstimator<F>,
+ feerate_per_kw: u32, cur_feerate_per_kw: Option<u32>, logger: &L
+ ) -> Result<(), ChannelError> where F::Target: FeeEstimator, L::Target: Logger,
{
// We only bound the fee updates on the upper side to prevent completely absurd feerates,
// always accepting up to 25 sat/vByte or 10x our fee estimator's "High Priority" fee.
// We generally don't care too much if they set the feerate to something very high, but it
- // could result in the channel being useless due to everything being dust.
- let upper_limit = cmp::max(250 * 25,
- fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::HighPriority) as u64 * 10);
- if feerate_per_kw as u64 > upper_limit {
- return Err(ChannelError::Close(format!("Peer's feerate much too high. Actual: {}. Our expected upper limit: {}", feerate_per_kw, upper_limit)));
- }
- let lower_limit = fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::Background);
+ // could result in the channel being useless due to everything being dust. This doesn't
+ // apply to channels supporting anchor outputs since HTLC transactions are pre-signed with a
+ // zero fee, so their fee is no longer considered to determine dust limits.
+ if !channel_type.supports_anchors_zero_fee_htlc_tx() {
+ let upper_limit = cmp::max(250 * 25,
+ fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::HighPriority) as u64 * 10);
+ if feerate_per_kw as u64 > upper_limit {
+ return Err(ChannelError::Close(format!("Peer's feerate much too high. Actual: {}. Our expected upper limit: {}", feerate_per_kw, upper_limit)));
+ }
+ }
+
+ // We can afford to use a lower bound with anchors than previously since we can now bump
+ // fees when broadcasting our commitment. However, we must still make sure we meet the
+ // minimum mempool feerate, until package relay is deployed, such that we can ensure the
+ // commitment transaction propagates throughout node mempools on its own.
+ let lower_limit_conf_target = if channel_type.supports_anchors_zero_fee_htlc_tx() {
+ ConfirmationTarget::MempoolMinimum
+ } else {
+ ConfirmationTarget::Background
+ };
+ let lower_limit = fee_estimator.bounded_sat_per_1000_weight(lower_limit_conf_target);
// Some fee estimators round up to the next full sat/vbyte (ie 250 sats per kw), causing
// occasional issues with feerate disagreements between an initiator that wants a feerate
// of 1.1 sat/vbyte and a receiver that wants 1.1 rounded up to 2. Thus, we always add 250
if self.context.channel_state & (ChannelState::PeerDisconnected as u32) == ChannelState::PeerDisconnected as u32 {
return Err(ChannelError::Close("Peer sent update_fee when we needed a channel_reestablish".to_owned()));
}
- Channel::<Signer>::check_remote_fee(fee_estimator, msg.feerate_per_kw, Some(self.context.feerate_per_kw), logger)?;
+ Channel::<Signer>::check_remote_fee(&self.context.channel_type, fee_estimator, msg.feerate_per_kw, Some(self.context.feerate_per_kw), logger)?;
let feerate_over_dust_buffer = msg.feerate_per_kw > self.context.get_dust_buffer_feerate(None);
self.context.pending_update_fee = Some((msg.feerate_per_kw, FeeUpdateState::RemoteAnnounced));
let channel_type = Self::get_initial_channel_type(&config, their_features);
debug_assert!(channel_type.is_subset(&channelmanager::provided_channel_type_features(&config)));
- let feerate = fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::Normal);
+ let commitment_conf_target = if channel_type.supports_anchors_zero_fee_htlc_tx() {
+ ConfirmationTarget::MempoolMinimum
+ } else {
+ ConfirmationTarget::Normal
+ };
+ let commitment_feerate = fee_estimator.bounded_sat_per_1000_weight(commitment_conf_target);
let value_to_self_msat = channel_value_satoshis * 1000 - push_msat;
- let commitment_tx_fee = commit_tx_fee_msat(feerate, MIN_AFFORDABLE_HTLC_COUNT, &channel_type);
+ let commitment_tx_fee = commit_tx_fee_msat(commitment_feerate, MIN_AFFORDABLE_HTLC_COUNT, &channel_type);
if value_to_self_msat < commitment_tx_fee {
return Err(APIError::APIMisuseError{ err: format!("Funding amount ({}) can't even pay fee for initial commitment transaction fee of {}.", value_to_self_msat / 1000, commitment_tx_fee / 1000) });
}
short_channel_id: None,
channel_creation_height: current_chain_height,
- feerate_per_kw: feerate,
+ feerate_per_kw: commitment_feerate,
counterparty_dust_limit_satoshis: 0,
holder_dust_limit_satoshis: MIN_CHAN_DUST_LIMIT_SATOSHIS,
counterparty_max_htlc_value_in_flight_msat: 0,
/// If we receive an error message, it may only be a rejection of the channel type we tried,
/// not of our ability to open any channel at all. Thus, on error, we should first call this
/// and see if we get a new `OpenChannel` message, otherwise the channel is failed.
- pub(crate) fn maybe_handle_error_without_close(&mut self, chain_hash: BlockHash) -> Result<msgs::OpenChannel, ()> {
+ pub(crate) fn maybe_handle_error_without_close<F: Deref>(
+ &mut self, chain_hash: BlockHash, fee_estimator: &LowerBoundedFeeEstimator<F>
+ ) -> Result<msgs::OpenChannel, ()>
+ where
+ F::Target: FeeEstimator
+ {
if !self.context.is_outbound() || self.context.channel_state != ChannelState::OurInitSent as u32 { return Err(()); }
if self.context.channel_type == ChannelTypeFeatures::only_static_remote_key() {
// We've exhausted our options
// whatever reason.
if self.context.channel_type.supports_anchors_zero_fee_htlc_tx() {
self.context.channel_type.clear_anchors_zero_fee_htlc_tx();
+ self.context.feerate_per_kw = fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::Normal);
assert!(!self.context.channel_transaction_parameters.channel_type_features.supports_anchors_nonzero_fee_htlc_tx());
} else if self.context.channel_type.supports_scid_privacy() {
self.context.channel_type.clear_scid_privacy();
if msg.htlc_minimum_msat >= full_channel_value_msat {
return Err(ChannelError::Close(format!("Minimum htlc value ({}) was larger than full channel value ({})", msg.htlc_minimum_msat, full_channel_value_msat)));
}
- Channel::<Signer>::check_remote_fee(fee_estimator, msg.feerate_per_kw, None, logger)?;
+ Channel::<Signer>::check_remote_fee(&channel_type, fee_estimator, msg.feerate_per_kw, None, logger)?;
let max_counterparty_selected_contest_delay = u16::min(config.channel_handshake_limits.their_to_self_delay, MAX_LOCAL_BREAKDOWN_TIMEOUT);
if msg.to_self_delay > max_counterparty_selected_contest_delay {
// arithmetic, causing a panic with debug assertions enabled.
let fee_est = TestFeeEstimator { fee_est: 42 };
let bounded_fee_estimator = LowerBoundedFeeEstimator::new(&fee_est);
- assert!(Channel::<InMemorySigner>::check_remote_fee(&bounded_fee_estimator,
+ assert!(Channel::<InMemorySigner>::check_remote_fee(
+ &ChannelTypeFeatures::only_static_remote_key(), &bounded_fee_estimator,
u32::max_value(), None, &&test_utils::TestLogger::new()).is_err());
}
PersistenceNotifierGuard::optionally_notify(&self.total_consistency_lock, &self.persistence_notifier, || {
let mut should_persist = self.process_background_events();
- let new_feerate = self.fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::Normal);
+ let normal_feerate = self.fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::Normal);
+ let min_mempool_feerate = self.fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::MempoolMinimum);
let per_peer_state = self.per_peer_state.read().unwrap();
for (_cp_id, peer_state_mutex) in per_peer_state.iter() {
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
let peer_state = &mut *peer_state_lock;
for (chan_id, chan) in peer_state.channel_by_id.iter_mut() {
+ let new_feerate = if chan.context.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
+ min_mempool_feerate
+ } else {
+ normal_feerate
+ };
let chan_needs_persist = self.update_channel_fee(chan_id, chan, new_feerate);
if chan_needs_persist == NotifyOption::DoPersist { should_persist = NotifyOption::DoPersist; }
}
PersistenceNotifierGuard::optionally_notify(&self.total_consistency_lock, &self.persistence_notifier, || {
let mut should_persist = self.process_background_events();
- let new_feerate = self.fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::Normal);
+ let normal_feerate = self.fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::Normal);
+ let min_mempool_feerate = self.fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::MempoolMinimum);
let mut handle_errors: Vec<(Result<(), _>, _)> = Vec::new();
let mut timed_out_mpp_htlcs = Vec::new();
let pending_msg_events = &mut peer_state.pending_msg_events;
let counterparty_node_id = *counterparty_node_id;
peer_state.channel_by_id.retain(|chan_id, chan| {
+ let new_feerate = if chan.context.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
+ min_mempool_feerate
+ } else {
+ normal_feerate
+ };
let chan_needs_persist = self.update_channel_fee(chan_id, chan, new_feerate);
if chan_needs_persist == NotifyOption::DoPersist { should_persist = NotifyOption::DoPersist; }
let mut peer_state_lock = peer_state_mutex_opt.unwrap().lock().unwrap();
let peer_state = &mut *peer_state_lock;
if let Some(chan) = peer_state.outbound_v1_channel_by_id.get_mut(&msg.channel_id) {
- if let Ok(msg) = chan.maybe_handle_error_without_close(self.genesis_hash) {
+ if let Ok(msg) = chan.maybe_handle_error_without_close(self.genesis_hash, &self.fee_estimator) {
peer_state.pending_msg_events.push(events::MessageSendEvent::SendOpenChannel {
node_id: *counterparty_node_id,
msg,
use crate::chain::channelmonitor::ChannelMonitor;
use crate::chain::transaction::OutPoint;
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::channelmanager::{AChannelManager, ChainParameters, ChannelManager, ChannelManagerReadArgs, RAACommitmentOrder, PaymentSendFailure, RecipientOnionFields, PaymentId, MIN_CLTV_EXPIRY_DELTA};
use crate::routing::gossip::{P2PGossipSync, NetworkGraph, NetworkUpdate};
use bitcoin::blockdata::block::{Block, BlockHeader};
use bitcoin::blockdata::transaction::{Transaction, TxOut};
-use bitcoin::network::constants::Network;
-
use bitcoin::hash_types::BlockHash;
use bitcoin::hashes::sha256::Hash as Sha256;
use bitcoin::hashes::Hash as _;
-
-use bitcoin::secp256k1::PublicKey;
+use bitcoin::network::constants::Network;
+use bitcoin::secp256k1::{PublicKey, SecretKey};
use crate::io;
use crate::prelude::*;
}
call_claimable_balances(node);
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 blocks: Arc<Mutex<Vec<(Block, u32)>>>,
pub connect_style: Rc<RefCell<ConnectStyle>>,
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> {
pub fn best_block_hash(&self) -> BlockHash {
for i in 0..node_count {
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{
chain_source: cfgs[i].chain_source, tx_broadcaster: cfgs[i].tx_broadcaster,
fee_estimator: cfgs[i].fee_estimator, router: &cfgs[i].router,
blocks: Arc::clone(&cfgs[i].tx_broadcaster.blocks),
connect_style: Rc::clone(&connect_style),
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,
+ ),
})
}
//! 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::transaction::OutPoint;
-use crate::chain::chaininterface::LowerBoundedFeeEstimator;
-use crate::events::bump_transaction::BumpTransactionEvent;
+use crate::chain::chaininterface::{LowerBoundedFeeEstimator, compute_feerate_sat_per_1000_weight};
+use crate::events::bump_transaction::{BumpTransactionEvent, WalletSource};
use crate::events::{Event, MessageSendEvent, MessageSendEventsProvider, ClosureReason, HTLCDestination};
use crate::ln::channel;
-use crate::ln::chan_utils;
use crate::ln::channelmanager::{BREAKDOWN_TIMEOUT, ChannelManager, PaymentId, RecipientOnionFields};
use crate::ln::msgs::ChannelMessageHandler;
use crate::util::config::UserConfig;
check_closed_event(&nodes[0], 1, ClosureReason::CommitmentTxConfirmed, false);
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
// 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.
let mut check_htlc_retry = |should_retry: bool, should_bump: bool| -> Option<Transaction> {
let (htlc_tx, htlc_tx_feerate) = if anchors {
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 });
if !should_retry {
return None;
}
- #[allow(unused_assignments)]
- let mut tx = Transaction {
- version: 2,
- lock_time: bitcoin::PackedLockTime::ZERO,
- input: vec![],
- output: vec![],
- };
- #[allow(unused_assignments)]
- let mut feerate = 0;
- feerate = if let Event::BumpTransaction(BumpTransactionEvent::HTLCResolution {
- target_feerate_sat_per_1000_weight, mut htlc_descriptors, tx_lock_time, ..
- }) = events.pop().unwrap() {
- let secp = Secp256k1::new();
- 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)
+ match &events[0] {
+ Event::BumpTransaction(event) => {
+ nodes[0].bump_tx_handler.handle_event(&event);
+ let mut txn = nodes[0].tx_broadcaster.unique_txn_broadcast();
+ assert_eq!(txn.len(), 1);
+ let htlc_tx = txn.pop().unwrap();
+ check_spends!(&htlc_tx, &commitment_txn[0], &coinbase_tx);
+ let htlc_tx_fee = HTLC_AMT_SAT + coinbase_tx.output[0].value -
+ htlc_tx.output.iter().map(|output| output.value).sum::<u64>();
+ let htlc_tx_weight = htlc_tx.weight() as u64;
+ (htlc_tx, compute_feerate_sat_per_1000_weight(htlc_tx_fee, htlc_tx_weight))
+ }
+ _ => panic!("Unexpected event"),
+ }
} else {
assert!(nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events().is_empty());
let mut txn = nodes[0].tx_broadcaster.txn_broadcast();
let htlc_tx = txn.pop().unwrap();
check_spends!(htlc_tx, commitment_txn[0]);
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;
- (htlc_tx, htlc_tx_feerate)
+ let htlc_tx_weight = htlc_tx.weight() as u64;
+ (htlc_tx, compute_feerate_sat_per_1000_weight(htlc_tx_fee, htlc_tx_weight))
};
if should_bump {
assert!(htlc_tx_feerate > prev_htlc_tx_feerate.take().unwrap());
// Mine the HTLC transaction to ensure we don't retry claims while they're confirmed.
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.
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);
}
nodes[0].chain_monitor.chain_monitor.rebroadcast_pending_claims();
// 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
// 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 node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
let mut anchors_config = UserConfig::default();
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);
check_closed_broadcast!(&nodes[0], true);
assert!(nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().is_empty());
let mut holder_events = nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events();
assert_eq!(holder_events.len(), 1);
let (commitment_tx, anchor_tx) = match holder_events.pop().unwrap() {
- Event::BumpTransaction(BumpTransactionEvent::ChannelClose { commitment_tx, anchor_descriptor, .. }) => {
- assert_eq!(commitment_tx.input.len(), 1);
- assert_eq!(commitment_tx.output.len(), 6);
- let mut anchor_tx = Transaction {
+ Event::BumpTransaction(event) => {
+ let coinbase_tx = Transaction {
version: 2,
lock_time: PackedLockTime::ZERO,
- input: vec![
- TxIn { previous_output: anchor_descriptor.outpoint, ..Default::default() },
- TxIn { ..Default::default() },
- ],
- output: vec![TxOut {
+ input: vec![TxIn { ..Default::default() }],
+ output: vec![TxOut { // UTXO to attach fees to `anchor_tx`
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);
- let funding_sig = signer.sign_holder_anchor_input(&mut anchor_tx, 0, &secp).unwrap();
- anchor_tx.input[0].witness = chan_utils::build_anchor_input_witness(
- &signer.pubkeys().funding_pubkey, &funding_sig
- );
+ nodes[0].wallet_source.add_utxo(bitcoin::OutPoint { txid: coinbase_tx.txid(), vout: 0 }, coinbase_tx.output[0].value);
+ nodes[0].bump_tx_handler.handle_event(&event);
+ let mut txn = nodes[0].tx_broadcaster.unique_txn_broadcast();
+ 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)
},
_ => panic!("Unexpected event"),
let mut htlc_txs = Vec::with_capacity(2);
for event in holder_events {
match event {
- Event::BumpTransaction(BumpTransactionEvent::HTLCResolution { htlc_descriptors, tx_lock_time, .. }) => {
- assert_eq!(htlc_descriptors.len(), 1);
- let htlc_descriptor = &htlc_descriptors[0];
- let mut htlc_tx = Transaction {
- version: 2,
- lock_time: tx_lock_time,
- 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);
+ Event::BumpTransaction(event) => {
+ nodes[0].bump_tx_handler.handle_event(&event);
+ let mut txn = nodes[0].tx_broadcaster.unique_txn_broadcast();
+ assert_eq!(txn.len(), 1);
+ let htlc_tx = txn.pop().unwrap();
+ check_spends!(htlc_tx, commitment_tx, anchor_tx);
htlc_txs.push(htlc_tx);
},
_ => panic!("Unexpected event"),
// 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
// the HTLCs still pending.
+ *nodes[1].fee_estimator.sat_per_kw.lock().unwrap() *= 2;
nodes[1].node.timer_tick_occurred();
check_added_monitors(&nodes[1], 2);
check_closed_event!(&nodes[1], 2, ClosureReason::OutdatedChannelManager);
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[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!(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();
assert_eq!(events.len(), 2);
- let anchor_tx = {
- let secret_key = SecretKey::from_slice(&[1; 32]).unwrap();
- let public_key = PublicKey::new(secret_key.public_key(&secp));
- let fee_utxo_script = Script::new_v0_p2wpkh(&public_key.wpubkey_hash().unwrap());
+ let mut anchor_txs = Vec::with_capacity(events.len());
+ for (idx, event) in events.into_iter().enumerate() {
+ let utxo_value = Amount::ONE_BTC.to_sat() * (idx + 1) as u64;
let coinbase_tx = Transaction {
version: 2,
lock_time: PackedLockTime::ZERO,
input: vec![TxIn { ..Default::default() }],
output: vec![TxOut { // UTXO to attach fees to `anchor_tx`
- value: Amount::ONE_BTC.to_sat(),
- script_pubkey: fee_utxo_script.clone(),
- }],
- };
- let mut anchor_tx = Transaction {
- 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(&[]),
+ value: utxo_value,
+ script_pubkey: nodes[1].wallet_source.get_change_script().unwrap(),
}],
};
- let mut signers = Vec::with_capacity(2);
- for event in events {
- match event {
- Event::BumpTransaction(BumpTransactionEvent::ChannelClose { anchor_descriptor, .. }) => {
- 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"),
- }
- }
- 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
+ nodes[1].wallet_source.add_utxo(bitcoin::OutPoint { txid: coinbase_tx.txid(), vout: 0 }, utxo_value);
+ match event {
+ Event::BumpTransaction(event) => nodes[1].bump_tx_handler.handle_event(&event),
+ _ => panic!("Unexpected event"),
};
- anchor_tx.input[0].witness = Witness::from_vec(vec![fee_utxo_sig, public_key.to_bytes()]);
- check_spends!(anchor_tx, coinbase_tx, revoked_commitment_a, revoked_commitment_b);
- anchor_tx
+ let txn = nodes[1].tx_broadcaster.txn_broadcast();
+ assert_eq!(txn.len(), 2);
+ 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 {
- 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_closed_broadcast(&nodes[0], 2, true);
};
let mut descriptors = Vec::with_capacity(4);
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 {
assert_eq!(htlc_descriptors.len(), 2);
for htlc_descriptor in &htlc_descriptors {
}
}
+/// 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)]
mod tests {
use crate::util::logger::{Logger, Level};
use crate::ln::chan_utils::HTLCClaim;
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.
#[macro_export]
macro_rules! log_pubkey {
use crate::chain::transaction::OutPoint;
use crate::sign;
use crate::events;
+use crate::events::bump_transaction::{WalletSource, Utxo};
use crate::ln::channelmanager;
use crate::ln::features::{ChannelFeatures, InitFeatures, NodeFeatures};
use crate::ln::{msgs, wire};
use crate::util::logger::{Logger, Level, Record};
use crate::util::ser::{Readable, ReadableArgs, Writer, Writeable};
+use bitcoin::EcdsaSighashType;
use bitcoin::blockdata::constants::ChainHash;
use bitcoin::blockdata::constants::genesis_block;
use bitcoin::blockdata::transaction::{Transaction, TxOut};
use bitcoin::blockdata::block::Block;
use bitcoin::network::constants::Network;
use bitcoin::hash_types::{BlockHash, Txid};
+use bitcoin::util::sighash::SighashCache;
use bitcoin::secp256k1::{SecretKey, PublicKey, Secp256k1, ecdsa::Signature, Scalar};
use bitcoin::secp256k1::ecdh::SharedSecret;
}
}
}
+
+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(())
+ }
+}