use crate::prelude::*;
use crate::tx::script::ANCHOR_OUTPUT_VALUE_SATOSHI;
use anyhow::anyhow;
use bitcoin::consensus::Encodable;
use bitcoin::io::sink;
use bitcoin::secp256k1::ecdsa::Signature;
use bitcoin::secp256k1::{All, PublicKey, Secp256k1};
use bitcoin::sighash::EcdsaSighashType;
use bitcoin::transaction::Version;
use bitcoin::{Amount, CompressedPublicKey, ScriptBuf, Sequence, TxIn, Witness, WitnessProgram};
use bitcoin::{Transaction, TxOut, VarInt};
use lightning::ln::chan_utils::{
get_commitment_transaction_number_obscure_factor, get_revokeable_redeemscript,
get_to_countersigner_keyed_anchor_redeemscript, make_funding_redeemscript,
ChannelTransactionParameters, TxCreationKeys,
};
use lightning::sign::{
DelayedPaymentOutputDescriptor, SpendableOutputDescriptor, StaticPaymentOutputDescriptor,
};
pub const MAX_VALUE_MSAT: u64 = 21_000_000_0000_0000_000;
pub const MIN_DUST_LIMIT_SATOSHIS: u64 = 330;
pub const MIN_CHAN_DUST_LIMIT_SATOSHIS: u64 = 354;
pub(crate) fn expected_commitment_tx_weight(opt_anchors: bool, num_untrimmed_htlc: usize) -> usize {
const COMMITMENT_TX_BASE_WEIGHT: usize = 724;
const COMMITMENT_TX_BASE_ANCHOR_WEIGHT: usize = 1124;
const COMMITMENT_TX_WEIGHT_PER_HTLC: usize = 172;
let base_weight =
if opt_anchors { COMMITMENT_TX_BASE_ANCHOR_WEIGHT } else { COMMITMENT_TX_BASE_WEIGHT };
base_weight + num_untrimmed_htlc * COMMITMENT_TX_WEIGHT_PER_HTLC
}
pub(crate) fn mutual_close_tx_weight(unsigned_tx: &Transaction) -> usize {
const EXPECTED_MUTUAL_CLOSE_WITNESS_WEIGHT: usize = 2 + 1 + 4 + 72 + 72 + 1 + 1 + 33 + 1 + 33 + 1 + 1; unsigned_tx.weight().to_wu() as usize + EXPECTED_MUTUAL_CLOSE_WITNESS_WEIGHT
}
pub(crate) fn estimated_sweep_tx_weight(unsigned_tx: &Transaction) -> usize {
const EXPECTED_SWEEP_WITNESS_WEIGHT: usize = 220;
unsigned_tx.weight().to_wu() as usize + EXPECTED_SWEEP_WITNESS_WEIGHT
}
pub fn maybe_add_change_output(
tx: &mut Transaction,
input_value: u64,
witness_max_weight: u64,
feerate_sat_per_1000_weight: u32,
change_destination_script: ScriptBuf,
) -> Result<(), ()> {
if input_value > MAX_VALUE_MSAT / 1000 {
return Err(());
}
let mut output_value = Amount::ZERO;
for output in tx.output.iter() {
output_value = output_value.checked_add(output.value).unwrap();
if output_value.to_sat() >= input_value {
return Err(());
}
}
let dust_value = change_destination_script.minimal_non_dust();
let mut change_output = TxOut { script_pubkey: change_destination_script, value: Amount::ZERO };
let change_len = change_output.consensus_encode(&mut sink()).map_err(|_| ())?;
let mut weight_with_change: i64 =
tx.weight().to_wu() as i64 + 2 + witness_max_weight as i64 + change_len as i64 * 4;
weight_with_change += (VarInt(tx.output.len() as u64 + 1).size()
- VarInt(tx.output.len() as u64).size()) as i64
* 4;
let difference = input_value.checked_sub(output_value.to_sat()).ok_or(())?;
let change_value: i64 =
difference as i64 - weight_with_change * feerate_sat_per_1000_weight as i64 / 1000;
if change_value >= dust_value.to_sat() as i64 {
change_output.value = Amount::from_sat(change_value as u64);
tx.output.push(change_output);
} else if difference as i64
- (tx.weight().to_wu() as i64 + 2 + witness_max_weight as i64)
* feerate_sat_per_1000_weight as i64
/ 1000
< 0
{
return Err(());
}
Ok(())
}
pub(crate) fn estimate_feerate_per_kw(total_fee: u64, weight: u64) -> u32 {
(((total_fee * 1000) + 999) / weight) as u32
}
pub(crate) fn add_holder_sig(
tx: &mut Transaction,
holder_sig: Signature,
counterparty_sig: Signature,
holder_funding_key: &PublicKey,
counterparty_funding_key: &PublicKey,
) {
let funding_redeemscript =
make_funding_redeemscript(&holder_funding_key, &counterparty_funding_key);
tx.input[0].witness.push(Vec::new());
let mut ser_holder_sig = holder_sig.serialize_der().to_vec();
ser_holder_sig.push(EcdsaSighashType::All as u8);
let mut ser_cp_sig = counterparty_sig.serialize_der().to_vec();
ser_cp_sig.push(EcdsaSighashType::All as u8);
let holder_sig_first =
holder_funding_key.serialize()[..] < counterparty_funding_key.serialize()[..];
if holder_sig_first {
tx.input[0].witness.push(ser_holder_sig);
tx.input[0].witness.push(ser_cp_sig);
} else {
tx.input[0].witness.push(ser_cp_sig);
tx.input[0].witness.push(ser_holder_sig);
}
tx.input[0].witness.push(funding_redeemscript.as_bytes().to_vec());
}
pub(crate) fn is_tx_non_malleable(tx: &Transaction, segwit_flags: &[bool]) -> bool {
assert_eq!(tx.input.len(), segwit_flags.len(), "tx and segwit_flags must have same length");
segwit_flags.iter().all(|flag| *flag)
}
pub fn decode_commitment_tx(
tx: &Transaction,
holder_per_commitment_point: &PublicKey,
cp_per_commitment_point: &Option<PublicKey>,
params: &ChannelTransactionParameters,
secp_ctx: &Secp256k1<All>,
) -> (Option<u32>, Vec<u32>) {
let cp_params = params.counterparty_parameters.as_ref().unwrap();
let opt_anchors = params.channel_type_features.supports_anchors_nonzero_fee_htlc_tx()
|| params.channel_type_features.supports_anchors_zero_fee_htlc_tx();
let holder_pubkeys = ¶ms.holder_pubkeys;
let cp_pubkeys = &cp_params.pubkeys;
let holder_non_delayed_script = if opt_anchors {
get_to_countersigner_keyed_anchor_redeemscript(&holder_pubkeys.payment_point).to_p2wsh()
} else {
let bitcoin_key =
CompressedPublicKey::from_slice(&holder_pubkeys.payment_point.serialize()).unwrap();
let prog = WitnessProgram::p2wpkh(&bitcoin_key);
ScriptBuf::new_witness_program(&prog)
};
let holder_tx_keys = TxCreationKeys::derive_new(
secp_ctx,
&holder_per_commitment_point,
&holder_pubkeys.delayed_payment_basepoint,
&holder_pubkeys.htlc_basepoint,
&cp_pubkeys.revocation_basepoint,
&cp_pubkeys.htlc_basepoint,
);
let holder_delayed_redeem_script = get_revokeable_redeemscript(
&holder_tx_keys.revocation_key,
cp_params.selected_contest_delay,
&holder_tx_keys.broadcaster_delayed_payment_key,
);
let holder_delayed_script = holder_delayed_redeem_script.to_p2wsh();
let cp_delayed_script = if let Some(cp_per_commitment_point) = cp_per_commitment_point {
let cp_tx_keys = TxCreationKeys::derive_new(
secp_ctx,
&cp_per_commitment_point,
&cp_pubkeys.delayed_payment_basepoint,
&cp_pubkeys.htlc_basepoint,
&holder_pubkeys.revocation_basepoint,
&holder_pubkeys.htlc_basepoint,
);
let cp_delayed_redeem_script = get_revokeable_redeemscript(
&cp_tx_keys.revocation_key,
params.holder_selected_contest_delay,
&cp_tx_keys.broadcaster_delayed_payment_key,
);
Some(cp_delayed_redeem_script.to_p2wsh())
} else {
None
};
let mut htlcs = Vec::new();
let mut main_output_index = None;
for (idx, output) in tx.output.iter().enumerate() {
if output.value == ANCHOR_OUTPUT_VALUE_SATOSHI {
continue;
}
if Some(&output.script_pubkey) == cp_delayed_script.as_ref() {
continue;
}
if output.script_pubkey == holder_non_delayed_script
|| output.script_pubkey == holder_delayed_script
{
main_output_index = Some(idx as u32);
} else if output.script_pubkey.is_p2wsh() {
htlcs.push(idx as u32);
}
}
(main_output_index, htlcs)
}
pub fn decode_commitment_number(
tx: &Transaction,
params: &ChannelTransactionParameters,
) -> Option<u64> {
let holder_pubkeys = ¶ms.holder_pubkeys;
let cp_params = params.counterparty_parameters.as_ref().unwrap();
let cp_pubkeys = &cp_params.pubkeys;
let obscure_factor = get_commitment_transaction_number_obscure_factor(
&holder_pubkeys.payment_point,
&cp_pubkeys.payment_point,
params.is_outbound_from_holder,
);
if tx.input.len() != 1 {
return None;
}
if (tx.input[0].sequence.0 >> 8 * 3) as u8 != 0x80
|| (tx.lock_time.to_consensus_u32() >> 8 * 3) as u8 != 0x20
{
return None;
}
let commitment_number = (((tx.input[0].sequence.0 as u64 & 0xffffff) << 3 * 8)
| (tx.lock_time.to_consensus_u32() as u64 & 0xffffff))
^ obscure_factor;
Some(commitment_number)
}
pub fn create_spending_transaction(
descriptors: &[&SpendableOutputDescriptor],
outputs: Vec<TxOut>,
change_destination_script: ScriptBuf,
feerate_sats_per_1000_weight: u32,
) -> anyhow::Result<Transaction> {
let mut input = Vec::new();
let mut input_value = Amount::ZERO;
let mut witness_weight = 0;
let mut output_set = UnorderedSet::with_capacity(descriptors.len());
for outp in descriptors {
match outp {
SpendableOutputDescriptor::StaticPaymentOutput(descriptor) => {
input.push(TxIn {
previous_output: descriptor.outpoint.into_bitcoin_outpoint(),
script_sig: ScriptBuf::new(),
sequence: Sequence(1),
witness: Witness::new(),
});
witness_weight += StaticPaymentOutputDescriptor::max_witness_length(descriptor);
input_value = input_value.checked_add(descriptor.output.value).unwrap();
if !output_set.insert(descriptor.outpoint) {
return Err(anyhow!("duplicate"));
}
}
SpendableOutputDescriptor::DelayedPaymentOutput(descriptor) => {
input.push(TxIn {
previous_output: descriptor.outpoint.into_bitcoin_outpoint(),
script_sig: ScriptBuf::new(),
sequence: Sequence(descriptor.to_self_delay as u32),
witness: Witness::new(),
});
witness_weight += DelayedPaymentOutputDescriptor::MAX_WITNESS_LENGTH;
input_value = input_value.checked_add(descriptor.output.value).unwrap();
if !output_set.insert(descriptor.outpoint) {
return Err(anyhow!("duplicate"));
}
}
SpendableOutputDescriptor::StaticOutput {
ref outpoint,
ref output,
channel_keys_id: _,
} => {
input.push(TxIn {
previous_output: outpoint.into_bitcoin_outpoint(),
script_sig: ScriptBuf::new(),
sequence: Sequence::ZERO,
witness: Witness::default(),
});
witness_weight += 1 + 73 + 34;
input_value = input_value.checked_add(output.value).unwrap();
if !output_set.insert(*outpoint) {
return Err(anyhow!("duplicate"));
}
}
}
if input_value.to_sat() > MAX_VALUE_MSAT / 1000 {
return Err(anyhow!("overflow"));
}
}
let mut spend_tx = Transaction {
version: Version::TWO,
lock_time: bitcoin::absolute::LockTime::ZERO,
input,
output: outputs,
};
maybe_add_change_output(
&mut spend_tx,
input_value.to_sat(),
witness_weight,
feerate_sats_per_1000_weight,
change_destination_script,
)
.map_err(|()| anyhow!("could not add or change"))?;
Ok(spend_tx)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::channel::ChannelBase;
use crate::util::test_utils::{
init_node_and_channel, make_test_channel_setup, TEST_NODE_CONFIG, TEST_SEED,
};
use bitcoin::consensus::deserialize;
use bitcoin::hashes::hex::FromHex;
use bitcoin::secp256k1::SecretKey;
use lightning::ln::chan_utils::{htlc_success_tx_weight, htlc_timeout_tx_weight};
use lightning::types::features::ChannelTypeFeatures;
use bitcoin::blockdata::constants::genesis_block;
use bitcoin::blockdata::transaction::TxIn;
use bitcoin::hashes::Hash;
use bitcoin::secp256k1::Secp256k1;
use bitcoin::Network;
use bitcoin::{Amount, ScriptBuf, Transaction, TxOut, Witness};
use lightning::chain::transaction::OutPoint as LightningOutPoint;
use lightning::sign::{SpendableOutputDescriptor, StaticPaymentOutputDescriptor};
#[test]
fn test_parse_closing_tx_holder() {
let secp_ctx = Secp256k1::new();
let commitment_number = 0;
let (node, channel_id) =
init_node_and_channel(TEST_NODE_CONFIG, TEST_SEED[0], make_test_channel_setup());
let params = node
.with_channel(&channel_id, |channel| Ok(channel.make_channel_parameters()))
.unwrap();
let (holder_commitment, per_commitment_point) = node
.with_channel(&channel_id, |channel| {
let per_commitment_point = channel.get_per_commitment_point(commitment_number)?;
Ok((
channel.make_holder_commitment_tx(
commitment_number,
&per_commitment_point,
123,
1000,
100,
Vec::new(),
),
per_commitment_point,
))
})
.unwrap();
let holder_tx = holder_commitment.trust().built_transaction().transaction.clone();
let parsed_commitment_number = decode_commitment_number(&holder_tx, ¶ms).unwrap();
assert_eq!(parsed_commitment_number, commitment_number);
let (parsed_main, htlcs) =
decode_commitment_tx(&holder_tx, &per_commitment_point, &None, ¶ms, &secp_ctx);
let our_main =
holder_tx.output.iter().position(|txout| txout.script_pubkey.is_p2wsh()).unwrap();
assert_eq!(parsed_main, Some(our_main as u32));
assert!(htlcs.is_empty());
}
#[test]
fn test_parse_closing_tx_counterparty() {
let secp_ctx = Secp256k1::new();
let commitment_number = 0;
let (node, channel_id) =
init_node_and_channel(TEST_NODE_CONFIG, TEST_SEED[0], make_test_channel_setup());
let params = node
.with_channel(&channel_id, |channel| Ok(channel.make_channel_parameters()))
.unwrap();
let cp_per_commitment_secret = SecretKey::from_slice(&[2; 32]).unwrap();
let cp_per_commitment_point =
PublicKey::from_secret_key(&secp_ctx, &cp_per_commitment_secret);
let (cp_commitment, holder_per_commitment_point) = node
.with_channel(&channel_id, |channel| {
let holder_per_commitment_point =
channel.get_per_commitment_point(commitment_number)?;
Ok((
channel.make_counterparty_commitment_tx(
&cp_per_commitment_point,
commitment_number,
123,
1000,
100,
Vec::new(),
),
holder_per_commitment_point,
))
})
.unwrap();
let cp_tx = cp_commitment.trust().built_transaction().transaction.clone();
let parsed_commit_number = decode_commitment_number(&cp_tx, ¶ms).unwrap();
assert_eq!(parsed_commit_number, commitment_number);
let (parsed_main, htlcs) = decode_commitment_tx(
&cp_tx,
&holder_per_commitment_point,
&Some(cp_per_commitment_point),
¶ms,
&secp_ctx,
);
let our_main =
cp_tx.output.iter().position(|txout| txout.script_pubkey.is_p2wpkh()).unwrap();
assert_eq!(parsed_main, Some(our_main as u32));
println!("htlcs: {:?}", htlcs);
assert!(htlcs.is_empty());
}
#[test]
fn test_estimate_feerate() {
let non_anchor_features = ChannelTypeFeatures::empty();
let mut anchor_features = ChannelTypeFeatures::empty();
anchor_features.set_anchors_zero_fee_htlc_tx_optional();
let weights = vec![
htlc_timeout_tx_weight(&non_anchor_features),
htlc_timeout_tx_weight(&anchor_features),
htlc_success_tx_weight(&non_anchor_features),
htlc_success_tx_weight(&anchor_features),
];
let feerate = 253;
for weight in &weights {
let total_fee = (feerate as u64 * *weight) / 1000;
let estimated_feerate = super::estimate_feerate_per_kw(total_fee, *weight);
assert!(estimated_feerate >= 253);
}
for feerate in (300..5000).step_by(10) {
for weight in &weights {
let total_fee = (feerate as u64 * *weight) / 1000;
let estimated_feerate = super::estimate_feerate_per_kw(total_fee, *weight);
let recovered_total_fee = (estimated_feerate as u64 * *weight) / 1000;
assert_eq!(total_fee, recovered_total_fee);
}
}
}
#[test]
fn test_issue_165() {
let tx: Transaction = deserialize(&Vec::from_hex("0200000001b78e0523c17f8ac709eec54654cc849529c05584bfda6e04c92a3b670476f2a20000000000ffffffff017d4417000000000016001476168b09afc66bd3956efb25cd8b83650bda0c5f00000000").unwrap()).unwrap();
let tx_weight = tx.weight();
let spk = tx.output[0].script_pubkey.len();
let weight = super::mutual_close_tx_weight(&tx);
let fee = 1524999 - tx.output[0].value.to_sat();
let estimated_feerate = super::estimate_feerate_per_kw(fee, weight as u64);
let expected_tx_weight = (4 + 1 + 36 + 1 + 4 + 1 + 4 )*4 + ((8+1) + spk as u64) * 4; assert_eq!(expected_tx_weight, tx_weight.to_wu());
assert_eq!(estimated_feerate, 252);
}
fn create_mock_transaction(outputs: Vec<TxOut>) -> Transaction {
Transaction {
version: Version::TWO,
lock_time: bitcoin::absolute::LockTime::ZERO,
input: vec![TxIn {
previous_output: bitcoin::OutPoint::new(
genesis_block(Network::Bitcoin).txdata[0].compute_txid(),
0,
),
script_sig: ScriptBuf::new(),
sequence: Sequence::ZERO,
witness: Witness::new(),
}],
output: outputs,
}
}
fn create_mock_script() -> ScriptBuf {
let hash = bitcoin::WPubkeyHash::from_slice(&[
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
])
.unwrap();
ScriptBuf::new_p2wpkh(&hash)
}
#[test]
fn test_maybe_add_change_output_success() {
let mut tx = create_mock_transaction(vec![TxOut {
value: Amount::from_sat(100_000),
script_pubkey: create_mock_script(),
}]);
let input_value = 150_000;
let witness_max_weight = 100;
let feerate_sat_per_1000_weight = 1000;
let change_destination_script = create_mock_script();
let result = maybe_add_change_output(
&mut tx,
input_value,
witness_max_weight,
feerate_sat_per_1000_weight,
change_destination_script.clone(),
);
assert!(result.is_ok());
assert_eq!(tx.output.len(), 2);
assert_eq!(
tx.output[1].script_pubkey.to_string(),
"OP_0 OP_PUSHBYTES_20 0102030405060708090a0b0c0d0e0f1011121314"
);
assert_eq!(tx.output[1].value.to_sat(), 49446);
}
#[test]
fn test_maybe_add_change_output_input_too_large() {
let mut tx = create_mock_transaction(vec![TxOut {
value: Amount::from_sat(100_000),
script_pubkey: create_mock_script(),
}]);
let input_value = MAX_VALUE_MSAT / 1000 + 1;
let witness_max_weight = 100;
let feerate_sat_per_1000_weight = 1000;
let change_destination_script = create_mock_script();
let result = maybe_add_change_output(
&mut tx,
input_value,
witness_max_weight,
feerate_sat_per_1000_weight,
change_destination_script,
);
assert!(result.is_err());
}
#[test]
fn test_maybe_add_change_output_output_exceeds_input() {
let mut tx = create_mock_transaction(vec![TxOut {
value: Amount::from_sat(200_000),
script_pubkey: create_mock_script(),
}]);
let input_value = 100_000;
let witness_max_weight = 100;
let feerate_sat_per_1000_weight = 1000;
let change_destination_script = create_mock_script();
let result = maybe_add_change_output(
&mut tx,
input_value,
witness_max_weight,
feerate_sat_per_1000_weight,
change_destination_script,
);
assert!(result.is_err());
}
#[test]
fn test_maybe_add_change_output_insufficient_for_fee() {
let mut tx = create_mock_transaction(vec![TxOut {
value: Amount::from_sat(100_000),
script_pubkey: create_mock_script(),
}]);
let input_value = 100_100;
let witness_max_weight = 100;
let feerate_sat_per_1000_weight = 1000;
let change_destination_script = create_mock_script();
let result = maybe_add_change_output(
&mut tx,
input_value,
witness_max_weight,
feerate_sat_per_1000_weight,
change_destination_script,
);
assert!(result.is_err());
}
#[test]
fn test_create_spending_transaction_static_payment() {
let _secp = Secp256k1::new();
let bitcoin_outpoint =
bitcoin::OutPoint::new(genesis_block(Network::Bitcoin).txdata[0].compute_txid(), 0);
let outpoint =
LightningOutPoint { txid: bitcoin_outpoint.txid, index: bitcoin_outpoint.vout as u16 };
let descriptor =
SpendableOutputDescriptor::StaticPaymentOutput(StaticPaymentOutputDescriptor {
outpoint,
output: TxOut {
value: Amount::from_sat(100_000),
script_pubkey: create_mock_script(),
},
channel_keys_id: [0u8; 32],
channel_value_satoshis: 100_000,
channel_transaction_parameters: None,
});
let outputs =
vec![TxOut { value: Amount::from_sat(50_000), script_pubkey: create_mock_script() }];
let change_destination_script = create_mock_script();
let feerate_sats_per_1000_weight = 1000;
let result = create_spending_transaction(
&[&descriptor],
outputs.clone(),
change_destination_script.clone(),
feerate_sats_per_1000_weight,
);
assert!(result.is_ok());
let tx = result.unwrap();
assert_eq!(tx.input.len(), 1);
assert_eq!(
tx.input[0].previous_output.to_string(),
"4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:0"
);
assert_eq!(tx.input[0].sequence, Sequence(1));
assert_eq!(tx.output.len(), 2);
assert_eq!(tx.output[0], outputs[0]);
assert_eq!(
tx.output[1].script_pubkey.to_string(),
"OP_0 OP_PUSHBYTES_20 0102030405060708090a0b0c0d0e0f1011121314"
);
}
#[test]
fn test_create_spending_transaction_duplicate_outpoint() {
let _secp = Secp256k1::new();
let bitcoin_outpoint =
bitcoin::OutPoint::new(genesis_block(Network::Bitcoin).txdata[0].compute_txid(), 0);
let outpoint =
LightningOutPoint { txid: bitcoin_outpoint.txid, index: bitcoin_outpoint.vout as u16 };
let descriptor =
SpendableOutputDescriptor::StaticPaymentOutput(StaticPaymentOutputDescriptor {
outpoint,
output: TxOut {
value: Amount::from_sat(100_000),
script_pubkey: create_mock_script(),
},
channel_keys_id: [0u8; 32],
channel_value_satoshis: 100_000,
channel_transaction_parameters: None,
});
let outputs =
vec![TxOut { value: Amount::from_sat(50_000), script_pubkey: create_mock_script() }];
let change_destination_script = create_mock_script();
let feerate_sats_per_1000_weight = 1000;
let result = create_spending_transaction(
&[&descriptor, &descriptor],
outputs,
change_destination_script,
feerate_sats_per_1000_weight,
);
assert!(result.is_err());
assert_eq!(result.unwrap_err().to_string(), "duplicate");
}
#[test]
fn test_create_spending_transaction_value_overflow() {
let _secp = Secp256k1::new();
let bitcoin_outpoint =
bitcoin::OutPoint::new(genesis_block(Network::Bitcoin).txdata[0].compute_txid(), 0);
let outpoint =
LightningOutPoint { txid: bitcoin_outpoint.txid, index: bitcoin_outpoint.vout as u16 };
let descriptor =
SpendableOutputDescriptor::StaticPaymentOutput(StaticPaymentOutputDescriptor {
outpoint,
output: TxOut {
value: Amount::from_sat(MAX_VALUE_MSAT / 1000 + 1),
script_pubkey: create_mock_script(),
},
channel_keys_id: [0u8; 32],
channel_value_satoshis: MAX_VALUE_MSAT / 1000 + 1,
channel_transaction_parameters: None,
});
let outputs =
vec![TxOut { value: Amount::from_sat(50_000), script_pubkey: create_mock_script() }];
let change_destination_script = create_mock_script();
let feerate_sats_per_1000_weight = 1000;
let result = create_spending_transaction(
&[&descriptor],
outputs,
change_destination_script,
feerate_sats_per_1000_weight,
);
assert!(result.is_err());
assert_eq!(result.unwrap_err().to_string(), "overflow");
}
}