use bitcoin::hashes::{Hash, HashEngine};
use bitcoin::hashes::sha256::Hash as Sha256;
use bitcoin::hashes::ripemd160::Hash as Ripemd160;
-use bitcoin::hash_types::{Txid, PubkeyHash};
+use bitcoin::hash_types::{Txid, PubkeyHash, WPubkeyHash};
+use crate::chain::chaininterface::fee_for_weight;
+use crate::chain::package::WEIGHT_REVOKED_OUTPUT;
use crate::sign::EntropySource;
use crate::ln::{PaymentHash, PaymentPreimage};
use crate::ln::msgs::DecodeError;
/// channel basepoints via the new function, or they were obtained via
/// CommitmentTransaction.trust().keys() because we trusted the source of the
/// pre-calculated keys.
-#[derive(PartialEq, Eq, Clone)]
+#[derive(PartialEq, Eq, Clone, Debug)]
pub struct TxCreationKeys {
/// The broadcaster's per-commitment public key which was used to derive the other keys.
pub per_commitment_point: PublicKey,
});
/// One counterparty's public keys which do not change over the life of a channel.
-#[derive(Clone, Debug, PartialEq, Eq)]
+#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct ChannelPublicKeys {
/// The public key which is used to sign all commitment transactions, as it appears in the
/// on-chain channel lock-in 2-of-2 multisig output.
res
}
+/// Returns the script for the counterparty's output on a holder's commitment transaction based on
+/// the channel type.
+pub fn get_counterparty_payment_script(channel_type_features: &ChannelTypeFeatures, payment_key: &PublicKey) -> Script {
+ if channel_type_features.supports_anchors_zero_fee_htlc_tx() {
+ get_to_countersignatory_with_anchors_redeemscript(payment_key).to_v0_p2wsh()
+ } else {
+ Script::new_v0_p2wpkh(&WPubkeyHash::hash(&payment_key.serialize()))
+ }
+}
+
/// Information about an HTLC as it appears in a commitment transaction
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct HTLCOutputInCommitment {
.into_script()
}
-#[cfg(anchors)]
/// Locates the output with an anchor script paying to `funding_pubkey` within `commitment_tx`.
pub(crate) fn get_anchor_output<'a>(commitment_tx: &'a Transaction, funding_pubkey: &PublicKey) -> Option<(u32, &'a TxOut)> {
let anchor_script = chan_utils::get_anchor_redeemscript(funding_pubkey).to_v0_p2wsh();
///
/// Normally, this is converted to the broadcaster/countersignatory-organized DirectedChannelTransactionParameters
/// before use, via the as_holder_broadcastable and as_counterparty_broadcastable functions.
-#[derive(Clone, Debug, PartialEq, Eq)]
+#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct ChannelTransactionParameters {
/// Holder public keys
pub holder_pubkeys: ChannelPublicKeys,
}
/// Late-bound per-channel counterparty data used to build transactions.
-#[derive(Clone, Debug, PartialEq, Eq)]
+#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct CounterpartyChannelTransactionParameters {
/// Counter-party public keys
pub pubkeys: ChannelPublicKeys,
let mut is_outbound_from_holder = RequiredWrapper(None);
let mut counterparty_parameters = None;
let mut funding_outpoint = None;
- let mut legacy_deserialization_prevention_marker: Option<()> = None;
+ let mut _legacy_deserialization_prevention_marker: Option<()> = None;
let mut channel_type_features = None;
read_tlv_fields!(reader, {
(4, is_outbound_from_holder, required),
(6, counterparty_parameters, option),
(8, funding_outpoint, option),
- (10, legacy_deserialization_prevention_marker, option),
+ (10, _legacy_deserialization_prevention_marker, option),
(11, channel_type_features, option),
});
+ let mut additional_features = ChannelTypeFeatures::empty();
+ additional_features.set_anchors_nonzero_fee_htlc_tx_required();
+ chain::package::verify_channel_type_features(&channel_type_features, Some(&additional_features))?;
+
Ok(Self {
holder_pubkeys: holder_pubkeys.0.unwrap(),
holder_selected_contest_delay: holder_selected_contest_delay.0.unwrap(),
impl<'a> DirectedChannelTransactionParameters<'a> {
/// Get the channel pubkeys for the broadcaster
- pub fn broadcaster_pubkeys(&self) -> &ChannelPublicKeys {
+ pub fn broadcaster_pubkeys(&self) -> &'a ChannelPublicKeys {
if self.holder_is_broadcaster {
&self.inner.holder_pubkeys
} else {
}
/// Get the channel pubkeys for the countersignatory
- pub fn countersignatory_pubkeys(&self) -> &ChannelPublicKeys {
+ pub fn countersignatory_pubkeys(&self) -> &'a ChannelPublicKeys {
if self.holder_is_broadcaster {
&self.inner.counterparty_parameters.as_ref().unwrap().pubkeys
} else {
}
/// Whether to use anchors for this channel
- pub fn channel_type_features(&self) -> &ChannelTypeFeatures {
+ pub fn channel_type_features(&self) -> &'a ChannelTypeFeatures {
&self.inner.channel_type_features
}
}
/// Information needed to build and sign a holder's commitment transaction.
///
/// The transaction is only signed once we are ready to broadcast.
-#[derive(Clone)]
+#[derive(Clone, Debug)]
pub struct HolderCommitmentTransaction {
inner: CommitmentTransaction,
/// Our counterparty's signature for the transaction
(0, inner, required),
(2, counterparty_sig, required),
(4, holder_sig_first, required),
- (6, counterparty_htlc_sigs, vec_type),
+ (6, counterparty_htlc_sigs, required_vec),
});
impl HolderCommitmentTransaction {
}
/// A pre-built Bitcoin commitment transaction and its txid.
-#[derive(Clone)]
+#[derive(Clone, Debug)]
pub struct BuiltCommitmentTransaction {
/// The commitment transaction
pub transaction: Transaction,
impl<'a> TrustedClosingTransaction<'a> {
/// The pre-built Bitcoin commitment transaction
- pub fn built_transaction(&self) -> &Transaction {
+ pub fn built_transaction(&self) -> &'a Transaction {
&self.inner.built
}
///
/// This class can be used inside a signer implementation to generate a signature given the relevant
/// secret key.
-#[derive(Clone)]
+#[derive(Clone, Debug)]
pub struct CommitmentTransaction {
commitment_number: u64,
to_broadcaster_value_sat: u64,
to_countersignatory_value_sat: u64,
+ to_broadcaster_delay: Option<u16>, // Added in 0.0.117
feerate_per_kw: u32,
htlcs: Vec<HTLCOutputInCommitment>,
// Note that on upgrades, some features of existing outputs may be missed.
let legacy_deserialization_prevention_marker = legacy_deserialization_prevention_marker_for_channel_type_features(&self.channel_type_features);
write_tlv_fields!(writer, {
(0, self.commitment_number, required),
+ (1, self.to_broadcaster_delay, option),
(2, self.to_broadcaster_value_sat, required),
(4, self.to_countersignatory_value_sat, required),
(6, self.feerate_per_kw, required),
(8, self.keys, required),
(10, self.built, required),
- (12, self.htlcs, vec_type),
+ (12, self.htlcs, required_vec),
(14, legacy_deserialization_prevention_marker, option),
(15, self.channel_type_features, required),
});
impl Readable for CommitmentTransaction {
fn read<R: io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
- let mut commitment_number = RequiredWrapper(None);
- let mut to_broadcaster_value_sat = RequiredWrapper(None);
- let mut to_countersignatory_value_sat = RequiredWrapper(None);
- let mut feerate_per_kw = RequiredWrapper(None);
- let mut keys = RequiredWrapper(None);
- let mut built = RequiredWrapper(None);
- _init_tlv_field_var!(htlcs, vec_type);
- let mut legacy_deserialization_prevention_marker: Option<()> = None;
- let mut channel_type_features = None;
-
- read_tlv_fields!(reader, {
+ _init_and_read_len_prefixed_tlv_fields!(reader, {
(0, commitment_number, required),
+ (1, to_broadcaster_delay, option),
(2, to_broadcaster_value_sat, required),
(4, to_countersignatory_value_sat, required),
(6, feerate_per_kw, required),
(8, keys, required),
(10, built, required),
- (12, htlcs, vec_type),
- (14, legacy_deserialization_prevention_marker, option),
+ (12, htlcs, required_vec),
+ (14, _legacy_deserialization_prevention_marker, option),
(15, channel_type_features, option),
});
+ let mut additional_features = ChannelTypeFeatures::empty();
+ additional_features.set_anchors_nonzero_fee_htlc_tx_required();
+ chain::package::verify_channel_type_features(&channel_type_features, Some(&additional_features))?;
+
Ok(Self {
commitment_number: commitment_number.0.unwrap(),
to_broadcaster_value_sat: to_broadcaster_value_sat.0.unwrap(),
to_countersignatory_value_sat: to_countersignatory_value_sat.0.unwrap(),
+ to_broadcaster_delay,
feerate_per_kw: feerate_per_kw.0.unwrap(),
keys: keys.0.unwrap(),
built: built.0.unwrap(),
- htlcs: _init_tlv_based_struct_field!(htlcs, vec_type),
+ htlcs,
channel_type_features: channel_type_features.unwrap_or(ChannelTypeFeatures::only_static_remote_key())
})
}
commitment_number,
to_broadcaster_value_sat,
to_countersignatory_value_sat,
+ to_broadcaster_delay: Some(channel_parameters.contest_delay()),
feerate_per_kw,
htlcs,
channel_type_features: channel_parameters.channel_type_features().clone(),
}
/// The pre-built Bitcoin commitment transaction
- pub fn built_transaction(&self) -> &BuiltCommitmentTransaction {
+ pub fn built_transaction(&self) -> &'a BuiltCommitmentTransaction {
&self.inner.built
}
/// The pre-calculated transaction creation public keys.
- pub fn keys(&self) -> &TxCreationKeys {
+ pub fn keys(&self) -> &'a TxCreationKeys {
&self.inner.keys
}
/// Should anchors be used.
- pub fn channel_type_features(&self) -> &ChannelTypeFeatures {
+ pub fn channel_type_features(&self) -> &'a ChannelTypeFeatures {
&self.inner.channel_type_features
}
);
htlc_tx
}
+
+ /// Returns the index of the revokeable output, i.e. the `to_local` output sending funds to
+ /// the broadcaster, in the built transaction, if any exists.
+ ///
+ /// There are two cases where this may return `None`:
+ /// - The balance of the revokeable output is below the dust limit (only found on commitments
+ /// early in the channel's lifetime, i.e. before the channel reserve is met).
+ /// - This commitment was created before LDK 0.0.117. In this case, the
+ /// commitment transaction previously didn't contain enough information to locate the
+ /// revokeable output.
+ pub fn revokeable_output_index(&self) -> Option<usize> {
+ let revokeable_redeemscript = get_revokeable_redeemscript(
+ &self.keys.revocation_key,
+ self.to_broadcaster_delay?,
+ &self.keys.broadcaster_delayed_payment_key,
+ );
+ let revokeable_p2wsh = revokeable_redeemscript.to_v0_p2wsh();
+ let outputs = &self.inner.built.transaction.output;
+ outputs.iter().enumerate()
+ .find(|(_, out)| out.script_pubkey == revokeable_p2wsh)
+ .map(|(idx, _)| idx)
+ }
+
+ /// Helper method to build an unsigned justice transaction spending the revokeable
+ /// `to_local` output to a destination script. Fee estimation accounts for the expected
+ /// revocation witness data that will be added when signed.
+ ///
+ /// This method will error if the given fee rate results in a fee greater than the value
+ /// of the output being spent, or if there exists no revokeable `to_local` output on this
+ /// commitment transaction. See [`Self::revokeable_output_index`] for more details.
+ ///
+ /// The built transaction will allow fee bumping with RBF, and this method takes
+ /// `feerate_per_kw` as an input such that multiple copies of a justice transaction at different
+ /// fee rates may be built.
+ pub fn build_to_local_justice_tx(&self, feerate_per_kw: u64, destination_script: Script)
+ -> Result<Transaction, ()> {
+ let output_idx = self.revokeable_output_index().ok_or(())?;
+ let input = vec![TxIn {
+ previous_output: OutPoint {
+ txid: self.trust().txid(),
+ vout: output_idx as u32,
+ },
+ script_sig: Script::new(),
+ sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
+ witness: Witness::new(),
+ }];
+ let value = self.inner.built.transaction.output[output_idx].value;
+ let output = vec![TxOut {
+ script_pubkey: destination_script,
+ value,
+ }];
+ let mut justice_tx = Transaction {
+ version: 2,
+ lock_time: PackedLockTime::ZERO,
+ input,
+ output,
+ };
+ let weight = justice_tx.weight() as u64 + WEIGHT_REVOKED_OUTPUT;
+ let fee = fee_for_weight(feerate_per_kw as u32, weight);
+ justice_tx.output[0].value = value.checked_sub(fee).ok_or(())?;
+ Ok(justice_tx)
+ }
+
}
/// Commitment transaction numbers which appear in the transactions themselves are XOR'd with a
#[cfg(test)]
mod tests {
- use super::CounterpartyCommitmentSecrets;
+ use super::{CounterpartyCommitmentSecrets, ChannelPublicKeys};
use crate::{hex, chain};
use crate::prelude::*;
use crate::ln::chan_utils::{get_htlc_redeemscript, get_to_countersignatory_with_anchors_redeemscript, CommitmentTransaction, TxCreationKeys, ChannelTransactionParameters, CounterpartyChannelTransactionParameters, HTLCOutputInCommitment};
use bitcoin::secp256k1::{PublicKey, SecretKey, Secp256k1};
use crate::util::test_utils;
use crate::sign::{ChannelSigner, SignerProvider};
- use bitcoin::{Network, Txid};
+ use bitcoin::{Network, Txid, Script};
use bitcoin::hashes::Hash;
use crate::ln::PaymentHash;
use bitcoin::hashes::hex::ToHex;
use bitcoin::PublicKey as BitcoinPublicKey;
use crate::ln::features::ChannelTypeFeatures;
- #[test]
- fn test_anchors() {
- let secp_ctx = Secp256k1::new();
+ struct TestCommitmentTxBuilder {
+ commitment_number: u64,
+ holder_funding_pubkey: PublicKey,
+ counterparty_funding_pubkey: PublicKey,
+ keys: TxCreationKeys,
+ feerate_per_kw: u32,
+ htlcs_with_aux: Vec<(HTLCOutputInCommitment, ())>,
+ channel_parameters: ChannelTransactionParameters,
+ counterparty_pubkeys: ChannelPublicKeys,
+ }
+
+ impl TestCommitmentTxBuilder {
+ fn new() -> Self {
+ let secp_ctx = Secp256k1::new();
+ let seed = [42; 32];
+ let network = Network::Testnet;
+ let keys_provider = test_utils::TestKeysInterface::new(&seed, network);
+ let signer = keys_provider.derive_channel_signer(3000, keys_provider.generate_channel_keys_id(false, 1_000_000, 0));
+ let counterparty_signer = keys_provider.derive_channel_signer(3000, keys_provider.generate_channel_keys_id(true, 1_000_000, 1));
+ let delayed_payment_base = &signer.pubkeys().delayed_payment_basepoint;
+ let per_commitment_secret = SecretKey::from_slice(&hex::decode("1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100").unwrap()[..]).unwrap();
+ let per_commitment_point = PublicKey::from_secret_key(&secp_ctx, &per_commitment_secret);
+ let htlc_basepoint = &signer.pubkeys().htlc_basepoint;
+ let holder_pubkeys = signer.pubkeys();
+ let counterparty_pubkeys = counterparty_signer.pubkeys().clone();
+ let keys = TxCreationKeys::derive_new(&secp_ctx, &per_commitment_point, delayed_payment_base, htlc_basepoint, &counterparty_pubkeys.revocation_basepoint, &counterparty_pubkeys.htlc_basepoint);
+ let channel_parameters = ChannelTransactionParameters {
+ holder_pubkeys: holder_pubkeys.clone(),
+ holder_selected_contest_delay: 0,
+ is_outbound_from_holder: false,
+ counterparty_parameters: Some(CounterpartyChannelTransactionParameters { pubkeys: counterparty_pubkeys.clone(), selected_contest_delay: 0 }),
+ funding_outpoint: Some(chain::transaction::OutPoint { txid: Txid::all_zeros(), index: 0 }),
+ channel_type_features: ChannelTypeFeatures::only_static_remote_key(),
+ };
+ let htlcs_with_aux = Vec::new();
+
+ Self {
+ commitment_number: 0,
+ holder_funding_pubkey: holder_pubkeys.funding_pubkey,
+ counterparty_funding_pubkey: counterparty_pubkeys.funding_pubkey,
+ keys,
+ feerate_per_kw: 1,
+ htlcs_with_aux,
+ channel_parameters,
+ counterparty_pubkeys,
+ }
+ }
- let seed = [42; 32];
- let network = Network::Testnet;
- let keys_provider = test_utils::TestKeysInterface::new(&seed, network);
- let signer = keys_provider.derive_channel_signer(3000, keys_provider.generate_channel_keys_id(false, 1_000_000, 0));
- let counterparty_signer = keys_provider.derive_channel_signer(3000, keys_provider.generate_channel_keys_id(true, 1_000_000, 1));
- let delayed_payment_base = &signer.pubkeys().delayed_payment_basepoint;
- let per_commitment_secret = SecretKey::from_slice(&hex::decode("1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100").unwrap()[..]).unwrap();
- let per_commitment_point = PublicKey::from_secret_key(&secp_ctx, &per_commitment_secret);
- let htlc_basepoint = &signer.pubkeys().htlc_basepoint;
- let holder_pubkeys = signer.pubkeys();
- let counterparty_pubkeys = counterparty_signer.pubkeys();
- let keys = TxCreationKeys::derive_new(&secp_ctx, &per_commitment_point, delayed_payment_base, htlc_basepoint, &counterparty_pubkeys.revocation_basepoint, &counterparty_pubkeys.htlc_basepoint);
- let mut channel_parameters = ChannelTransactionParameters {
- holder_pubkeys: holder_pubkeys.clone(),
- holder_selected_contest_delay: 0,
- is_outbound_from_holder: false,
- counterparty_parameters: Some(CounterpartyChannelTransactionParameters { pubkeys: counterparty_pubkeys.clone(), selected_contest_delay: 0 }),
- funding_outpoint: Some(chain::transaction::OutPoint { txid: Txid::all_zeros(), index: 0 }),
- channel_type_features: ChannelTypeFeatures::only_static_remote_key(),
- };
+ fn build(&mut self, to_broadcaster_sats: u64, to_countersignatory_sats: u64) -> CommitmentTransaction {
+ CommitmentTransaction::new_with_auxiliary_htlc_data(
+ self.commitment_number, to_broadcaster_sats, to_countersignatory_sats,
+ self.holder_funding_pubkey.clone(),
+ self.counterparty_funding_pubkey.clone(),
+ self.keys.clone(), self.feerate_per_kw,
+ &mut self.htlcs_with_aux, &self.channel_parameters.as_holder_broadcastable()
+ )
+ }
+ }
- let mut htlcs_with_aux: Vec<(_, ())> = Vec::new();
+ #[test]
+ fn test_anchors() {
+ let mut builder = TestCommitmentTxBuilder::new();
// Generate broadcaster and counterparty outputs
- let tx = CommitmentTransaction::new_with_auxiliary_htlc_data(
- 0, 1000, 2000,
- holder_pubkeys.funding_pubkey,
- counterparty_pubkeys.funding_pubkey,
- keys.clone(), 1,
- &mut htlcs_with_aux, &channel_parameters.as_holder_broadcastable()
- );
+ let tx = builder.build(1000, 2000);
assert_eq!(tx.built.transaction.output.len(), 2);
- assert_eq!(tx.built.transaction.output[1].script_pubkey, Payload::p2wpkh(&BitcoinPublicKey::new(counterparty_pubkeys.payment_point)).unwrap().script_pubkey());
+ assert_eq!(tx.built.transaction.output[1].script_pubkey, Payload::p2wpkh(&BitcoinPublicKey::new(builder.counterparty_pubkeys.payment_point)).unwrap().script_pubkey());
// Generate broadcaster and counterparty outputs as well as two anchors
- channel_parameters.channel_type_features = ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies();
- let tx = CommitmentTransaction::new_with_auxiliary_htlc_data(
- 0, 1000, 2000,
- holder_pubkeys.funding_pubkey,
- counterparty_pubkeys.funding_pubkey,
- keys.clone(), 1,
- &mut htlcs_with_aux, &channel_parameters.as_holder_broadcastable()
- );
+ builder.channel_parameters.channel_type_features = ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies();
+ let tx = builder.build(1000, 2000);
assert_eq!(tx.built.transaction.output.len(), 4);
- assert_eq!(tx.built.transaction.output[3].script_pubkey, get_to_countersignatory_with_anchors_redeemscript(&counterparty_pubkeys.payment_point).to_v0_p2wsh());
+ assert_eq!(tx.built.transaction.output[3].script_pubkey, get_to_countersignatory_with_anchors_redeemscript(&builder.counterparty_pubkeys.payment_point).to_v0_p2wsh());
// Generate broadcaster output and anchor
- let tx = CommitmentTransaction::new_with_auxiliary_htlc_data(
- 0, 3000, 0,
- holder_pubkeys.funding_pubkey,
- counterparty_pubkeys.funding_pubkey,
- keys.clone(), 1,
- &mut htlcs_with_aux, &channel_parameters.as_holder_broadcastable()
- );
+ let tx = builder.build(3000, 0);
assert_eq!(tx.built.transaction.output.len(), 2);
// Generate counterparty output and anchor
- let tx = CommitmentTransaction::new_with_auxiliary_htlc_data(
- 0, 0, 3000,
- holder_pubkeys.funding_pubkey,
- counterparty_pubkeys.funding_pubkey,
- keys.clone(), 1,
- &mut htlcs_with_aux, &channel_parameters.as_holder_broadcastable()
- );
+ let tx = builder.build(0, 3000);
assert_eq!(tx.built.transaction.output.len(), 2);
let received_htlc = HTLCOutputInCommitment {
};
// Generate broadcaster output and received and offered HTLC outputs, w/o anchors
- channel_parameters.channel_type_features = ChannelTypeFeatures::only_static_remote_key();
- let tx = CommitmentTransaction::new_with_auxiliary_htlc_data(
- 0, 3000, 0,
- holder_pubkeys.funding_pubkey,
- counterparty_pubkeys.funding_pubkey,
- keys.clone(), 1,
- &mut vec![(received_htlc.clone(), ()), (offered_htlc.clone(), ())],
- &channel_parameters.as_holder_broadcastable()
- );
+ builder.channel_parameters.channel_type_features = ChannelTypeFeatures::only_static_remote_key();
+ builder.htlcs_with_aux = vec![(received_htlc.clone(), ()), (offered_htlc.clone(), ())];
+ let tx = builder.build(3000, 0);
+ let keys = &builder.keys.clone();
assert_eq!(tx.built.transaction.output.len(), 3);
assert_eq!(tx.built.transaction.output[0].script_pubkey, get_htlc_redeemscript(&received_htlc, &ChannelTypeFeatures::only_static_remote_key(), &keys).to_v0_p2wsh());
assert_eq!(tx.built.transaction.output[1].script_pubkey, get_htlc_redeemscript(&offered_htlc, &ChannelTypeFeatures::only_static_remote_key(), &keys).to_v0_p2wsh());
"0020215d61bba56b19e9eadb6107f5a85d7f99c40f65992443f69229c290165bc00d");
// Generate broadcaster output and received and offered HTLC outputs, with anchors
- channel_parameters.channel_type_features = ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies();
- let tx = CommitmentTransaction::new_with_auxiliary_htlc_data(
- 0, 3000, 0,
- holder_pubkeys.funding_pubkey,
- counterparty_pubkeys.funding_pubkey,
- keys.clone(), 1,
- &mut vec![(received_htlc.clone(), ()), (offered_htlc.clone(), ())],
- &channel_parameters.as_holder_broadcastable()
- );
+ builder.channel_parameters.channel_type_features = ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies();
+ builder.htlcs_with_aux = vec![(received_htlc.clone(), ()), (offered_htlc.clone(), ())];
+ let tx = builder.build(3000, 0);
assert_eq!(tx.built.transaction.output.len(), 5);
assert_eq!(tx.built.transaction.output[2].script_pubkey, get_htlc_redeemscript(&received_htlc, &ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies(), &keys).to_v0_p2wsh());
assert_eq!(tx.built.transaction.output[3].script_pubkey, get_htlc_redeemscript(&offered_htlc, &ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies(), &keys).to_v0_p2wsh());
"002087a3faeb1950a469c0e2db4a79b093a41b9526e5a6fc6ef5cb949bde3be379c7");
}
+ #[test]
+ fn test_finding_revokeable_output_index() {
+ let mut builder = TestCommitmentTxBuilder::new();
+
+ // Revokeable output present
+ let tx = builder.build(1000, 2000);
+ assert_eq!(tx.built.transaction.output.len(), 2);
+ assert_eq!(tx.trust().revokeable_output_index(), Some(0));
+
+ // Revokeable output present (but to_broadcaster_delay missing)
+ let tx = CommitmentTransaction { to_broadcaster_delay: None, ..tx };
+ assert_eq!(tx.built.transaction.output.len(), 2);
+ assert_eq!(tx.trust().revokeable_output_index(), None);
+
+ // Revokeable output not present (our balance is dust)
+ let tx = builder.build(0, 2000);
+ assert_eq!(tx.built.transaction.output.len(), 1);
+ assert_eq!(tx.trust().revokeable_output_index(), None);
+ }
+
+ #[test]
+ fn test_building_to_local_justice_tx() {
+ let mut builder = TestCommitmentTxBuilder::new();
+
+ // Revokeable output not present (our balance is dust)
+ let tx = builder.build(0, 2000);
+ assert_eq!(tx.built.transaction.output.len(), 1);
+ assert!(tx.trust().build_to_local_justice_tx(253, Script::new()).is_err());
+
+ // Revokeable output present
+ let tx = builder.build(1000, 2000);
+ assert_eq!(tx.built.transaction.output.len(), 2);
+
+ // Too high feerate
+ assert!(tx.trust().build_to_local_justice_tx(100_000, Script::new()).is_err());
+
+ // Generate a random public key for destination script
+ let secret_key = SecretKey::from_slice(
+ &hex::decode("1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100")
+ .unwrap()[..]).unwrap();
+ let pubkey_hash = BitcoinPublicKey::new(
+ PublicKey::from_secret_key(&Secp256k1::new(), &secret_key)).wpubkey_hash().unwrap();
+ let destination_script = Script::new_v0_p2wpkh(&pubkey_hash);
+
+ let justice_tx = tx.trust().build_to_local_justice_tx(253, destination_script.clone()).unwrap();
+ assert_eq!(justice_tx.input.len(), 1);
+ assert_eq!(justice_tx.input[0].previous_output.txid, tx.built.transaction.txid());
+ assert_eq!(justice_tx.input[0].previous_output.vout, tx.trust().revokeable_output_index().unwrap() as u32);
+ assert!(justice_tx.input[0].sequence.is_rbf());
+
+ assert_eq!(justice_tx.output.len(), 1);
+ assert!(justice_tx.output[0].value < 1000);
+ assert_eq!(justice_tx.output[0].script_pubkey, destination_script);
+ }
+
#[test]
fn test_per_commitment_storage() {
// Test vectors from BOLT 3: