Skip to content

Commit

Permalink
Tests
Browse files Browse the repository at this point in the history
  • Loading branch information
wvanlint committed Sep 17, 2024
1 parent c9b6f6d commit a2bea40
Show file tree
Hide file tree
Showing 3 changed files with 354 additions and 438 deletions.
257 changes: 149 additions & 108 deletions lightning/src/chain/package.rs
Original file line number Diff line number Diff line change
Expand Up @@ -538,37 +538,6 @@ impl PackageSolvingData {
}
}

/// Checks if this and `other` are spending types of inputs which could have descended from the
/// same commitment transaction(s) and thus could both be spent without requiring a
/// double-spend.
fn is_possibly_from_same_tx_tree(&self, other: &PackageSolvingData) -> bool {
match self {
PackageSolvingData::RevokedOutput(_)|PackageSolvingData::RevokedHTLCOutput(_) => {
match other {
PackageSolvingData::RevokedOutput(_)|
PackageSolvingData::RevokedHTLCOutput(_) => true,
_ => false,
}
},
PackageSolvingData::CounterpartyOfferedHTLCOutput(_)|
PackageSolvingData::CounterpartyReceivedHTLCOutput(_) => {
match other {
PackageSolvingData::CounterpartyOfferedHTLCOutput(_)|
PackageSolvingData::CounterpartyReceivedHTLCOutput(_) => true,
_ => false,
}
},
PackageSolvingData::HolderHTLCOutput(_)|
PackageSolvingData::HolderFundingOutput(_) => {
match other {
PackageSolvingData::HolderHTLCOutput(_)|
PackageSolvingData::HolderFundingOutput(_) => true,
_ => false,
}
},
}
}

fn as_tx_input(&self, previous_output: BitcoinOutPoint) -> TxIn {
let sequence = match self {
PackageSolvingData::RevokedOutput(_) => Sequence::ENABLE_RBF_NO_LOCKTIME,
Expand Down Expand Up @@ -791,28 +760,6 @@ impl PackageTemplate {
return false;
}

// First check the types of the inputs and only merge if they are possibly claiming
// at the same time.
// This shouldn't ever happen, but if we do end up with packages trying to claim
// funds from two different commitment transactions for the same channel,
// (which cannot possibly be on-chain at the same time) we definitely shouldn't merge them.
#[cfg(debug_assertions)] {
for i in 0..self.inputs.len() {
for j in 0..i {
assert!(self.inputs[i].1.is_possibly_from_same_tx_tree(&self.inputs[j].1));
}
}
for i in 0..other.inputs.len() {
for j in 0..i {
assert!(other.inputs[i].1.is_possibly_from_same_tx_tree(&other.inputs[j].1));
}
}
}
if !self.inputs[0].1.is_possibly_from_same_tx_tree(&other.inputs[0].1) {
debug_assert!(false, "We shouldn't have packages from different tx trees");
return false;
}

// Check if the packages have signed locktimes. If they do, we only want to aggregate
// packages with the same, signed locktime.
if self.signed_locktime() != other.signed_locktime() {
Expand Down Expand Up @@ -1258,7 +1205,7 @@ where

#[cfg(test)]
mod tests {
use crate::chain::package::{CounterpartyOfferedHTLCOutput, CounterpartyReceivedHTLCOutput, HolderHTLCOutput, PackageTemplate, PackageSolvingData, RevokedOutput, WEIGHT_REVOKED_OUTPUT, weight_offered_htlc, weight_received_htlc};
use crate::chain::package::{CounterpartyOfferedHTLCOutput, CounterpartyReceivedHTLCOutput, HolderFundingOutput, HolderHTLCOutput, PackageTemplate, PackageSolvingData, RevokedOutput, WEIGHT_REVOKED_OUTPUT, weight_offered_htlc, weight_received_htlc};
use crate::chain::Txid;
use crate::ln::chan_utils::HTLCOutputInCommitment;
use crate::ln::types::{PaymentPreimage, PaymentHash};
Expand Down Expand Up @@ -1287,13 +1234,13 @@ mod tests {
}
}

macro_rules! dumb_counterparty_output {
($secp_ctx: expr, $amt: expr, $opt_anchors: expr) => {
macro_rules! dumb_counterparty_received_output {
($secp_ctx: expr, $amt: expr, $expiry: expr, $opt_anchors: expr) => {
{
let dumb_scalar = SecretKey::from_slice(&<Vec<u8>>::from_hex("0101010101010101010101010101010101010101010101010101010101010101").unwrap()[..]).unwrap();
let dumb_point = PublicKey::from_secret_key(&$secp_ctx, &dumb_scalar);
let hash = PaymentHash([1; 32]);
let htlc = HTLCOutputInCommitment { offered: true, amount_msat: $amt, cltv_expiry: 0, payment_hash: hash, transaction_output_index: None };
let htlc = HTLCOutputInCommitment { offered: true, amount_msat: $amt, cltv_expiry: $expiry, payment_hash: hash, transaction_output_index: None };
PackageSolvingData::CounterpartyReceivedHTLCOutput(CounterpartyReceivedHTLCOutput::build(dumb_point, DelayedPaymentBasepoint::from(dumb_point), HtlcBasepoint::from(dumb_point), htlc, $opt_anchors))
}
}
Expand All @@ -1306,97 +1253,191 @@ mod tests {
let dumb_point = PublicKey::from_secret_key(&$secp_ctx, &dumb_scalar);
let hash = PaymentHash([1; 32]);
let preimage = PaymentPreimage([2;32]);
let htlc = HTLCOutputInCommitment { offered: false, amount_msat: $amt, cltv_expiry: 1000, payment_hash: hash, transaction_output_index: None };
let htlc = HTLCOutputInCommitment { offered: false, amount_msat: $amt, cltv_expiry: 0, payment_hash: hash, transaction_output_index: None };
PackageSolvingData::CounterpartyOfferedHTLCOutput(CounterpartyOfferedHTLCOutput::build(dumb_point, DelayedPaymentBasepoint::from(dumb_point), HtlcBasepoint::from(dumb_point), preimage, htlc, $opt_anchors))
}
}
}

macro_rules! dumb_htlc_output {
() => {
macro_rules! dumb_accepted_htlc_output {
($opt_anchors: expr) => {
{
let preimage = PaymentPreimage([2;32]);
PackageSolvingData::HolderHTLCOutput(HolderHTLCOutput::build_accepted(preimage, 0, ChannelTypeFeatures::only_static_remote_key()))
let features = if $opt_anchors {
ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies()
} else {
ChannelTypeFeatures::only_static_remote_key()
};
PackageSolvingData::HolderHTLCOutput(HolderHTLCOutput::build_accepted(preimage, 0, features))
}
}
}

macro_rules! dumb_offered_htlc_output {
($cltv_expiry: expr, $opt_anchors: expr) => {
{
let features = if $opt_anchors {
ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies()
} else {
ChannelTypeFeatures::only_static_remote_key()
};
PackageSolvingData::HolderHTLCOutput(HolderHTLCOutput::build_offered(0, $cltv_expiry, features))
}
}
}

macro_rules! dumb_funding_output {
() => {
PackageSolvingData::HolderFundingOutput(HolderFundingOutput::build(ScriptBuf::new(), 0, ChannelTypeFeatures::only_static_remote_key()))
}
}

#[test]
#[should_panic]
fn test_package_untractable_merge_to() {
fn test_merge_package_untractable_funding_output() {
let txid = Txid::from_str("c2d4449afa8d26140898dd54d3390b057ba2a5afcf03ba29d7dc0d8b9ffe966e").unwrap();
let secp_ctx = Secp256k1::new();
let revk_outp = dumb_revk_output!(secp_ctx, false);
let htlc_outp = dumb_htlc_output!();
let funding_outp = dumb_funding_output!();

let mut untractable_package = PackageTemplate::build_package(txid, 0, revk_outp.clone(), 1000);
let malleable_package = PackageTemplate::build_package(txid, 1, htlc_outp.clone(), 1000);
untractable_package.merge_package(malleable_package);
let mut untractable_package = PackageTemplate::build_package(txid, 0, funding_outp.clone(), 1000);
let mut malleable_package = PackageTemplate::build_package(txid, 1, revk_outp.clone(), 1000);

assert!(!untractable_package.can_merge_with(&malleable_package, 950));
assert!(untractable_package.merge_package(malleable_package.clone(), 950).is_err());

assert!(!malleable_package.can_merge_with(&untractable_package, 950));
assert!(malleable_package.merge_package(untractable_package.clone(), 950).is_err());
}

#[test]
#[should_panic]
fn test_package_untractable_merge_from() {
fn test_merge_empty_package() {
let txid = Txid::from_str("c2d4449afa8d26140898dd54d3390b057ba2a5afcf03ba29d7dc0d8b9ffe966e").unwrap();
let secp_ctx = Secp256k1::new();
let htlc_outp = dumb_htlc_output!();
let revk_outp = dumb_revk_output!(secp_ctx, false);

let mut malleable_package = PackageTemplate::build_package(txid, 0, htlc_outp.clone(), 1000);
let untractable_package = PackageTemplate::build_package(txid, 1, revk_outp.clone(), 1000);
malleable_package.merge_package(untractable_package);
let mut empty_package = PackageTemplate::build_package(txid, 0, revk_outp.clone(), 1000);
empty_package.inputs = vec![];
let package = PackageTemplate::build_package(txid, 1, revk_outp.clone(), 1000);
assert!(empty_package.merge_package(package, 950).is_err());
}

#[test]
#[should_panic]
fn test_package_noaggregation_to() {
fn test_merge_package_with_empty() {
let txid = Txid::from_str("c2d4449afa8d26140898dd54d3390b057ba2a5afcf03ba29d7dc0d8b9ffe966e").unwrap();
let secp_ctx = Secp256k1::new();
let revk_outp = dumb_revk_output!(secp_ctx, false);
let revk_outp_counterparty_balance = dumb_revk_output!(secp_ctx, true);

let mut noaggregation_package = PackageTemplate::build_package(txid, 0, revk_outp_counterparty_balance.clone(), 1000);
let aggregation_package = PackageTemplate::build_package(txid, 1, revk_outp.clone(), 1000);
noaggregation_package.merge_package(aggregation_package);
let mut empty_package = PackageTemplate::build_package(txid, 0, revk_outp.clone(), 1000);
empty_package.inputs = vec![];
let mut package = PackageTemplate::build_package(txid, 1, revk_outp.clone(), 1000);
assert!(package.merge_package(empty_package, 950).is_err());
}

#[test]
#[should_panic]
fn test_package_noaggregation_from() {
fn test_merge_package_different_signed_locktimes() {
// Malleable HTLC transactions are signed over the locktime, and can't be aggregated with
// different locktimes.
let txid = Txid::from_str("c2d4449afa8d26140898dd54d3390b057ba2a5afcf03ba29d7dc0d8b9ffe966e").unwrap();
let secp_ctx = Secp256k1::new();
let revk_outp = dumb_revk_output!(secp_ctx, false);
let revk_outp_counterparty_balance = dumb_revk_output!(secp_ctx, true);
let funding_outp = dumb_funding_output!();
let offered_htlc_1 = dumb_offered_htlc_output!(900, true);
let offered_htlc_2 = dumb_offered_htlc_output!(901, true);
let accepted_htlc = dumb_accepted_htlc_output!(true);

let mut funding_outp_package = PackageTemplate::build_package(txid, 0, funding_outp.clone(), 0);
let mut offered_htlc_1_package = PackageTemplate::build_package(txid, 1, offered_htlc_1.clone(), 0);
let mut offered_htlc_2_package = PackageTemplate::build_package(txid, 2, offered_htlc_2.clone(), 0);
let mut accepted_htlc_package = PackageTemplate::build_package(txid, 3, accepted_htlc.clone(), 1000);

assert!(!funding_outp_package.can_merge_with(&offered_htlc_1_package, 950));
assert!(funding_outp_package.merge_package(offered_htlc_1_package.clone(), 950).is_err());
assert!(!offered_htlc_1_package.can_merge_with(&funding_outp_package, 950));
assert!(offered_htlc_1_package.merge_package(funding_outp_package.clone(), 950).is_err());

let mut aggregation_package = PackageTemplate::build_package(txid, 0, revk_outp.clone(), 1000);
let noaggregation_package = PackageTemplate::build_package(txid, 1, revk_outp_counterparty_balance.clone(), 1000);
aggregation_package.merge_package(noaggregation_package);
assert!(!offered_htlc_2_package.can_merge_with(&offered_htlc_1_package, 950));
assert!(offered_htlc_2_package.merge_package(offered_htlc_1_package.clone(), 950).is_err());
assert!(!offered_htlc_1_package.can_merge_with(&offered_htlc_2_package, 950));
assert!(offered_htlc_1_package.merge_package(offered_htlc_2_package.clone(), 950).is_err());

assert!(!accepted_htlc_package.can_merge_with(&offered_htlc_1_package, 950));
assert!(accepted_htlc_package.merge_package(offered_htlc_1_package.clone(), 950).is_err());
assert!(!offered_htlc_1_package.can_merge_with(&accepted_htlc_package, 950));
assert!(offered_htlc_1_package.merge_package(accepted_htlc_package.clone(), 950).is_err());
}

#[test]
#[should_panic]
fn test_package_empty() {
fn test_merge_package_different_effective_locktimes() {
// Spends of outputs can have different minimum locktimes, and are not mergeable if they are in the
// future.
let txid = Txid::from_str("c2d4449afa8d26140898dd54d3390b057ba2a5afcf03ba29d7dc0d8b9ffe966e").unwrap();
let secp_ctx = Secp256k1::new();
let revk_outp = dumb_revk_output!(secp_ctx, false);
let secp_ctx = Secp256k1::new();

let mut empty_package = PackageTemplate::build_package(txid, 0, revk_outp.clone(), 1000);
empty_package.inputs = vec![];
let package = PackageTemplate::build_package(txid, 1, revk_outp.clone(), 1000);
empty_package.merge_package(package);
let old_outp_1 = dumb_counterparty_received_output!(secp_ctx, 1_000_000, 900, ChannelTypeFeatures::only_static_remote_key());
let old_outp_2 = dumb_counterparty_received_output!(secp_ctx, 1_000_000, 901, ChannelTypeFeatures::only_static_remote_key());
let new_outp_1 = dumb_counterparty_received_output!(secp_ctx, 1_000_000, 1000, ChannelTypeFeatures::only_static_remote_key());
let new_outp_2 = dumb_counterparty_received_output!(secp_ctx, 1_000_000, 1001, ChannelTypeFeatures::only_static_remote_key());

let mut old_outp_1_package = PackageTemplate::build_package(txid, 0, old_outp_1.clone(), 0);
let mut old_outp_2_package = PackageTemplate::build_package(txid, 1, old_outp_2.clone(), 0);
let mut new_outp_1_package = PackageTemplate::build_package(txid, 2, new_outp_1.clone(), 0);
let mut new_outp_2_package = PackageTemplate::build_package(txid, 3, new_outp_2.clone(), 0);

assert!(old_outp_1_package.can_merge_with(&old_outp_2_package, 950));
assert!(old_outp_2_package.can_merge_with(&old_outp_1_package, 950));
assert!(old_outp_1_package.merge_package(old_outp_2_package.clone(), 950).is_ok());

assert!(!new_outp_1_package.can_merge_with(&new_outp_2_package, 950));
assert!(!new_outp_2_package.can_merge_with(&new_outp_1_package, 950));
assert!(new_outp_1_package.merge_package(new_outp_2_package.clone(), 950).is_err());
assert!(new_outp_2_package.merge_package(new_outp_1_package.clone(), 950).is_err());
}

#[test]
#[should_panic]
fn test_package_differing_categories() {
fn test_merge_package_holder_htlcs() {
// Pinnable and unpinnable malleable packages are kept separate. A package is considered
// unpinnable if it can only be claimed by the counterparty a given amount of time in the
// future.
let txid = Txid::from_str("c2d4449afa8d26140898dd54d3390b057ba2a5afcf03ba29d7dc0d8b9ffe966e").unwrap();
let secp_ctx = Secp256k1::new();
let revk_outp = dumb_revk_output!(secp_ctx, false);
let counterparty_outp = dumb_counterparty_output!(secp_ctx, 0, ChannelTypeFeatures::only_static_remote_key());

let mut revoked_package = PackageTemplate::build_package(txid, 0, revk_outp, 1000);
let counterparty_package = PackageTemplate::build_package(txid, 1, counterparty_outp, 1000);
revoked_package.merge_package(counterparty_package);
let secp_ctx = Secp256k1::new();

// Signed locktimes of 0.
let unpinnable_1 = dumb_accepted_htlc_output!(true);
let unpinnable_2 = dumb_accepted_htlc_output!(true);
let considered_pinnable = dumb_accepted_htlc_output!(true);
// Signed locktimes of 950.
let pinnable_1 = dumb_offered_htlc_output!(950, true);
let pinnable_2 = dumb_offered_htlc_output!(950, true);

let mut unpinnable_1_package = PackageTemplate::build_package(txid, 0, unpinnable_1.clone(), 1000);
let mut unpinnable_2_package = PackageTemplate::build_package(txid, 1, unpinnable_2.clone(), 1001);
let mut considered_pinnable_package = PackageTemplate::build_package(txid, 2, considered_pinnable.clone(), 951);
let mut pinnable_1_package = PackageTemplate::build_package(txid, 3, pinnable_1.clone(), 0);
let mut pinnable_2_package = PackageTemplate::build_package(txid, 4, pinnable_2.clone(), 0);

// Unpinnable with signed locktimes of 0.
let unpinnable_cluster = [&mut unpinnable_1_package, &mut unpinnable_2_package];
// Pinnables with signed locktime of 950.
let pinnable_cluster = [&mut pinnable_1_package, &mut pinnable_2_package];
// Pinnable with signed locktime of 0.
let considered_pinnable_cluster = [&mut considered_pinnable_package];
let clusters = [unpinnable_cluster.as_slice(), pinnable_cluster.as_slice(), considered_pinnable_cluster.as_slice()];

for a in 0..clusters.len() {
for b in 0..clusters.len() {
for i in 0..clusters[a].len() {
for j in 0..clusters[b].len() {
if a != b {
assert!(!clusters[a][i].can_merge_with(clusters[b][j], 950));
} else {
if i != j {
assert!(clusters[a][i].can_merge_with(clusters[b][j], 950));
}
}
}
}
}
}
}

#[test]
Expand All @@ -1411,8 +1452,8 @@ mod tests {
let package_two = PackageTemplate::build_package(txid, 1, revk_outp_two, 1000);
let package_three = PackageTemplate::build_package(txid, 2, revk_outp_three, 1000);

package_one.merge_package(package_two);
package_one.merge_package(package_three);
assert!(package_one.merge_package(package_two, 950).is_ok());
assert!(package_one.merge_package(package_three, 950).is_ok());
assert_eq!(package_one.outpoints().len(), 3);

if let Some(split_package) = package_one.split_package(&BitcoinOutPoint { txid, vout: 1 }) {
Expand All @@ -1428,10 +1469,10 @@ mod tests {
#[test]
fn test_package_split_untractable() {
let txid = Txid::from_str("c2d4449afa8d26140898dd54d3390b057ba2a5afcf03ba29d7dc0d8b9ffe966e").unwrap();
let htlc_outp_one = dumb_htlc_output!();
let htlc_outp_one = dumb_accepted_htlc_output!(false);

let mut package_one = PackageTemplate::build_package(txid, 0, htlc_outp_one, 1000);
let ret_split = package_one.split_package(&BitcoinOutPoint { txid, vout: 0});
let ret_split = package_one.split_package(&BitcoinOutPoint { txid, vout: 0 });
assert!(ret_split.is_none());
}

Expand All @@ -1451,7 +1492,7 @@ mod tests {
fn test_package_amounts() {
let txid = Txid::from_str("c2d4449afa8d26140898dd54d3390b057ba2a5afcf03ba29d7dc0d8b9ffe966e").unwrap();
let secp_ctx = Secp256k1::new();
let counterparty_outp = dumb_counterparty_output!(secp_ctx, 1_000_000, ChannelTypeFeatures::only_static_remote_key());
let counterparty_outp = dumb_counterparty_received_output!(secp_ctx, 1_000_000, 1000, ChannelTypeFeatures::only_static_remote_key());

let package = PackageTemplate::build_package(txid, 0, counterparty_outp, 1000);
assert_eq!(package.package_amount(), 1000);
Expand All @@ -1473,7 +1514,7 @@ mod tests {

{
for channel_type_features in [ChannelTypeFeatures::only_static_remote_key(), ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies()].iter() {
let counterparty_outp = dumb_counterparty_output!(secp_ctx, 1_000_000, channel_type_features.clone());
let counterparty_outp = dumb_counterparty_received_output!(secp_ctx, 1_000_000, 1000, channel_type_features.clone());
let package = PackageTemplate::build_package(txid, 0, counterparty_outp, 1000);
assert_eq!(package.package_weight(&ScriptBuf::new()), weight_sans_output + weight_received_htlc(channel_type_features));
}
Expand Down
Loading

0 comments on commit a2bea40

Please sign in to comment.