#[cfg(test)]
mod tests {
use std::mem;
use bitcoin::bip32::DerivationPath;
use bitcoin::hashes::hex::FromHex;
use bitcoin::{self, Address, Amount, Network, OutPoint, ScriptBuf, Transaction, TxOut};
use lightning::ln::chan_utils::{
make_funding_redeemscript, ChannelPublicKeys, ClosingTransaction,
};
use lightning::types::payment::PaymentHash;
use test_log::test;
use vls_common::to_derivation_path;
use crate::channel::{Channel, ChannelBase, ChannelId, ChannelSetup, TypedSignature};
use crate::node::{Node, NodeMonitor};
use crate::prelude::Arc;
use crate::tx::tx::{CommitmentInfo2, HTLCInfo2};
use crate::util::status::{Code, Status};
use crate::util::test_utils::key::*;
use crate::util::test_utils::*;
use crate::util::transaction_utils::mutual_close_tx_weight;
macro_rules! hex (($hex:expr) => (Vec::from_hex($hex).unwrap()));
macro_rules! hex_script (($hex:expr) => (ScriptBuf::from(hex!($hex))));
fn setup_mutual_close_tx(
outbound: bool,
) -> Result<
(ChannelSetup, Arc<Node>, ChannelId, u64, u64, u64, DerivationPath, ChannelPublicKeys),
Status,
> {
let mut setup = make_test_channel_setup();
setup.is_outbound = outbound;
let (node, channel_id) =
init_node_and_channel(TEST_NODE_CONFIG, TEST_SEED[1], setup.clone());
let counterparty_points = make_test_counterparty_points();
let holder_commit_num = 22;
let counterparty_commit_num = 43;
let holder_wallet_path_hint = to_derivation_path(&[7u32]);
let feerate_per_kw = 7500;
let fee = 2000;
let mut to_holder_value_sat = 2_000_000;
let mut to_counterparty_value_sat = setup.channel_value_sat - to_holder_value_sat; if outbound {
to_holder_value_sat -= fee;
} else {
to_counterparty_value_sat -= fee;
};
node.with_channel(&channel_id, |chan| {
let estate = &mut chan.enforcement_state;
estate.next_holder_commit_num = holder_commit_num + 1;
estate.next_counterparty_commit_num = counterparty_commit_num + 1;
estate.next_counterparty_revoke_num = counterparty_commit_num;
estate.current_counterparty_point =
Some(make_test_pubkey(counterparty_commit_num as u8));
estate.previous_counterparty_point = None;
estate.current_holder_commit_info = Some(CommitmentInfo2 {
is_counterparty_broadcaster: false,
to_countersigner_value_sat: to_counterparty_value_sat,
to_broadcaster_value_sat: to_holder_value_sat,
offered_htlcs: vec![],
received_htlcs: vec![],
feerate_per_kw,
});
estate.current_counterparty_commit_info = Some(CommitmentInfo2 {
is_counterparty_broadcaster: true,
to_countersigner_value_sat: to_holder_value_sat,
to_broadcaster_value_sat: to_counterparty_value_sat,
offered_htlcs: vec![],
received_htlcs: vec![],
feerate_per_kw,
});
estate.previous_counterparty_commit_info = None;
estate.channel_closed = false;
Ok(())
})
.expect("state setup");
Ok((
setup,
node,
channel_id,
holder_commit_num,
to_holder_value_sat,
to_counterparty_value_sat,
holder_wallet_path_hint,
counterparty_points,
))
}
fn sign_mutual_close_tx_with_mutators<
MutualCloseInputMutator,
MutualCloseTxMutator,
ChannelStateValidator,
>(
outbound: bool,
mutate_close_input: MutualCloseInputMutator,
mutate_close_tx: MutualCloseTxMutator,
validate_channel_state: ChannelStateValidator,
) -> Result<(), Status>
where
MutualCloseInputMutator:
Fn(&mut Channel, &mut u64, &mut u64, &mut ScriptBuf, &mut ScriptBuf, &mut OutPoint),
MutualCloseTxMutator: Fn(&mut Transaction, &mut Vec<DerivationPath>, &mut Vec<String>),
ChannelStateValidator: Fn(&Channel),
{
let (
setup,
node,
channel_id,
holder_commit_num,
to_holder_value_sat,
to_counterparty_value_sat,
holder_wallet_path_hint,
counterparty_points,
) = setup_mutual_close_tx(outbound)?;
assert_eq!(
node.channel_balance(),
ChannelBalanceBuilder::new().claimable(2_000_000).unconfirmed_count(1).build()
);
let (tx, sig) = node.with_channel(&channel_id, |chan| {
let mut holder_value_sat = to_holder_value_sat;
let mut counterparty_value_sat = to_counterparty_value_sat;
let mut holder_shutdown_script = Address::p2wpkh(
&node.get_wallet_pubkey(&holder_wallet_path_hint).unwrap(),
Network::Testnet,
)
.script_pubkey();
let mut counterparty_shutdown_script =
ScriptBuf::from_hex("0014be56df7de366ad8ee9ccdad54e9a9993e99ef565")
.expect("script_pubkey");
let mut funding_outpoint = setup.funding_outpoint;
mutate_close_input(
chan,
&mut holder_value_sat,
&mut counterparty_value_sat,
&mut holder_shutdown_script,
&mut counterparty_shutdown_script,
&mut funding_outpoint,
);
let closing_tx = ClosingTransaction::new(
holder_value_sat,
counterparty_value_sat,
holder_shutdown_script,
counterparty_shutdown_script,
funding_outpoint,
);
let trusted = closing_tx.trust();
let tx = trusted.built_transaction();
assert!(chan.get_per_commitment_secret(holder_commit_num - 1).is_ok());
let mut mtx = tx.clone();
let mut wallet_paths = vec![DerivationPath::master(), holder_wallet_path_hint];
let mut allowlist = vec![];
mutate_close_tx(&mut mtx, &mut wallet_paths, &mut allowlist);
node.add_allowlist(&allowlist)?;
let deferred_rv = chan.sign_mutual_close_tx(&mtx, &wallet_paths);
validate_channel_state(chan);
let sig = deferred_rv?;
Ok((tx.clone(), sig))
})?;
let funding_pubkey = get_channel_funding_pubkey(&node, &channel_id);
let channel_funding_redeemscript =
make_funding_redeemscript(&funding_pubkey, &counterparty_points.funding_pubkey);
check_signature(
&tx,
0,
TypedSignature::all(sig),
&funding_pubkey,
setup.channel_value_sat,
&channel_funding_redeemscript,
);
assert_status_ok!(node.with_channel(&channel_id, |chan| {
chan.get_per_commitment_secret(holder_commit_num - 1)
}));
assert_status_ok!(node.with_channel(&channel_id, |chan| {
assert_eq!(chan.enforcement_state.channel_closed, true);
Ok(())
}));
Ok(())
}
fn sign_mutual_close_tx_phase2_with_mutators<MutualCloseInput2Mutator, ChannelStateValidator>(
outbound: bool,
mutate_close_input: MutualCloseInput2Mutator,
validate_channel_state: ChannelStateValidator,
) -> Result<(), Status>
where
MutualCloseInput2Mutator: Fn(
&mut Channel,
&mut u64,
&mut u64,
&mut ScriptBuf,
&mut ScriptBuf,
&mut OutPoint,
&mut DerivationPath,
&mut Vec<String>,
),
ChannelStateValidator: Fn(&Channel),
{
let (
setup,
node,
channel_id,
holder_commit_num,
to_holder_value_sat,
to_counterparty_value_sat,
mut init_holder_wallet_path_hint,
counterparty_points,
) = setup_mutual_close_tx(outbound)?;
let (
holder_value_sat,
counterparty_value_sat,
holder_shutdown_script,
counterparty_shutdown_script,
funding_outpoint,
sig,
) = node.with_channel(&channel_id, |chan| {
let mut holder_value_sat = to_holder_value_sat;
let mut counterparty_value_sat = to_counterparty_value_sat;
let mut holder_shutdown_script = Address::p2wpkh(
&node.get_wallet_pubkey(&init_holder_wallet_path_hint).unwrap(),
Network::Testnet,
)
.script_pubkey();
let mut counterparty_shutdown_script =
ScriptBuf::from_hex("0014be56df7de366ad8ee9ccdad54e9a9993e99ef565")
.expect("script_pubkey");
let mut funding_outpoint = setup.funding_outpoint;
assert!(chan.get_per_commitment_secret(holder_commit_num - 1).is_ok());
let mut allowlist = vec![];
mutate_close_input(
chan,
&mut holder_value_sat,
&mut counterparty_value_sat,
&mut holder_shutdown_script,
&mut counterparty_shutdown_script,
&mut funding_outpoint,
&mut init_holder_wallet_path_hint,
&mut allowlist,
);
node.add_allowlist(&allowlist)?;
let deferred_rv = chan.sign_mutual_close_tx_phase2(
holder_value_sat,
counterparty_value_sat,
&Some(holder_shutdown_script.clone()),
&Some(counterparty_shutdown_script.clone()),
&init_holder_wallet_path_hint,
);
validate_channel_state(chan);
let sig = deferred_rv?;
Ok((
holder_value_sat,
counterparty_value_sat,
holder_shutdown_script,
counterparty_shutdown_script,
funding_outpoint,
sig,
))
})?;
let closing_tx = ClosingTransaction::new(
holder_value_sat,
counterparty_value_sat,
holder_shutdown_script,
counterparty_shutdown_script,
funding_outpoint,
);
let trusted = closing_tx.trust();
let tx = trusted.built_transaction();
let funding_pubkey = get_channel_funding_pubkey(&node, &channel_id);
let channel_funding_redeemscript =
make_funding_redeemscript(&funding_pubkey, &counterparty_points.funding_pubkey);
check_signature(
&tx,
0,
TypedSignature::all(sig),
&funding_pubkey,
setup.channel_value_sat,
&channel_funding_redeemscript,
);
assert_status_ok!(node.with_channel(&channel_id, |chan| {
chan.get_per_commitment_secret(holder_commit_num - 1)
}));
assert_status_ok!(node.with_channel(&channel_id, |chan| {
assert_eq!(chan.enforcement_state.channel_closed, true);
Ok(())
}));
Ok(())
}
macro_rules! sign_mutual_close_tx_with_mutators_inbound {
($mci: expr, $mct: expr, $vcs: expr) => {
sign_mutual_close_tx_with_mutators(false, $mci, $mct, $vcs)
};
}
macro_rules! sign_mutual_close_tx_with_mutators_outbound {
($mci: expr, $mct: expr, $vcs: expr) => {
sign_mutual_close_tx_with_mutators(true, $mci, $mct, $vcs)
};
}
macro_rules! sign_mutual_close_tx_phase2_with_mutators_inbound {
($mci: expr, $vcs: expr) => {
sign_mutual_close_tx_phase2_with_mutators(false, $mci, $vcs)
};
}
macro_rules! sign_mutual_close_tx_phase2_with_mutators_outbound {
($mci: expr, $vcs: expr) => {
sign_mutual_close_tx_phase2_with_mutators(true, $mci, $vcs)
};
}
#[test]
fn sign_mutual_close_tx_phase2_success() {
assert_status_ok!(sign_mutual_close_tx_phase2_with_mutators_outbound!(
|_chan,
_to_holder,
_to_counterparty,
_holder_script,
_counter_script,
_outpoint,
_wallet_path,
_allowlist| {
},
|chan| {
assert_eq!(chan.enforcement_state.channel_closed, true);
}
));
}
#[test]
fn sign_mutual_close_tx_success() {
assert_status_ok!(sign_mutual_close_tx_with_mutators_outbound!(
|_chan, _to_holder, _to_counterparty, _holder_script, _counter_script, _outpoint| {
},
|_tx, _wallet_paths, _allowlist| {
},
|chan| {
assert_eq!(chan.enforcement_state.channel_closed, true);
}
));
}
#[test]
fn sign_mutual_close_tx_phase2_inbound_success() {
assert_status_ok!(sign_mutual_close_tx_phase2_with_mutators_inbound!(
|_chan,
_to_holder,
_to_counterparty,
_holder_script,
_counter_script,
_outpoint,
_wallet_path,
_allowlist| {
},
|chan| {
assert_eq!(chan.enforcement_state.channel_closed, true);
}
));
}
#[test]
fn sign_mutual_close_tx_inbound_success() {
assert_status_ok!(sign_mutual_close_tx_with_mutators_inbound!(
|_chan, _to_holder, _to_counterparty, _holder_script, _counter_script, _outpoint| {
},
|_tx, _wallet_paths, _allowlist| {
},
|chan| {
assert_eq!(chan.enforcement_state.channel_closed, true);
}
));
}
#[test]
fn sign_mutual_close_tx_only_holder_success() {
assert_status_ok!(sign_mutual_close_tx_with_mutators_outbound!(
|chan, to_holder, to_counterparty, _holder_script, counter_script, _outpoint| {
let mut holder =
chan.enforcement_state.current_holder_commit_info.as_ref().unwrap().clone();
holder.to_broadcaster_value_sat += holder.to_countersigner_value_sat;
holder.to_countersigner_value_sat = 0;
chan.enforcement_state.current_holder_commit_info = Some(holder);
let mut cparty = chan
.enforcement_state
.current_counterparty_commit_info
.as_ref()
.unwrap()
.clone();
cparty.to_countersigner_value_sat += cparty.to_broadcaster_value_sat;
cparty.to_broadcaster_value_sat = 0;
chan.enforcement_state.current_counterparty_commit_info = Some(cparty);
*to_holder += *to_counterparty;
*to_counterparty = 0;
*counter_script = ScriptBuf::new();
},
|_tx, wallet_paths, _allowlist| {
wallet_paths[0] = wallet_paths[1].clone();
wallet_paths.pop();
},
|chan| {
assert_eq!(chan.enforcement_state.channel_closed, true);
}
));
}
#[test]
fn sign_mutual_close_tx_only_counterparty_success() {
assert_status_ok!(sign_mutual_close_tx_with_mutators_outbound!(
|chan, to_holder, to_counterparty, holder_script, _counter_script, _outpoint| {
let fee = 2000;
let mut holder =
chan.enforcement_state.current_holder_commit_info.as_ref().unwrap().clone();
holder.to_countersigner_value_sat += holder.to_broadcaster_value_sat - fee;
holder.to_broadcaster_value_sat = 0;
chan.enforcement_state.current_holder_commit_info = Some(holder);
let mut cparty = chan
.enforcement_state
.current_counterparty_commit_info
.as_ref()
.unwrap()
.clone();
cparty.to_broadcaster_value_sat += cparty.to_countersigner_value_sat - fee;
cparty.to_countersigner_value_sat = 0;
chan.enforcement_state.current_counterparty_commit_info = Some(cparty);
*to_counterparty += *to_holder - fee;
*to_holder = 0;
*holder_script = ScriptBuf::new();
},
|_tx, wallet_paths, _allowlist| {
wallet_paths.pop();
},
|chan| {
assert_eq!(chan.enforcement_state.channel_closed, true);
}
));
}
#[test]
fn sign_mutual_close_tx_catch_allowlist_bad_assign_success() {
assert_status_ok!(sign_mutual_close_tx_with_mutators_outbound!(
|chan, to_holder, to_counterparty, holder_script, counter_script, _outpoint| {
let fee = 2000;
let mut holder =
chan.enforcement_state.current_holder_commit_info.as_ref().unwrap().clone();
holder.to_countersigner_value_sat += holder.to_broadcaster_value_sat - fee;
holder.to_broadcaster_value_sat = 0;
chan.enforcement_state.current_holder_commit_info = Some(holder);
let mut cparty = chan
.enforcement_state
.current_counterparty_commit_info
.as_ref()
.unwrap()
.clone();
cparty.to_broadcaster_value_sat += cparty.to_countersigner_value_sat - fee;
cparty.to_countersigner_value_sat = 0;
chan.enforcement_state.current_counterparty_commit_info = Some(cparty);
*to_counterparty += *to_holder - fee;
*to_holder = 0;
*holder_script = ScriptBuf::new();
*counter_script = hex_script!("0014be56df7de366ad8ee9ccdad54e9a9993e99ef565");
},
|_tx, wallet_paths, allowlist| {
wallet_paths.pop();
wallet_paths.pop();
wallet_paths.push(DerivationPath::master());
allowlist.push("tb1qhetd7l0rv6kca6wvmt25ax5ej05eaat9q29z7z".to_string());
},
|chan| {
assert_eq!(chan.enforcement_state.channel_closed, true);
}
));
}
#[test]
fn sign_mutual_close_tx_with_allowlist_success() {
assert_status_ok!(sign_mutual_close_tx_with_mutators_outbound!(
|_chan, _to_holder, _to_counterparty, _holder_script, _counter_script, _outpoint| {
},
|_tx, wallet_paths, allowlist| {
wallet_paths.pop();
wallet_paths.pop();
wallet_paths.push(DerivationPath::master());
wallet_paths.push(DerivationPath::master());
allowlist.push("tb1qkakav8jpkhhs22hjrndrycyg3srshwd09gax07".to_string());
},
|chan| {
assert_eq!(chan.enforcement_state.channel_closed, true);
}
));
}
#[test]
fn sign_mutual_close_tx_phase2_with_allowlist_success() {
assert_status_ok!(sign_mutual_close_tx_phase2_with_mutators_outbound!(
|_chan,
_to_holder,
_to_counterparty,
_holder_script,
_counter_script,
_outpoint,
wallet_path,
allowlist| {
*wallet_path = DerivationPath::master();
allowlist.push("tb1qkakav8jpkhhs22hjrndrycyg3srshwd09gax07".to_string());
},
|chan| {
assert_eq!(chan.enforcement_state.channel_closed, true);
}
));
}
#[test]
fn sign_mutual_close_tx_phase2_no_wallet_path_or_allowlist() {
assert_failed_precondition_err!(
sign_mutual_close_tx_phase2_with_mutators_outbound!(
|_chan,
_to_holder,
_to_counterparty,
_holder_script,
_counter_script,
_outpoint,
wallet_path,
_allowlist| {
*wallet_path = DerivationPath::master();
},
|chan| {
assert_eq!(chan.enforcement_state.channel_closed, false);
}
),
"policy failure: validate_mutual_close_tx: holder output not to wallet or in allowlist"
);
}
#[test]
fn sign_mutual_close_tx_phase2_holder_upfront_script_mismatch() {
assert_failed_precondition_err!(
sign_mutual_close_tx_phase2_with_mutators_outbound!(
|chan,
_to_holder,
_to_counterparty,
holder_script,
_counter_script,
_outpoint,
_wallet_path,
_allowlist| {
chan.setup.holder_shutdown_script =
Some(hex_script!("0014b76dd61e41b5ef052af21cda3260888c070bb9af"));
*holder_script =
hex_script!("76a9149f9a7abd600c0caa03983a77c8c3df8e062cb2fa88ac");
},
|chan| {
assert_eq!(chan.enforcement_state.channel_closed, false);
}
),
"policy failure: validate_mutual_close_tx: \
holder_script doesn't match upfront holder_shutdown_script"
);
}
#[test]
fn sign_mutual_close_tx_phase2_with_fee_too_large() {
assert_failed_precondition_err!(
sign_mutual_close_tx_phase2_with_mutators_outbound!(
|_chan,
to_holder,
to_counterparty,
_holder_script,
_counter_script,
_outpoint,
_wallet_path,
_allowlist| {
*to_holder -= 120_000;
*to_counterparty -= 120_000;
},
|chan| {
assert_eq!(chan.enforcement_state.channel_closed, false);
}
),
"policy failure: validate_mutual_close_tx: \
validate_fee: feerate above maximum: 359051 > 333333"
);
}
#[test]
fn sign_mutual_close_tx_phase2_with_fee_too_small() {
assert_failed_precondition_err!(
sign_mutual_close_tx_phase2_with_mutators_outbound!(
|_chan,
to_holder,
to_counterparty,
_holder_script,
_counter_script,
_outpoint,
_wallet_path,
_allowlist| {
*to_holder += 1_000;
*to_counterparty += 950;
},
|chan| {
assert_eq!(chan.enforcement_state.channel_closed, false);
}
),
"policy failure: validate_mutual_close_tx: validate_fee: \
feerate below minimum: 75 < 253"
);
}
#[test]
fn sign_mutual_close_tx_with_bad_num_txout() {
assert_failed_precondition_err!(
sign_mutual_close_tx_with_mutators_outbound!(
|_chan,
_to_holder,
_to_counterparty,
_holder_script,
_counter_script,
_outpoint| {
},
|tx, wallet_paths, _allowlist| {
let steal_amt = Amount::from_sat(1_000);
tx.output[0].value -= steal_amt;
tx.output.push(TxOut {
value: steal_amt,
script_pubkey: hex_script!(
"76a9149f9a7abd600c0caa03983a77c8c3df8e062cb2fa88ac"
),
});
wallet_paths.push(DerivationPath::master());
},
|chan| {
assert_eq!(chan.enforcement_state.channel_closed, false);
}
),
"transaction format: decode_and_validate_mutual_close_tx: invalid number of outputs: 3"
);
}
#[test]
fn sign_mutual_close_tx_with_opath_len_mismatch() {
assert_invalid_argument_err!(
sign_mutual_close_tx_with_mutators_outbound!(
|_chan,
_to_holder,
_to_counterparty,
_holder_script,
_counter_script,
_outpoint| {
},
|_tx, wallet_paths, _allowlist| {
wallet_paths.push(DerivationPath::master());
},
|chan| {
assert_eq!(chan.enforcement_state.channel_closed, false);
}
),
"sign_mutual_close_tx: bad opath len 3 with tx.output len 2"
);
}
#[test]
fn sign_mutual_close_tx_with_unestablished_holder() {
assert_failed_precondition_err!(
sign_mutual_close_tx_with_mutators_outbound!(
|_chan,
_to_holder,
_to_counterparty,
_holder_script,
_counter_script,
_outpoint| {
},
|_tx, wallet_paths, _allowlist| {
wallet_paths.pop();
wallet_paths.pop();
wallet_paths.push(vec![].into());
wallet_paths.push(vec![].into());
},
|chan| {
assert_eq!(chan.enforcement_state.channel_closed, false);
}
),
"policy failure: validate_mutual_close_tx: holder output not to wallet or in allowlist"
);
}
#[test]
fn sign_mutual_close_tx_with_ambiguous_holder_output() {
assert_status_ok!(sign_mutual_close_tx_with_mutators_outbound!(
|chan, to_holder, to_counterparty, _holder_script, _counter_script, _outpoint| {
mem::swap(to_holder, to_counterparty);
let mut hinfo =
chan.enforcement_state.current_holder_commit_info.as_ref().unwrap().clone();
mem::swap(
&mut hinfo.to_broadcaster_value_sat,
&mut hinfo.to_countersigner_value_sat,
);
chan.enforcement_state.current_holder_commit_info = Some(hinfo);
let mut cinfo = chan
.enforcement_state
.current_counterparty_commit_info
.as_ref()
.unwrap()
.clone();
mem::swap(
&mut cinfo.to_broadcaster_value_sat,
&mut cinfo.to_countersigner_value_sat,
);
chan.enforcement_state.current_counterparty_commit_info = Some(cinfo);
},
|_tx, wallet_paths, allowlist| {
wallet_paths.pop();
wallet_paths.pop();
wallet_paths.push(DerivationPath::master());
wallet_paths.push(DerivationPath::master());
allowlist.push("tb1qhetd7l0rv6kca6wvmt25ax5ej05eaat9q29z7z".to_string());
allowlist.push("tb1qkakav8jpkhhs22hjrndrycyg3srshwd09gax07".to_string());
},
|chan| {
assert_eq!(chan.enforcement_state.channel_closed, true);
}
));
}
#[test]
fn sign_mutual_close_tx_without_holder_commitment() {
assert_failed_precondition_err!(
sign_mutual_close_tx_with_mutators_outbound!(
|chan, _to_holder, _to_counterparty, _holder_script, _counter_script, _outpoint| {
chan.enforcement_state.current_holder_commit_info = None;
},
|_tx, _wallet_paths, _allowlist| {
},
|chan| {
assert_eq!(chan.enforcement_state.channel_closed, false);
}
),
"policy failure: decode_and_validate_mutual_close_tx: \
current_holder_commit_info missing"
);
}
#[test]
fn sign_mutual_close_tx_without_counterparty_commitment() {
assert_failed_precondition_err!(
sign_mutual_close_tx_with_mutators_outbound!(
|chan, _to_holder, _to_counterparty, _holder_script, _counter_script, _outpoint| {
chan.enforcement_state.current_counterparty_commit_info = None;
},
|_tx, _wallet_paths, _allowlist| {
},
|chan| {
assert_eq!(chan.enforcement_state.channel_closed, false);
}
),
"policy failure: decode_and_validate_mutual_close_tx: \
current_counterparty_commit_info missing"
);
}
#[test]
fn sign_mutual_close_tx_with_holder_offered_htlcs() {
assert_failed_precondition_err!(
sign_mutual_close_tx_with_mutators_outbound!(
|chan, _to_holder, _to_counterparty, _holder_script, _counter_script, _outpoint| {
let mut holder =
chan.enforcement_state.current_holder_commit_info.as_ref().unwrap().clone();
holder.offered_htlcs.push(HTLCInfo2 {
value_sat: 1,
payment_hash: PaymentHash([1; 32]),
cltv_expiry: 2 << 16,
});
chan.enforcement_state.current_holder_commit_info = Some(holder);
},
|_tx, _wallet_paths, _allowlist| {
},
|chan| {
assert_eq!(chan.enforcement_state.channel_closed, false);
}
),
"policy failure: validate_mutual_close_tx: cannot close with pending htlcs"
);
}
#[test]
fn sign_mutual_close_tx_with_holder_received_htlcs() {
assert_failed_precondition_err!(
sign_mutual_close_tx_with_mutators_outbound!(
|chan, _to_holder, _to_counterparty, _holder_script, _counter_script, _outpoint| {
let mut holder =
chan.enforcement_state.current_holder_commit_info.as_ref().unwrap().clone();
holder.received_htlcs.push(HTLCInfo2 {
value_sat: 1,
payment_hash: PaymentHash([1; 32]),
cltv_expiry: 2 << 16,
});
chan.enforcement_state.current_holder_commit_info = Some(holder);
},
|_tx, _wallet_paths, _allowlist| {
},
|chan| {
assert_eq!(chan.enforcement_state.channel_closed, false);
}
),
"policy failure: validate_mutual_close_tx: cannot close with pending htlcs"
);
}
#[test]
fn sign_mutual_close_tx_with_counterparty_offered_htlcs() {
assert_failed_precondition_err!(
sign_mutual_close_tx_with_mutators_outbound!(
|chan, _to_holder, _to_counterparty, _holder_script, _counter_script, _outpoint| {
let mut cparty = chan
.enforcement_state
.current_counterparty_commit_info
.as_ref()
.unwrap()
.clone();
cparty.offered_htlcs.push(HTLCInfo2 {
value_sat: 1,
payment_hash: PaymentHash([1; 32]),
cltv_expiry: 2 << 16,
});
chan.enforcement_state.current_counterparty_commit_info = Some(cparty);
},
|_tx, _wallet_paths, _allowlist| {
},
|chan| {
assert_eq!(chan.enforcement_state.channel_closed, false);
}
),
"policy failure: validate_mutual_close_tx: cannot close with pending htlcs"
);
}
#[test]
fn sign_mutual_close_tx_with_counterparty_received_htlcs() {
assert_failed_precondition_err!(
sign_mutual_close_tx_with_mutators_outbound!(
|chan, _to_holder, _to_counterparty, _holder_script, _counter_script, _outpoint| {
let mut cparty = chan
.enforcement_state
.current_counterparty_commit_info
.as_ref()
.unwrap()
.clone();
cparty.received_htlcs.push(HTLCInfo2 {
value_sat: 1,
payment_hash: PaymentHash([1; 32]),
cltv_expiry: 2 << 16,
});
chan.enforcement_state.current_counterparty_commit_info = Some(cparty);
},
|_tx, _wallet_paths, _allowlist| {
},
|chan| {
assert_eq!(chan.enforcement_state.channel_closed, false);
}
),
"policy failure: validate_mutual_close_tx: cannot close with pending htlcs"
);
}
#[test]
fn sign_mutual_close_tx_outbound_with_pos_skewed_holder_commitment() {
assert_failed_precondition_err!(
sign_mutual_close_tx_with_mutators_outbound!(
|chan, _to_holder, _to_counterparty, _holder_script, _counter_script, _outpoint| {
let mut holder =
chan.enforcement_state.current_holder_commit_info.as_ref().unwrap().clone();
let mut cparty = chan
.enforcement_state
.current_counterparty_commit_info
.as_ref()
.unwrap()
.clone();
holder.to_broadcaster_value_sat += 14_000; holder.to_countersigner_value_sat -= 14_000; cparty.to_countersigner_value_sat += 9_000;
cparty.to_broadcaster_value_sat -= 9_000;
chan.enforcement_state.current_holder_commit_info = Some(holder);
chan.enforcement_state.current_counterparty_commit_info = Some(cparty);
},
|_tx, _wallet_paths, _allowlist| {
},
|chan| {
assert_eq!(chan.enforcement_state.channel_closed, false);
}
),
"policy failure: validate_mutual_close_tx: \
to_counterparty_value 1000000 is larger than \
holder_info.countersigner_value_sat 986000"
);
}
#[test]
fn sign_mutual_close_tx_outbound_with_neg_skewed_holder_commitment() {
assert_failed_precondition_err!(
sign_mutual_close_tx_with_mutators_outbound!(
|chan, _to_holder, _to_counterparty, _holder_script, _counter_script, _outpoint| {
let mut holder =
chan.enforcement_state.current_holder_commit_info.as_ref().unwrap().clone();
let mut cparty = chan
.enforcement_state
.current_counterparty_commit_info
.as_ref()
.unwrap()
.clone();
holder.to_broadcaster_value_sat -= 14_000; holder.to_countersigner_value_sat += 14_000; cparty.to_countersigner_value_sat -= 9_000;
cparty.to_broadcaster_value_sat += 9_000;
chan.enforcement_state.current_holder_commit_info = Some(holder);
chan.enforcement_state.current_counterparty_commit_info = Some(cparty);
},
|_tx, _wallet_paths, _allowlist| {
},
|chan| {
assert_eq!(chan.enforcement_state.channel_closed, false);
}
),
"policy failure: validate_mutual_close_tx: \
to_counterparty_value 1000000 is smaller than \
holder_info.countersigner_value_sat 1014000"
);
}
#[test]
fn sign_mutual_close_tx_outbound_with_pos_skewed_cparty_commitment() {
assert_failed_precondition_err!(
sign_mutual_close_tx_with_mutators_outbound!(
|chan, _to_holder, _to_counterparty, _holder_script, _counter_script, _outpoint| {
let mut holder =
chan.enforcement_state.current_holder_commit_info.as_ref().unwrap().clone();
let mut cparty = chan
.enforcement_state
.current_counterparty_commit_info
.as_ref()
.unwrap()
.clone();
holder.to_broadcaster_value_sat -= 9_000;
holder.to_countersigner_value_sat += 9_000;
cparty.to_countersigner_value_sat -= 14_000; cparty.to_broadcaster_value_sat += 14_000; chan.enforcement_state.current_holder_commit_info = Some(holder);
chan.enforcement_state.current_counterparty_commit_info = Some(cparty);
},
|_tx, _wallet_paths, _allowlist| {
},
|chan| {
assert_eq!(chan.enforcement_state.channel_closed, false);
}
),
"policy failure: validate_mutual_close_tx: \
to_counterparty_value 1000000 is smaller than \
counterparty_info.broadcaster_value_sat 1014000"
);
}
#[test]
fn sign_mutual_close_tx_outbound_with_neg_skewed_cparty_commitment() {
assert_failed_precondition_err!(
sign_mutual_close_tx_with_mutators_outbound!(
|chan, _to_holder, _to_counterparty, _holder_script, _counter_script, _outpoint| {
let mut holder =
chan.enforcement_state.current_holder_commit_info.as_ref().unwrap().clone();
let mut cparty = chan
.enforcement_state
.current_counterparty_commit_info
.as_ref()
.unwrap()
.clone();
holder.to_broadcaster_value_sat += 9_000;
holder.to_countersigner_value_sat -= 9_000;
cparty.to_countersigner_value_sat += 14_000; cparty.to_broadcaster_value_sat -= 14_000; chan.enforcement_state.current_holder_commit_info = Some(holder);
chan.enforcement_state.current_counterparty_commit_info = Some(cparty);
},
|_tx, _wallet_paths, _allowlist| {
},
|chan| {
assert_eq!(chan.enforcement_state.channel_closed, false);
}
),
"policy failure: validate_mutual_close_tx: \
to_counterparty_value 1000000 is larger than \
counterparty_info.broadcaster_value_sat 986000"
);
}
#[test]
fn sign_mutual_close_tx_inbound_with_pos_skewed_holder_commitment() {
assert_failed_precondition_err!(
sign_mutual_close_tx_with_mutators_inbound!(
|chan, _to_holder, _to_counterparty, _holder_script, _counter_script, _outpoint| {
let mut holder =
chan.enforcement_state.current_holder_commit_info.as_ref().unwrap().clone();
let mut cparty = chan
.enforcement_state
.current_counterparty_commit_info
.as_ref()
.unwrap()
.clone();
holder.to_broadcaster_value_sat += 14_000; holder.to_countersigner_value_sat -= 14_000; cparty.to_countersigner_value_sat += 9_000;
cparty.to_broadcaster_value_sat -= 9_000;
chan.enforcement_state.current_holder_commit_info = Some(holder);
chan.enforcement_state.current_counterparty_commit_info = Some(cparty);
},
|_tx, _wallet_paths, _allowlist| {
},
|chan| {
assert_eq!(chan.enforcement_state.channel_closed, false);
}
),
"policy failure: validate_mutual_close_tx: \
to_holder_value 2000000 is smaller than \
holder_info.broadcaster_value_sat 2014000"
);
}
#[test]
fn sign_mutual_close_tx_inbound_with_neg_skewed_holder_commitment() {
assert_failed_precondition_err!(
sign_mutual_close_tx_with_mutators_inbound!(
|chan, _to_holder, _to_counterparty, _holder_script, _counter_script, _outpoint| {
let mut holder =
chan.enforcement_state.current_holder_commit_info.as_ref().unwrap().clone();
let mut cparty = chan
.enforcement_state
.current_counterparty_commit_info
.as_ref()
.unwrap()
.clone();
holder.to_broadcaster_value_sat -= 14_000; holder.to_countersigner_value_sat += 14_000; cparty.to_countersigner_value_sat -= 9_000;
cparty.to_broadcaster_value_sat += 9_000;
chan.enforcement_state.current_holder_commit_info = Some(holder);
chan.enforcement_state.current_counterparty_commit_info = Some(cparty);
},
|_tx, _wallet_paths, _allowlist| {
},
|chan| {
assert_eq!(chan.enforcement_state.channel_closed, false);
}
),
"policy failure: validate_mutual_close_tx: \
to_holder_value 2000000 is larger than \
holder_info.broadcaster_value_sat 1986000"
);
}
#[test]
fn sign_mutual_close_tx_inbound_with_pos_skewed_cparty_commitment() {
assert_failed_precondition_err!(
sign_mutual_close_tx_with_mutators_inbound!(
|chan, _to_holder, _to_counterparty, _holder_script, _counter_script, _outpoint| {
let mut holder =
chan.enforcement_state.current_holder_commit_info.as_ref().unwrap().clone();
let mut cparty = chan
.enforcement_state
.current_counterparty_commit_info
.as_ref()
.unwrap()
.clone();
holder.to_broadcaster_value_sat -= 9_000;
holder.to_countersigner_value_sat += 9_000;
cparty.to_countersigner_value_sat -= 14_000; cparty.to_broadcaster_value_sat += 14_000; chan.enforcement_state.current_holder_commit_info = Some(holder);
chan.enforcement_state.current_counterparty_commit_info = Some(cparty);
},
|_tx, _wallet_paths, _allowlist| {
},
|chan| {
assert_eq!(chan.enforcement_state.channel_closed, false);
}
),
"policy failure: validate_mutual_close_tx: \
to_holder_value 2000000 is larger than \
counterparty_info.countersigner_value_sat 1986000"
);
}
#[test]
fn sign_mutual_close_tx_inbound_with_neg_skewed_cparty_commitment() {
assert_failed_precondition_err!(
sign_mutual_close_tx_with_mutators_inbound!(
|chan, _to_holder, _to_counterparty, _holder_script, _counter_script, _outpoint| {
let mut holder =
chan.enforcement_state.current_holder_commit_info.as_ref().unwrap().clone();
let mut cparty = chan
.enforcement_state
.current_counterparty_commit_info
.as_ref()
.unwrap()
.clone();
holder.to_broadcaster_value_sat += 9_000;
holder.to_countersigner_value_sat -= 9_000;
cparty.to_countersigner_value_sat += 14_000; cparty.to_broadcaster_value_sat -= 14_000; chan.enforcement_state.current_holder_commit_info = Some(holder);
chan.enforcement_state.current_counterparty_commit_info = Some(cparty);
},
|_tx, _wallet_paths, _allowlist| {
},
|chan| {
assert_eq!(chan.enforcement_state.channel_closed, false);
}
),
"policy failure: validate_mutual_close_tx: \
to_holder_value 2000000 is smaller than \
counterparty_info.countersigner_value_sat 2014000"
);
}
#[test]
fn sign_mutual_close_tx_outbound_with_holder_output_too_large() {
assert_failed_precondition_err!(
sign_mutual_close_tx_with_mutators_outbound!(
|_chan, to_holder, to_counterparty, _holder_script, _counter_script, _outpoint| {
*to_holder += 15_000;
*to_counterparty -= 15_000;
},
|_tx, _wallet_paths, _allowlist| {
},
|chan| {
assert_eq!(chan.enforcement_state.channel_closed, false);
}
),
"policy failure: validate_mutual_close_tx: \
to_counterparty_value 985000 is smaller than \
counterparty_info.broadcaster_value_sat 1000000"
);
}
#[test]
fn sign_mutual_close_tx_outbound_with_holder_output_too_small() {
assert_failed_precondition_err!(
sign_mutual_close_tx_with_mutators_outbound!(
|_chan, to_holder, to_counterparty, _holder_script, _counter_script, _outpoint| {
*to_holder -= 15_000;
*to_counterparty += 15_000;
},
|_tx, _wallet_paths, _allowlist| {
},
|chan| {
assert_eq!(chan.enforcement_state.channel_closed, false);
}
),
"policy failure: validate_mutual_close_tx: \
to_counterparty_value 1015000 is larger than \
counterparty_info.broadcaster_value_sat 1000000"
);
}
#[test]
fn sign_mutual_close_tx_inbound_with_holder_output_too_large() {
assert_failed_precondition_err!(
sign_mutual_close_tx_with_mutators_inbound!(
|_chan, to_holder, to_counterparty, _holder_script, _counter_script, _outpoint| {
*to_holder += 15_000;
*to_counterparty -= 15_000;
},
|_tx, _wallet_paths, _allowlist| {
},
|chan| {
assert_eq!(chan.enforcement_state.channel_closed, false);
}
),
"policy failure: validate_mutual_close_tx: \
to_holder_value 2015000 is larger than holder_info.broadcaster_value_sat 2000000"
);
}
#[test]
fn sign_mutual_close_tx_inbound_with_holder_output_too_small() {
assert_failed_precondition_err!(
sign_mutual_close_tx_with_mutators_inbound!(
|_chan, to_holder, to_counterparty, _holder_script, _counter_script, _outpoint| {
*to_holder -= 15_000;
*to_counterparty += 15_000;
},
|_tx, _wallet_paths, _allowlist| {
},
|chan| {
assert_eq!(chan.enforcement_state.channel_closed, false);
}
),
"policy failure: validate_mutual_close_tx: \
to_holder_value 1985000 is smaller than holder_info.broadcaster_value_sat 2000000"
);
}
#[test]
fn check_expected_weight() {
assert_status_ok!(sign_mutual_close_tx_phase2_with_mutators_outbound!(
|_chan,
to_holder,
to_counterparty,
holder_script,
counter_script,
outpoint,
_wallet_path,
_allowlist| {
let weight = mutual_close_tx_weight(
&ClosingTransaction::new(
*to_holder,
*to_counterparty,
holder_script.clone(),
counter_script.clone(),
*outpoint,
)
.trust()
.built_transaction(),
);
assert_eq!(weight, 674);
},
|chan| {
assert_eq!(chan.enforcement_state.channel_closed, true);
}
));
}
}