#[cfg(test)]
mod tests {
use bitcoin::absolute::{Height, LockTime};
use bitcoin::bip32::DerivationPath;
use bitcoin::transaction::Version;
use bitcoin::{
self, Amount, OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Txid, Witness,
};
use lightning::ln::chan_utils::get_revokeable_redeemscript;
use test_log::test;
use vls_common::to_derivation_path;
use crate::channel::{Channel, ChannelBase, TypedSignature};
use crate::node::SpendType::{P2shP2wpkh, P2wpkh};
use crate::policy::validator::ChainState;
use crate::util::status::{Code, Status};
use crate::util::test_utils::*;
fn make_test_delayed_sweep_tx(
txid: Txid,
vout: u32,
contest_delay: u16,
script_pubkey: ScriptBuf,
amount_sat: u64,
) -> Transaction {
Transaction {
version: Version::TWO,
lock_time: LockTime::ZERO,
input: vec![TxIn {
previous_output: OutPoint { txid, vout },
script_sig: ScriptBuf::new(),
sequence: Sequence(contest_delay as u32),
witness: Witness::default(),
}],
output: vec![TxOut {
script_pubkey: script_pubkey,
value: Amount::from_sat(amount_sat),
}],
}
}
const HOLD_COMMIT_NUM: u64 = 53;
fn sign_delayed_sweep_with_mutators<MakeDestination, InputMutator>(
make_dest: MakeDestination,
mutate_signing_input: InputMutator,
) -> Result<(), Status>
where
MakeDestination: Fn(&TestNodeContext) -> (ScriptBuf, DerivationPath),
InputMutator: Fn(
&mut Channel,
&mut ChainState,
&mut Transaction,
&mut usize,
&mut u64,
&mut ScriptBuf,
&mut u64,
),
{
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 (node_ctx, chan_ctx) = setup_funded_channel(
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| {},
)?;
let (sig, tx, per_commitment_point, input, redeemscript, amount_sat) =
node_ctx.node.with_channel(&chan_ctx.channel_id, |chan| {
let mut input = 0;
let built_commit =
commit_tx_ctx.tx.as_ref().unwrap().trust().built_transaction().clone();
let built_commit_tx = &built_commit.transaction;
let built_commit_txid = &built_commit.txid;
let mut commit_num = HOLD_COMMIT_NUM;
let per_commitment_point = chan.get_per_commitment_point(commit_num)?;
let keys = chan.make_holder_tx_keys(&per_commitment_point);
let contest_delay = chan.setup.counterparty_selected_contest_delay;
let mut redeemscript = get_revokeable_redeemscript(
&keys.revocation_key,
contest_delay,
&keys.broadcaster_delayed_payment_key,
);
let to_local_outndx = 4;
let mut amount_sat = built_commit_tx.output[to_local_outndx].value.to_sat();
assert_eq!(amount_sat, 1979997);
let (script_pubkey, wallet_path) = make_dest(&node_ctx);
let fee = 1_000;
let mut tx = make_test_delayed_sweep_tx(
built_commit_txid.clone(),
to_local_outndx as u32,
contest_delay,
script_pubkey,
amount_sat - fee,
);
let mut cstate = make_test_chain_state();
mutate_signing_input(
chan,
&mut cstate,
&mut tx,
&mut input,
&mut commit_num,
&mut redeemscript,
&mut amount_sat,
);
let sig = chan.sign_delayed_sweep(
&tx,
input,
commit_num,
&redeemscript,
amount_sat,
&wallet_path,
)?;
Ok((sig, tx, per_commitment_point, input, redeemscript, amount_sat))
})?;
let delayed_pubkey = get_channel_delayed_payment_pubkey(
&node_ctx.node,
&chan_ctx.channel_id,
&per_commitment_point,
);
check_signature(
&tx,
input,
TypedSignature::all(sig),
&delayed_pubkey,
amount_sat,
&redeemscript,
);
Ok(())
}
#[test]
fn sign_delayed_to_local_wallet_p2wpkh_success() {
assert_status_ok!(sign_delayed_sweep_with_mutators(
|node_ctx| { make_test_wallet_dest(node_ctx, 19, P2wpkh) },
|_chan, _cstate, _tx, _input, _commit_num, _redeemscript, _amount_sat| {},
));
}
#[test]
fn sign_delayed_to_local_wallet_p2shwpkh_success() {
assert_status_ok!(sign_delayed_sweep_with_mutators(
|node_ctx| { make_test_wallet_dest(node_ctx, 19, P2shP2wpkh) },
|_chan, _cstate, _tx, _input, _commit_num, _redeemscript, _amount_sat| {},
));
}
#[test]
fn sign_delayed_to_local_allowlist_p2wpkh_success() {
assert_status_ok!(sign_delayed_sweep_with_mutators(
|node_ctx| { make_test_nonwallet_dest(node_ctx, 3, P2wpkh) },
|chan, _cstate, _tx, _input, _commit_num, _redeemscript, _amount_sat| {
chan.node
.upgrade()
.unwrap()
.add_allowlist(&vec!["tb1qg975h6gdx5mryeac72h6lj2nzygugxhyk6dnhr".to_string()])
.expect("add_allowlist");
},
));
}
#[test]
fn sign_delayed_to_local_allowlist_p2shwpkh_success() {
assert_status_ok!(sign_delayed_sweep_with_mutators(
|node_ctx| { make_test_nonwallet_dest(node_ctx, 3, P2shP2wpkh) },
|chan, _cstate, _tx, _input, _commit_num, _redeemscript, _amount_sat| {
chan.node
.upgrade()
.unwrap()
.add_allowlist(&vec!["2MspRgcQvaVN2RkpumN1X8GkzsE7BVTTb6y".to_string()])
.expect("add_allowlist");
},
));
}
#[test]
fn sign_delayed_to_local_with_unknown_dest() {
assert_failed_precondition_err!(
sign_delayed_sweep_with_mutators(
|node_ctx| { make_test_nonwallet_dest(node_ctx, 3, P2shP2wpkh) },
|_chan, _cstate, _tx, _input, _commit_num, _redeemscript, _amount_sat| {},
),
"policy failure: validate_delayed_sweep: validate_sweep: \
destination is not in wallet or allowlist"
);
}
#[test]
fn sign_delayed_sweep_with_wrong_wallet_path() {
assert_failed_precondition_err!(
sign_delayed_sweep_with_mutators(
|node_ctx| {
let script = make_test_wallet_dest(node_ctx, 19, P2wpkh).0;
let derivation_path = to_derivation_path(&[21u32]);
(script, derivation_path)
},
|_chan, _cstate, _tx, _input, _commit_num, _redeemscript, _amount_sat| {},
),
"policy failure: validate_delayed_sweep: validate_sweep: \
destination is not in wallet or allowlist"
);
}
#[test]
fn sign_delayed_sweep_with_bad_input_index() {
assert_invalid_argument_err!(
sign_delayed_sweep_with_mutators(
|node_ctx| { make_test_wallet_dest(node_ctx, 19, P2wpkh) },
|_chan, _cstate, _tx, input, _commit_num, _redeemscript, _amount_sat| {
*input = 1;
},
),
"sign_delayed_sweep: bad input index: 1 >= 1"
);
}
#[test]
fn sign_delayed_sweep_with_bad_version() {
assert_failed_precondition_err!(
sign_delayed_sweep_with_mutators(
|node_ctx| { make_test_wallet_dest(node_ctx, 19, P2wpkh) },
|_chan, _cstate, tx, _input, _commit_num, _redeemscript, _amount_sat| {
tx.version = Version::non_standard(3);
},
),
"transaction format: validate_delayed_sweep: validate_sweep: bad version: 3"
);
}
#[test]
fn sign_delayed_sweep_with_bad_locktime() {
assert_failed_precondition_err!(
sign_delayed_sweep_with_mutators(
|node_ctx| { make_test_wallet_dest(node_ctx, 19, P2wpkh) },
|_chan, _cstate, tx, _input, _commit_num, _redeemscript, _amount_sat| {
tx.lock_time = LockTime::Blocks(Height::from_consensus(1_000_000).unwrap());
},
),
"transaction format: validate_delayed_sweep: bad locktime: 1000000 > 5"
);
}
#[test]
fn sign_delayed_sweep_with_bad_sequence() {
assert_failed_precondition_err!(
sign_delayed_sweep_with_mutators(
|node_ctx| { make_test_wallet_dest(node_ctx, 19, P2wpkh) },
|_chan, _cstate, tx, _input, _commit_num, _redeemscript, _amount_sat| {
tx.input[0].sequence = Sequence(42);
},
),
"transaction format: validate_delayed_sweep: bad sequence: 42 != 7"
);
}
#[test]
#[ignore] fn sign_delayed_sweep_with_fee_underflow() {
assert_failed_precondition_err!(
sign_delayed_sweep_with_mutators(
|node_ctx| { make_test_wallet_dest(node_ctx, 19, P2wpkh) },
|_chan, _cstate, _tx, _input, _commit_num, _redeemscript, amount_sat| {
*amount_sat -= 100_000;
},
),
"policy failure: validate_delayed_sweep: validate_sweep: \
fee underflow: 1879997 - 1978997"
);
}
#[test]
#[ignore] fn sign_delayed_sweep_with_fee_too_small() {
assert_failed_precondition_err!(
sign_delayed_sweep_with_mutators(
|node_ctx| { make_test_wallet_dest(node_ctx, 19, P2wpkh) },
|_chan, _cstate, tx, _input, _commit_num, _redeemscript, amount_sat| {
*amount_sat = tx.output[0].value.to_sat(); },
),
"policy failure: validate_delayed_sweep: validate_sweep: validate_fee: \
fee below minimum: 0 < 100"
);
}
#[test]
#[ignore] fn sign_delayed_sweep_with_fee_too_large() {
assert_failed_precondition_err!(
sign_delayed_sweep_with_mutators(
|node_ctx| { make_test_wallet_dest(node_ctx, 19, P2wpkh) },
|_chan, _cstate, tx, _input, _commit_num, _redeemscript, _amount_sat| {
tx.output[0].value = Amount::from_sat(1_000);
},
),
"policy failure: validate_delayed_sweep: validate_sweep: validate_fee: \
fee above maximum: 1978997 > 200000"
);
}
}