Skip to content

Commit 299b9bd

Browse files
committed
Add a test for the past few commits
This adds a single test for coin selection which exercises the issues fixed in the past three commits.
1 parent e8528d0 commit 299b9bd

File tree

2 files changed

+139
-0
lines changed

2 files changed

+139
-0
lines changed

lightning/src/events/bump_transaction.rs

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -852,3 +852,118 @@ where
852852
}
853853
}
854854
}
855+
856+
#[cfg(test)]
857+
mod tests {
858+
use super::*;
859+
860+
use crate::io::Cursor;
861+
use crate::ln::chan_utils::ChannelTransactionParameters;
862+
use crate::util::ser::Readable;
863+
use crate::util::test_utils::{TestBroadcaster, TestLogger};
864+
use crate::sign::KeysManager;
865+
866+
use bitcoin::hashes::Hash;
867+
use bitcoin::hex::FromHex;
868+
use bitcoin::transaction::Version;
869+
use bitcoin::locktime::absolute::LockTime;
870+
use bitcoin::{Network, ScriptBuf, Transaction, Txid};
871+
872+
struct TestCoinSelectionSource {
873+
// (commitment + anchor value, commitment + input weight, target feerate, result)
874+
expected_selects: Mutex<Vec<(u64, u64, u32, CoinSelection)>>,
875+
}
876+
impl CoinSelectionSource for TestCoinSelectionSource {
877+
fn select_confirmed_utxos(
878+
&self,
879+
_claim_id: ClaimId,
880+
must_spend: Vec<Input>,
881+
_must_pay_to: &[TxOut],
882+
target_feerate_sat_per_1000_weight: u32
883+
) -> Result<CoinSelection, ()> {
884+
let mut expected_selects = self.expected_selects.lock().unwrap();
885+
let (weight, value, feerate, res) = expected_selects.remove(0);
886+
assert_eq!(must_spend.len(), 1);
887+
assert_eq!(must_spend[0].satisfaction_weight, weight);
888+
assert_eq!(must_spend[0].previous_utxo.value.to_sat(), value);
889+
assert_eq!(target_feerate_sat_per_1000_weight, feerate);
890+
Ok(res)
891+
}
892+
fn sign_psbt(&self, psbt: Psbt) -> Result<Transaction, ()> {
893+
let mut tx = psbt.unsigned_tx;
894+
for input in tx.input.iter_mut() {
895+
if input.previous_output.txid != Txid::from_byte_array([44; 32]) {
896+
// Channel output, add a realistic size witness to make the assertions happy
897+
input.witness = Witness::from_slice(&[vec![42; 162]]);
898+
}
899+
}
900+
Ok(tx)
901+
}
902+
}
903+
904+
impl Drop for TestCoinSelectionSource {
905+
fn drop(&mut self) {
906+
assert!(self.expected_selects.lock().unwrap().is_empty());
907+
}
908+
}
909+
910+
#[test]
911+
fn test_op_return_under_funds() {
912+
// Test what happens if we have to select coins but the anchor output value itself suffices
913+
// to pay the required fee.
914+
//
915+
// This tests a case that occurred on mainnet (with the below transaction) where the target
916+
// feerate (of 868 sat/kW) was met by the anchor output's 330 sats alone. This caused the
917+
// use of an OP_RETURN which created a transaction which, at the time, was less than 64
918+
// bytes long (the current code generates a 65 byte transaction instead to meet
919+
// standardness rule). It also tests the handling of selection failure where we selected
920+
// coins which were insufficient once the OP_RETURN output was added, causing us to need to
921+
// select coins again with additional weight.
922+
923+
// Tx 18032ad172a5f28fa6e16392d6cc57ea47895781434ce15d03766cc47a955fb9
924+
let commitment_tx_bytes = Vec::<u8>::from_hex("02000000000101cc6b0a9dd84b52c07340fff6fab002fc37b4bdccfdce9f39c5ec8391a56b652907000000009b948b80044a01000000000000220020b4182433fdfdfbf894897c98f84d92cec815cee222755ffd000ae091c9dadc2d4a01000000000000220020f83f7dbf90e2de325b5bb6bab0ae370151278c6964739242b2e7ce0cb68a5d81cb4a02000000000022002024add256b3dccee772610caef82a601045ab6f98fd6d5df608cc756b891ccfe63ffa490000000000220020894bf32b37906a643625e87131897c3714c71b3ac9b161862c9aa6c8d468b4c70400473044022060abd347bff2cca0212b660e6addff792b3356bd4a1b5b26672dc2e694c3c5f002202b40b7e346b494a7b1d048b4ec33ba99c90a09ab48eb1df64ccdc768066c865c014730440220554d8361e04dc0ee178dcb23d2d23f53ec7a1ae4312a5be76bd9e83ab8981f3d0220501f23ffb18cb81ccea72d30252f88d5e69fd28ba4992803d03c00d06fa8899e0147522102817f6ce189ab7114f89e8d5df58cdbbaf272dc8e71b92982d47456a0b6a0ceee2102c9b4d2f24aca54f65e13f4c83e2a8d8e877e12d3c71a76e81f28a5cabc652aa352ae626c7620").unwrap();
925+
let commitment_tx: Transaction = Readable::read(&mut Cursor::new(&commitment_tx_bytes)).unwrap();
926+
let total_commitment_weight = commitment_tx.weight().to_wu() + ANCHOR_INPUT_WITNESS_WEIGHT + EMPTY_SCRIPT_SIG_WEIGHT;
927+
let commitment_and_anchor_fee = 930 + 330;
928+
let op_return_weight = TxOut {
929+
value: Amount::ZERO,
930+
script_pubkey: ScriptBuf::new_op_return(&[0; 3]),
931+
}.weight().to_wu();
932+
933+
let broadcaster = TestBroadcaster::new(Network::Testnet);
934+
let source = TestCoinSelectionSource {
935+
expected_selects: Mutex::new(vec![
936+
(total_commitment_weight, commitment_and_anchor_fee, 868, CoinSelection { confirmed_utxos: Vec::new(), change_output: None }),
937+
(total_commitment_weight + op_return_weight, commitment_and_anchor_fee, 868, CoinSelection {
938+
confirmed_utxos: vec![Utxo {
939+
outpoint: OutPoint { txid: Txid::from_byte_array([44; 32]), vout: 0 },
940+
output: TxOut { value: Amount::from_sat(200), script_pubkey: ScriptBuf::new() },
941+
satisfaction_weight: 5, // Just the script_sig and witness lengths
942+
}],
943+
change_output: None,
944+
})
945+
]),
946+
};
947+
let signer = KeysManager::new(&[42; 32], 42, 42);
948+
let logger = TestLogger::new();
949+
let handler = BumpTransactionEventHandler::new(&broadcaster, &source, &signer, &logger);
950+
951+
handler.handle_event(&BumpTransactionEvent::ChannelClose {
952+
channel_id: ChannelId([42; 32]),
953+
counterparty_node_id: PublicKey::from_slice(&[2; 33]).unwrap(),
954+
claim_id: ClaimId([42; 32]),
955+
package_target_feerate_sat_per_1000_weight: 868,
956+
commitment_tx_fee_satoshis: 930,
957+
commitment_tx,
958+
anchor_descriptor: AnchorDescriptor {
959+
channel_derivation_parameters: ChannelDerivationParameters {
960+
value_satoshis: 42_000_000,
961+
keys_id: [42; 32],
962+
transaction_parameters: ChannelTransactionParameters::test_dummy(),
963+
},
964+
outpoint: OutPoint { txid: Txid::from_byte_array([42; 32]), vout: 0 },
965+
},
966+
pending_htlcs: Vec::new(),
967+
});
968+
}
969+
}

lightning/src/ln/chan_utils.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -924,6 +924,30 @@ impl ChannelTransactionParameters {
924924
holder_is_broadcaster: false
925925
}
926926
}
927+
928+
#[cfg(test)]
929+
pub fn test_dummy() -> Self {
930+
let dummy_keys = ChannelPublicKeys {
931+
funding_pubkey: PublicKey::from_slice(&[2; 33]).unwrap(),
932+
revocation_basepoint: PublicKey::from_slice(&[2; 33]).unwrap().into(),
933+
payment_point: PublicKey::from_slice(&[2; 33]).unwrap(),
934+
delayed_payment_basepoint: PublicKey::from_slice(&[2; 33]).unwrap().into(),
935+
htlc_basepoint: PublicKey::from_slice(&[2; 33]).unwrap().into(),
936+
};
937+
Self {
938+
holder_pubkeys: dummy_keys.clone(),
939+
holder_selected_contest_delay: 42,
940+
is_outbound_from_holder: true,
941+
counterparty_parameters: Some(CounterpartyChannelTransactionParameters {
942+
pubkeys: dummy_keys,
943+
selected_contest_delay: 42,
944+
}),
945+
funding_outpoint: Some(chain::transaction::OutPoint {
946+
txid: Txid::from_byte_array([42; 32]), index: 0
947+
}),
948+
channel_type_features: ChannelTypeFeatures::empty(),
949+
}
950+
}
927951
}
928952

929953
impl_writeable_tlv_based!(CounterpartyChannelTransactionParameters, {

0 commit comments

Comments
 (0)