#[cfg(test)]
mod tests {
use bitcoin::{self, ScriptBuf, Transaction};
use lightning::ln::chan_utils::{
build_htlc_transaction, get_htlc_redeemscript, make_funding_redeemscript,
};
use test_log::test;
use crate::channel::{Channel, ChannelBase, CommitmentType, TypedSignature};
use crate::node::NodeMonitor;
use crate::policy::validator::{ChainState, EnforcementState};
use crate::util::status::{Code, Status};
use crate::util::test_utils::key::make_test_pubkey;
use crate::util::test_utils::*;
use crate::util::transaction_utils::expected_commitment_tx_weight;
use paste::paste;
const HOLD_COMMIT_NUM: u64 = 23;
#[allow(dead_code)]
struct SignMutationState<'a> {
cstate: &'a mut ChainState,
estate: &'a mut EnforcementState,
commit_num: &'a mut u64,
chan_ctx: &'a TestChannelContext,
num_htlcs: usize,
tx: &'a Transaction,
}
fn sign_holder_commitment_tx_with_mutators<SignInputMutator>(
commitment_type: CommitmentType,
mutate_sign_inputs: SignInputMutator,
) -> Result<(), Status>
where
SignInputMutator: Fn(&mut SignMutationState),
{
let next_holder_commit_num = HOLD_COMMIT_NUM;
let next_counterparty_commit_num = HOLD_COMMIT_NUM + 1;
let next_counterparty_revoke_num = next_counterparty_commit_num - 1;
let mut setup = make_test_channel_setup();
setup.commitment_type = commitment_type;
let (node_ctx, chan_ctx) = setup_funded_channel_with_setup(
setup,
next_holder_commit_num,
next_counterparty_commit_num,
next_counterparty_revoke_num,
);
let commit_tx_ctx = setup_validated_holder_commitment(
&node_ctx,
&chan_ctx,
HOLD_COMMIT_NUM,
|_commit_tx_ctx| {},
|_keys| {},
)?;
sign_holder_commitment_tx_with_mutators_common(
&node_ctx,
&chan_ctx,
commit_tx_ctx,
mutate_sign_inputs,
)
}
fn sign_holder_commitment_tx_retry_with_mutators<SignInputMutator>(
commitment_type: CommitmentType,
mutate_sign_inputs: SignInputMutator,
) -> Result<(), Status>
where
SignInputMutator: Fn(&mut SignMutationState),
{
let next_holder_commit_num = HOLD_COMMIT_NUM;
let next_counterparty_commit_num = HOLD_COMMIT_NUM + 1;
let next_counterparty_revoke_num = next_counterparty_commit_num - 1;
let mut setup = make_test_channel_setup();
setup.commitment_type = commitment_type;
let (node_ctx, chan_ctx) = setup_funded_channel_with_setup(
setup,
next_holder_commit_num,
next_counterparty_commit_num,
next_counterparty_revoke_num,
);
let commit_tx_ctx = setup_validated_holder_commitment(
&node_ctx,
&chan_ctx,
HOLD_COMMIT_NUM,
|_commit_tx_ctx| {},
|_keys| {},
)?;
sign_holder_commitment_tx_with_mutators_common(
&node_ctx,
&chan_ctx,
commit_tx_ctx.clone(),
|_sms| {},
)?;
sign_holder_commitment_tx_with_mutators_common(
&node_ctx,
&chan_ctx,
commit_tx_ctx,
mutate_sign_inputs,
)
}
fn setup_pending_next_holder_commitment(
commitment_type: CommitmentType,
) -> Result<(TestNodeContext, TestChannelContext, TestCommitmentTxContext), Status> {
let next_holder_commit_num = HOLD_COMMIT_NUM;
let next_counterparty_commit_num = HOLD_COMMIT_NUM + 1;
let next_counterparty_revoke_num = next_counterparty_commit_num - 1;
let mut setup = make_test_channel_setup();
setup.commitment_type = commitment_type;
let (node_ctx, chan_ctx) = setup_funded_channel_with_setup(
setup,
next_holder_commit_num,
next_counterparty_commit_num,
next_counterparty_revoke_num,
);
let current_commit_tx_ctx = setup_validated_holder_commitment(
&node_ctx,
&chan_ctx,
HOLD_COMMIT_NUM,
|_commit_tx_ctx| {},
|_keys| {},
)?;
let mut next_commit_tx_ctx = channel_commitment(
&node_ctx,
&chan_ctx,
HOLD_COMMIT_NUM + 1,
current_commit_tx_ctx.feerate_per_kw,
current_commit_tx_ctx.to_broadcaster,
current_commit_tx_ctx.to_countersignatory,
current_commit_tx_ctx.offered_htlcs.clone(),
current_commit_tx_ctx.received_htlcs.clone(),
);
let (commit_sig, htlc_sigs) =
counterparty_sign_holder_commitment(&node_ctx, &chan_ctx, &mut next_commit_tx_ctx);
for offered_htlc in next_commit_tx_ctx.offered_htlcs.clone() {
node_ctx.node.add_keysend(
make_test_pubkey(1),
offered_htlc.payment_hash,
offered_htlc.value_sat * 1000,
)?;
}
node_ctx.node.with_channel(&chan_ctx.channel_id, |chan| {
chan.validate_holder_commitment_tx_phase2(
next_commit_tx_ctx.commit_num,
next_commit_tx_ctx.feerate_per_kw,
next_commit_tx_ctx.to_broadcaster,
next_commit_tx_ctx.to_countersignatory,
next_commit_tx_ctx.offered_htlcs.clone(),
next_commit_tx_ctx.received_htlcs.clone(),
&commit_sig,
&htlc_sigs,
)?;
assert_eq!(
chan.enforcement_state.next_holder_commit_num,
next_commit_tx_ctx.commit_num
);
assert!(chan.enforcement_state.next_holder_commit_info.is_some());
Ok(())
})?;
Ok((node_ctx, chan_ctx, next_commit_tx_ctx))
}
fn sign_pending_next_holder_commitment(commitment_type: CommitmentType) -> Result<(), Status> {
let (node_ctx, chan_ctx, next_commit_tx_ctx) =
setup_pending_next_holder_commitment(commitment_type)?;
let (sig, tx) = node_ctx.node.with_channel(&chan_ctx.channel_id, |chan| {
let trusted_tx = next_commit_tx_ctx.tx.as_ref().unwrap().trust();
let tx = trusted_tx.built_transaction().transaction.clone();
let sig = chan.sign_holder_commitment_tx_phase2(next_commit_tx_ctx.commit_num)?;
assert_eq!(chan.enforcement_state.channel_closed, true);
let spent = vec![false; trusted_tx.nondust_htlcs().len()];
let (tx_r, _htlc_txs_r, _revocable_script_r, _uck_r, _revocation_pubkey_r) =
chan.sign_holder_commitment_tx_for_recovery(&spent, None)?;
assert_eq!(tx_r.compute_txid(), tx.compute_txid());
Ok((sig, tx))
})?;
let funding_pubkey = get_channel_funding_pubkey(&node_ctx.node, &chan_ctx.channel_id);
let channel_funding_redeemscript = make_funding_redeemscript(
&funding_pubkey,
&chan_ctx.setup.counterparty_points.funding_pubkey,
);
check_signature(
&tx,
0,
TypedSignature::all(sig),
&funding_pubkey,
chan_ctx.setup.channel_value_sat,
&channel_funding_redeemscript,
);
Ok(())
}
fn recover_pending_next_holder_commitment(
commitment_type: CommitmentType,
) -> Result<(), Status> {
let (node_ctx, chan_ctx, next_commit_tx_ctx) =
setup_pending_next_holder_commitment(commitment_type)?;
node_ctx.node.with_channel(&chan_ctx.channel_id, |chan| {
let trusted_tx = next_commit_tx_ctx.tx.as_ref().unwrap().trust();
let spent = vec![false; trusted_tx.nondust_htlcs().len()];
let (tx_r, _htlc_txs_r, _revocable_script_r, _uck_r, _revocation_pubkey_r) =
chan.sign_holder_commitment_tx_for_recovery(&spent, None)?;
assert_eq!(
tx_r.compute_txid(),
trusted_tx.built_transaction().transaction.compute_txid()
);
Ok(())
})
}
fn sign_holder_commitment_tx_with_mutators_common<SignInputMutator>(
node_ctx: &TestNodeContext,
chan_ctx: &TestChannelContext,
commit_tx_ctx0: TestCommitmentTxContext,
mutate_sign_inputs: SignInputMutator,
) -> Result<(), Status>
where
SignInputMutator: Fn(&mut SignMutationState),
{
let (sig, tx) = node_ctx.node.with_channel(&chan_ctx.channel_id, |chan| {
let mut commit_tx_ctx = commit_tx_ctx0.clone();
let per_commitment_point =
chan.get_per_commitment_point(commit_tx_ctx.commit_num).expect("point");
let txkeys = chan.make_holder_tx_keys(&per_commitment_point);
let htlcs = Channel::htlcs_info2_to_oic(
&commit_tx_ctx.offered_htlcs,
&commit_tx_ctx.received_htlcs,
);
let commitment_tx = chan.make_holder_commitment_tx(
commit_tx_ctx.commit_num,
&per_commitment_point,
commit_tx_ctx.feerate_per_kw,
commit_tx_ctx.to_broadcaster,
commit_tx_ctx.to_countersignatory,
htlcs.clone(),
);
let trusted_tx = commitment_tx.trust();
let tx = trusted_tx.built_transaction().clone();
let mut cstate = make_test_chain_state();
mutate_sign_inputs(&mut SignMutationState {
cstate: &mut cstate,
estate: &mut chan.enforcement_state,
commit_num: &mut commit_tx_ctx.commit_num,
chan_ctx: chan_ctx,
num_htlcs: commit_tx_ctx.offered_htlcs.len() + commit_tx_ctx.received_htlcs.len(),
tx: &tx.transaction,
});
let sig = chan.sign_holder_commitment_tx_phase2(commit_tx_ctx.commit_num)?;
let build_feerate =
if chan_ctx.setup.is_zero_fee_htlc() { 0 } else { commit_tx_ctx.feerate_per_kw };
let _htlc_txs = trusted_tx
.nondust_htlcs()
.iter()
.map(|htlc| {
build_htlc_transaction(
&tx.transaction.compute_txid(),
build_feerate,
chan_ctx.setup.counterparty_selected_contest_delay,
&htlc,
&chan_ctx.setup.features(),
&txkeys.broadcaster_delayed_payment_key,
&txkeys.revocation_key,
)
})
.collect::<Vec<Transaction>>();
let _htlc_redeemscripts = htlcs
.iter()
.map(|htlc| get_htlc_redeemscript(&htlc, &chan_ctx.setup.features(), &txkeys))
.collect::<Vec<ScriptBuf>>();
assert_eq!(chan.enforcement_state.channel_closed, true);
let spent = vec![false; trusted_tx.nondust_htlcs().len()];
let (tx_r, _htlc_txs_r, _revocable_script_r, _uck_r, _revocation_pubkey_r) =
chan.sign_holder_commitment_tx_for_recovery(&spent, None)?;
assert_eq!(tx_r.compute_txid(), tx.transaction.compute_txid());
Ok((sig, tx.transaction.clone()))
})?;
assert_eq!(node_ctx.node.channel_balance(), ChannelBalanceBuilder::new().build());
assert_eq!(
tx.compute_txid().to_string(),
if chan_ctx.setup.commitment_type == CommitmentType::StaticRemoteKey {
"54cb4849bc8cb6474bc0209665ad88519121a1a2c35b0ac59aa1c0f0e42a772e"
} else {
"dd2ace31bff26915c6a1e30b44918a0743b09d08e35eff56c8bc00c09b70498a"
}
);
let funding_pubkey = get_channel_funding_pubkey(&node_ctx.node, &chan_ctx.channel_id);
let channel_funding_redeemscript = make_funding_redeemscript(
&funding_pubkey,
&chan_ctx.setup.counterparty_points.funding_pubkey,
);
check_signature(
&tx,
0,
TypedSignature::all(sig),
&funding_pubkey,
chan_ctx.setup.channel_value_sat,
&channel_funding_redeemscript,
);
Ok(())
}
macro_rules! generate_status_ok_variations {
($name: ident, $sms: expr) => {
paste! {
#[test]
fn [<$name _static>]() {
assert_status_ok!(
sign_holder_commitment_tx_with_mutators(
CommitmentType::StaticRemoteKey, $sms)
);
}
}
paste! {
#[test]
fn [<$name _zerofee>]() {
assert_status_ok!(
sign_holder_commitment_tx_with_mutators(
CommitmentType::AnchorsZeroFeeHtlc, $sms)
);
}
}
};
}
macro_rules! generate_status_ok_retry_variations {
($name: ident, $sms: expr) => {
paste! {
#[test]
fn [<$name _retry_static>]() {
assert_status_ok!(
sign_holder_commitment_tx_retry_with_mutators(
CommitmentType::StaticRemoteKey, $sms)
);
}
}
paste! {
#[test]
fn [<$name _retry_zerofee>]() {
assert_status_ok!(
sign_holder_commitment_tx_retry_with_mutators(
CommitmentType::AnchorsZeroFeeHtlc, $sms)
);
}
}
};
}
generate_status_ok_variations!(success, |_| {});
generate_status_ok_variations!(ok_after_mutual_close, |sms| {
sms.estate.channel_closed = true;
});
#[test]
fn pending_next_success_static() {
assert_status_ok!(sign_pending_next_holder_commitment(CommitmentType::StaticRemoteKey));
}
#[test]
fn pending_next_success_zerofee() {
assert_status_ok!(sign_pending_next_holder_commitment(CommitmentType::AnchorsZeroFeeHtlc));
}
#[test]
fn pending_next_recovery_static() {
assert_status_ok!(recover_pending_next_holder_commitment(CommitmentType::StaticRemoteKey));
}
#[test]
fn pending_next_recovery_zerofee() {
assert_status_ok!(recover_pending_next_holder_commitment(
CommitmentType::AnchorsZeroFeeHtlc
));
}
generate_status_ok_retry_variations!(success, |_| {});
generate_status_ok_retry_variations!(ok_after_mutual_close, |sms| {
sms.estate.channel_closed = true;
});
generate_status_ok_variations!(check_expected_tx_weight, |sms| {
assert_eq!(
expected_commitment_tx_weight(sms.chan_ctx.setup.is_anchors(), sms.num_htlcs),
if sms.chan_ctx.setup.is_anchors() { 1640 } else { 1240 }
);
const WITNESS_WEIGHT: usize = 2 + 1 + 1 + 1 + 73 + 1 + 73 + 1 + 1 + 1 + 33 + 1 + 33 + 1 + 1;
assert_eq!(
sms.tx.weight().to_wu() + WITNESS_WEIGHT as u64,
if sms.chan_ctx.setup.is_anchors() { 1632 } else { 1240 }
);
});
#[allow(dead_code)]
struct ErrMsgContext {
opt_anchors: bool,
}
const ERR_MSG_CONTEXT_STATIC: ErrMsgContext = ErrMsgContext { opt_anchors: false };
const ERR_MSG_CONTEXT_ANCHORS: ErrMsgContext = ErrMsgContext { opt_anchors: true };
macro_rules! generate_failed_precondition_error_variations {
($name: ident, $sms: expr, $errcls: expr) => {
paste! {
#[test]
fn [<$name _static>]() {
assert_failed_precondition_err!(
sign_holder_commitment_tx_with_mutators(
CommitmentType::StaticRemoteKey, $sms),
($errcls)(ERR_MSG_CONTEXT_STATIC)
);
}
}
paste! {
#[test]
fn [<$name _zero_fee>]() {
assert_failed_precondition_err!(
sign_holder_commitment_tx_with_mutators(
CommitmentType::AnchorsZeroFeeHtlc, $sms),
($errcls)(ERR_MSG_CONTEXT_ANCHORS)
);
}
}
};
}
macro_rules! generate_failed_precondition_error_retry_variations {
($name: ident, $sms: expr, $errcls: expr) => {
paste! {
#[test]
fn [<$name _retry_static>]() {
assert_failed_precondition_err!(
sign_holder_commitment_tx_retry_with_mutators(
CommitmentType::StaticRemoteKey, $sms),
($errcls)(ERR_MSG_CONTEXT_STATIC)
);
}
}
};
}
generate_failed_precondition_error_variations!(
bad_prior_commit_num,
|sms| *sms.commit_num -= 1,
|_| "policy failure: get_current_holder_commitment_info: \
invalid next holder commitment number: 23 != 24"
);
generate_failed_precondition_error_variations!(
bad_later_commit_num,
|sms| *sms.commit_num += 1,
|_| "policy failure: get_current_holder_commitment_info: \
invalid next holder commitment number: 25 != 24"
);
generate_failed_precondition_error_retry_variations!(
bad_prior_commit_num,
|sms| *sms.commit_num -= 1,
|_| "policy failure: get_current_holder_commitment_info: \
invalid next holder commitment number: 23 != 24"
);
generate_failed_precondition_error_retry_variations!(
bad_later_commit_num,
|sms| *sms.commit_num += 1,
|_| "policy failure: get_current_holder_commitment_info: \
invalid next holder commitment number: 25 != 24"
);
}