diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f4ca7f4c7a4..d8cfe44dfe4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -46,6 +46,10 @@ jobs: - name: Install Rust ${{ matrix.toolchain }} toolchain run: | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile=minimal --default-toolchain ${{ matrix.toolchain }} + - name: Use rust-lld linker on Windows + if: matrix.platform == 'windows-latest' + shell: bash + run: echo "RUSTFLAGS=-C linker=rust-lld" >> "$GITHUB_ENV" - name: Install no-std-check dependencies for ARM Embedded if: "matrix.platform == 'self-hosted'" run: | diff --git a/Cargo.toml b/Cargo.toml index 3891b11a2b4..b89127b4f94 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,6 +66,6 @@ check-cfg = [ "cfg(taproot)", "cfg(require_route_graph_test)", "cfg(splicing)", - "cfg(async_payments)", "cfg(simple_close)", + "cfg(peer_storage)", ] diff --git a/ci/check-lint.sh b/ci/check-lint.sh index 09b328008f6..f197784610a 100755 --- a/ci/check-lint.sh +++ b/ci/check-lint.sh @@ -5,6 +5,8 @@ set -x CLIPPY() { # shellcheck disable=SC2086 RUSTFLAGS='-D warnings' cargo clippy $1 -- $2 \ + `# https://github.com/rust-lang/rust-clippy/issues/15442` \ + -A unused_imports \ `# Things clippy defaults to allowing but we should avoid` \ -D clippy::clone_on_ref_ptr \ `# Things where clippy is just wrong` \ diff --git a/ci/ci-tests.sh b/ci/ci-tests.sh index 2ab512e2d3e..1c8a53602c1 100755 --- a/ci/ci-tests.sh +++ b/ci/ci-tests.sh @@ -158,3 +158,5 @@ RUSTFLAGS="--cfg=async_payments" cargo test --verbose --color always -p lightnin RUSTFLAGS="--cfg=simple_close" cargo test --verbose --color always -p lightning [ "$CI_MINIMIZE_DISK_USAGE" != "" ] && cargo clean RUSTFLAGS="--cfg=lsps1_service" cargo test --verbose --color always -p lightning-liquidity +[ "$CI_MINIMIZE_DISK_USAGE" != "" ] && cargo clean +RUSTFLAGS="--cfg=peer_storage" cargo test --verbose --color always -p lightning diff --git a/fuzz/src/bin/gen_target.sh b/fuzz/src/bin/gen_target.sh index 18f715e149a..0f7f9c9210d 100755 --- a/fuzz/src/bin/gen_target.sh +++ b/fuzz/src/bin/gen_target.sh @@ -14,6 +14,7 @@ GEN_TEST invoice_deser GEN_TEST invoice_request_deser GEN_TEST offer_deser GEN_TEST bolt11_deser +GEN_TEST static_invoice_deser GEN_TEST onion_message GEN_TEST peer_crypt GEN_TEST process_network_graph diff --git a/fuzz/src/bin/static_invoice_deser_target.rs b/fuzz/src/bin/static_invoice_deser_target.rs new file mode 100644 index 00000000000..573f0aa0b22 --- /dev/null +++ b/fuzz/src/bin/static_invoice_deser_target.rs @@ -0,0 +1,120 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +// This file is auto-generated by gen_target.sh based on target_template.txt +// To modify it, modify target_template.txt and run gen_target.sh instead. + +#![cfg_attr(feature = "libfuzzer_fuzz", no_main)] +#![cfg_attr(rustfmt, rustfmt_skip)] + +#[cfg(not(fuzzing))] +compile_error!("Fuzz targets need cfg=fuzzing"); + +#[cfg(not(hashes_fuzz))] +compile_error!("Fuzz targets need cfg=hashes_fuzz"); + +#[cfg(not(secp256k1_fuzz))] +compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); + +extern crate lightning_fuzz; +use lightning_fuzz::static_invoice_deser::*; + +#[cfg(feature = "afl")] +#[macro_use] extern crate afl; +#[cfg(feature = "afl")] +fn main() { + fuzz!(|data| { + static_invoice_deser_run(data.as_ptr(), data.len()); + }); +} + +#[cfg(feature = "honggfuzz")] +#[macro_use] extern crate honggfuzz; +#[cfg(feature = "honggfuzz")] +fn main() { + loop { + fuzz!(|data| { + static_invoice_deser_run(data.as_ptr(), data.len()); + }); + } +} + +#[cfg(feature = "libfuzzer_fuzz")] +#[macro_use] extern crate libfuzzer_sys; +#[cfg(feature = "libfuzzer_fuzz")] +fuzz_target!(|data: &[u8]| { + static_invoice_deser_run(data.as_ptr(), data.len()); +}); + +#[cfg(feature = "stdin_fuzz")] +fn main() { + use std::io::Read; + + let mut data = Vec::with_capacity(8192); + std::io::stdin().read_to_end(&mut data).unwrap(); + static_invoice_deser_run(data.as_ptr(), data.len()); +} + +#[test] +fn run_test_cases() { + use std::fs; + use std::io::Read; + use lightning_fuzz::utils::test_logger::StringBuffer; + + use std::sync::{atomic, Arc}; + { + let data: Vec = vec![0]; + static_invoice_deser_run(data.as_ptr(), data.len()); + } + let mut threads = Vec::new(); + let threads_running = Arc::new(atomic::AtomicUsize::new(0)); + if let Ok(tests) = fs::read_dir("test_cases/static_invoice_deser") { + for test in tests { + let mut data: Vec = Vec::new(); + let path = test.unwrap().path(); + fs::File::open(&path).unwrap().read_to_end(&mut data).unwrap(); + threads_running.fetch_add(1, atomic::Ordering::AcqRel); + + let thread_count_ref = Arc::clone(&threads_running); + let main_thread_ref = std::thread::current(); + threads.push((path.file_name().unwrap().to_str().unwrap().to_string(), + std::thread::spawn(move || { + let string_logger = StringBuffer::new(); + + let panic_logger = string_logger.clone(); + let res = if ::std::panic::catch_unwind(move || { + static_invoice_deser_test(&data, panic_logger); + }).is_err() { + Some(string_logger.into_string()) + } else { None }; + thread_count_ref.fetch_sub(1, atomic::Ordering::AcqRel); + main_thread_ref.unpark(); + res + }) + )); + while threads_running.load(atomic::Ordering::Acquire) > 32 { + std::thread::park(); + } + } + } + let mut failed_outputs = Vec::new(); + for (test, thread) in threads.drain(..) { + if let Some(output) = thread.join().unwrap() { + println!("\nOutput of {}:\n{}\n", test, output); + failed_outputs.push(test); + } + } + if !failed_outputs.is_empty() { + println!("Test cases which failed: "); + for case in failed_outputs { + println!("{}", case); + } + panic!(); + } +} diff --git a/fuzz/src/full_stack.rs b/fuzz/src/full_stack.rs index eb9d51d487d..88e68e5e01d 100644 --- a/fuzz/src/full_stack.rs +++ b/fuzz/src/full_stack.rs @@ -79,9 +79,11 @@ use bitcoin::secp256k1::schnorr; use bitcoin::secp256k1::{self, Message, PublicKey, Scalar, Secp256k1, SecretKey}; use lightning::util::dyn_signer::DynSigner; + +use std::collections::VecDeque; use std::cell::RefCell; use std::cmp; -use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering}; +use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering}; use std::sync::{Arc, Mutex}; #[inline] @@ -110,7 +112,7 @@ pub fn slice_to_be24(v: &[u8]) -> u32 { struct InputData { data: Vec, read_pos: AtomicUsize, - halt_fee_est_reads: AtomicBool, + fee_estimates: Mutex>, } impl InputData { fn get_slice(&self, len: usize) -> Option<&[u8]> { @@ -137,14 +139,10 @@ struct FuzzEstimator { } impl FeeEstimator for FuzzEstimator { fn get_est_sat_per_1000_weight(&self, _: ConfirmationTarget) -> u32 { - if self.input.halt_fee_est_reads.load(Ordering::Acquire) { - return 253; - } - //TODO: We should actually be testing at least much more than 64k... - match self.input.get_slice(2) { - Some(slice) => cmp::max(slice_to_be16(slice) as u32, 253), - None => 253, + if let Some(val) = self.input.fee_estimates.lock().unwrap().pop_front() { + return val; } + return 253; } } @@ -377,16 +375,12 @@ struct KeyProvider { inbound_payment_key: ExpandedKey, counter: AtomicU64, signer_state: RefCell>)>>, + rng_output: RefCell<[u8; 32]>, } impl EntropySource for KeyProvider { fn get_secure_random_bytes(&self) -> [u8; 32] { - let ctr = self.counter.fetch_add(1, Ordering::Relaxed); - #[rustfmt::skip] - let random_bytes = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - (ctr >> 8*7) as u8, (ctr >> 8*6) as u8, (ctr >> 8*5) as u8, (ctr >> 8*4) as u8, - (ctr >> 8*3) as u8, (ctr >> 8*2) as u8, (ctr >> 8*1) as u8, 14, (ctr >> 8*0) as u8]; - random_bytes + *self.rng_output.borrow() } } @@ -539,7 +533,7 @@ pub fn do_test(mut data: &[u8], logger: &Arc) { let input = Arc::new(InputData { data: data.to_vec(), read_pos: AtomicUsize::new(0), - halt_fee_est_reads: AtomicBool::new(false), + fee_estimates: Mutex::new(VecDeque::new()), }); let fee_est = Arc::new(FuzzEstimator { input: input.clone() }); let router = FuzzRouter {}; @@ -585,6 +579,7 @@ pub fn do_test(mut data: &[u8], logger: &Arc) { inbound_payment_key: ExpandedKey::new(inbound_payment_key), counter: AtomicU64::new(0), signer_state: RefCell::new(new_hash_map()), + rng_output: RefCell::new([42; 32]), }); let monitor = Arc::new(chainmonitor::ChainMonitor::new( @@ -862,32 +857,24 @@ pub fn do_test(mut data: &[u8], logger: &Arc) { } if tx.version.0 <= 0xff && !channels.is_empty() { let chans = channels.iter().map(|(a, b)| (a, b)).collect::>(); - if let Err(e) = - channelmanager.batch_funding_transaction_generated(&chans, tx.clone()) - { - // It's possible the channel has been closed in the mean time, but any other - // failure may be a bug. - if let APIError::ChannelUnavailable { .. } = e { - } else { - panic!(); + let res = + channelmanager.batch_funding_transaction_generated(&chans, tx.clone()); + if res.is_ok() { + let funding_txid = tx.compute_txid(); + for idx in 0..tx.output.len() { + let outpoint = OutPoint { txid: funding_txid, index: idx as u16 }; + pending_funding_signatures.insert(outpoint, tx.clone()); } } - let funding_txid = tx.compute_txid(); - for idx in 0..tx.output.len() { - let outpoint = OutPoint { txid: funding_txid, index: idx as u16 }; - pending_funding_signatures.insert(outpoint, tx.clone()); - } } }, 11 => { let mut txn = broadcast.txn_broadcasted.lock().unwrap().split_off(0); if !txn.is_empty() { - input.halt_fee_est_reads.store(true, Ordering::Release); loss_detector.connect_block(&txn[..]); for _ in 2..100 { loss_detector.connect_block(&[]); } - input.halt_fee_est_reads.store(false, Ordering::Release); } for tx in txn.drain(..) { loss_detector.funding_txn.push(tx); @@ -986,6 +973,15 @@ pub fn do_test(mut data: &[u8], logger: &Arc) { ); } }, + 48 => { + let fee = u32::from_le_bytes(get_slice!(4).try_into().unwrap()); + input.fee_estimates.lock().unwrap().push_back(fee); + }, + 49 => { + let mut rng_output = [0; 32]; + rng_output.copy_from_slice(&get_slice!(32)); + *keys_manager.rng_output.borrow_mut() = rng_output; + }, _ => return, } loss_detector.handler.process_events(); @@ -1084,8 +1080,6 @@ fn two_peer_forwarding_seed() -> Vec { // rest of open_channel and mac ext_from_hex("030000000000000000000000000000000000000000000000000000000000000005 020900000000000000000000000000000000000000000000000000000000000000 01 0000 01021000 03000000000000000000000000000000", &mut test); - // One feerate request returning min feerate, which our open_channel also uses (ingested by FuzzEstimator) - ext_from_hex("00fd", &mut test); // client should now respond with accept_channel (CHECK 1: type 33 to peer 03000000) // inbound read from peer id 0 of len 18 @@ -1095,53 +1089,27 @@ fn two_peer_forwarding_seed() -> Vec { // inbound read from peer id 0 of len 148 ext_from_hex("030094", &mut test); // funding_created and mac - ext_from_hex("0022 ff4f00f805273c1b203bb5ebf8436bfde57b3be8c2f5e95d9491dbb181909679 3d00000000000000000000000000000000000000000000000000000000000000 0000 00000000000000000000000000000000000000000000000000000000000000210100000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000", &mut test); + ext_from_hex("0022 ff4f00f805273c1b203bb5ebf8436bfde57b3be8c2f5e95d9491dbb181909679 c000000000000000000000000000000000000000000000000000000000000000 0000 00000000000000000000000000000000000000000000000000000000000000dc0100000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000", &mut test); // client should now respond with funding_signed (CHECK 2: type 35 to peer 03000000) // connect a block with one transaction of len 94 ext_from_hex("0c005e", &mut test); // the funding transaction - ext_from_hex("020000000100000000000000000000000000000000000000000000000000000000000000000000000000ffffffff0150c3000000000000220020ae0000000000000000000000000000000000000000000000000000000000000000000000", &mut test); - // Two feerate requests during block connection - ext_from_hex("00fd00fd", &mut test); + ext_from_hex("020000000100000000000000000000000000000000000000000000000000000000000000000000000000ffffffff0150c3000000000000220020530000000000000000000000000000000000000000000000000000000000000000000000", &mut test); // connect a block with no transactions, one per line ext_from_hex("0c0000", &mut test); - // Two feerate requests during block connection - ext_from_hex("00fd00fd", &mut test); ext_from_hex("0c0000", &mut test); - // Two feerate requests during block connection - ext_from_hex("00fd00fd", &mut test); ext_from_hex("0c0000", &mut test); - // Two feerate requests during block connection - ext_from_hex("00fd00fd", &mut test); ext_from_hex("0c0000", &mut test); - // Two feerate requests during block connection - ext_from_hex("00fd00fd", &mut test); ext_from_hex("0c0000", &mut test); - // Two feerate requests during block connection - ext_from_hex("00fd00fd", &mut test); ext_from_hex("0c0000", &mut test); - // Two feerate requests during block connection - ext_from_hex("00fd00fd", &mut test); ext_from_hex("0c0000", &mut test); - // Two feerate requests during block connection - ext_from_hex("00fd00fd", &mut test); ext_from_hex("0c0000", &mut test); - // Two feerate requests during block connection - ext_from_hex("00fd00fd", &mut test); ext_from_hex("0c0000", &mut test); - // Two feerate requests during block connection - ext_from_hex("00fd00fd", &mut test); ext_from_hex("0c0000", &mut test); - // Two feerate requests during block connection - ext_from_hex("00fd00fd", &mut test); ext_from_hex("0c0000", &mut test); - // Two feerate requests during block connection - ext_from_hex("00fd00fd", &mut test); ext_from_hex("0c0000", &mut test); - // Two feerate requests during block connection - ext_from_hex("00fd00fd", &mut test); - // by now client should have sent a channel_ready (CHECK 3: SendChannelReady to 03000000 for chan 3d000000) + // by now client should have sent a channel_ready (CHECK 3: SendChannelReady to 03000000 for chan c0000000) // inbound read from peer id 0 of len 18 ext_from_hex("030012", &mut test); @@ -1150,7 +1118,7 @@ fn two_peer_forwarding_seed() -> Vec { // inbound read from peer id 0 of len 83 ext_from_hex("030053", &mut test); // channel_ready and mac - ext_from_hex("0024 3d00000000000000000000000000000000000000000000000000000000000000 020800000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000", &mut test); + ext_from_hex("0024 c000000000000000000000000000000000000000000000000000000000000000 020800000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000", &mut test); // new inbound connection with id 1 ext_from_hex("01", &mut test); @@ -1177,8 +1145,6 @@ fn two_peer_forwarding_seed() -> Vec { "05 01 030200000000000000000000000000000000000000000000000000000000000000 00c350 0003e8", &mut test, ); - // One feerate requests (all returning min feerate) (gonna be ingested by FuzzEstimator) - ext_from_hex("00fd", &mut test); // inbound read from peer id 1 of len 18 ext_from_hex("030112", &mut test); @@ -1187,7 +1153,7 @@ fn two_peer_forwarding_seed() -> Vec { // inbound read from peer id 1 of len 255 ext_from_hex("0301ff", &mut test); // beginning of accept_channel - ext_from_hex("0021 0000000000000000000000000000000000000000000000000000000000000e12 0000000000000162 00000000004c4b40 00000000000003e8 00000000000003e8 00000002 03f0 0005 030000000000000000000000000000000000000000000000000000000000000100 030000000000000000000000000000000000000000000000000000000000000200 030000000000000000000000000000000000000000000000000000000000000300 030000000000000000000000000000000000000000000000000000000000000400 030000000000000000000000000000000000000000000000000000000000000500 02660000000000000000000000000000", &mut test); + ext_from_hex("0021 2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a 0000000000000162 00000000004c4b40 00000000000003e8 00000000000003e8 00000002 03f0 0005 030000000000000000000000000000000000000000000000000000000000000100 030000000000000000000000000000000000000000000000000000000000000200 030000000000000000000000000000000000000000000000000000000000000300 030000000000000000000000000000000000000000000000000000000000000400 030000000000000000000000000000000000000000000000000000000000000500 02660000000000000000000000000000", &mut test); // inbound read from peer id 1 of len 39 ext_from_hex("030127", &mut test); // rest of accept_channel and mac @@ -1198,8 +1164,6 @@ fn two_peer_forwarding_seed() -> Vec { // create the funding transaction (client should send funding_created now) ext_from_hex("0a", &mut test); - // Two feerate requests to check the dust exposure on the initial commitment tx - ext_from_hex("00fd00fd", &mut test); // inbound read from peer id 1 of len 18 ext_from_hex("030112", &mut test); @@ -1208,7 +1172,7 @@ fn two_peer_forwarding_seed() -> Vec { // inbound read from peer id 1 of len 114 ext_from_hex("030172", &mut test); // funding_signed message and mac - ext_from_hex("0023 2900000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000b90001000000000000000000000000000000000000000000000000000000000000 01000000000000000000000000000000", &mut test); + ext_from_hex("0023 c400000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000310001000000000000000000000000000000000000000000000000000000000000 01000000000000000000000000000000", &mut test); // broadcast funding transaction ext_from_hex("0b", &mut test); @@ -1221,7 +1185,7 @@ fn two_peer_forwarding_seed() -> Vec { // inbound read from peer id 1 of len 83 ext_from_hex("030153", &mut test); // channel_ready and mac - ext_from_hex("0024 2900000000000000000000000000000000000000000000000000000000000000 026700000000000000000000000000000000000000000000000000000000000000 01000000000000000000000000000000", &mut test); + ext_from_hex("0024 c400000000000000000000000000000000000000000000000000000000000000 026700000000000000000000000000000000000000000000000000000000000000 01000000000000000000000000000000", &mut test); // inbound read from peer id 0 of len 18 ext_from_hex("030012", &mut test); @@ -1230,7 +1194,7 @@ fn two_peer_forwarding_seed() -> Vec { // inbound read from peer id 0 of len 255 ext_from_hex("0300ff", &mut test); // beginning of update_add_htlc from 0 to 1 via client - ext_from_hex("0080 3d00000000000000000000000000000000000000000000000000000000000000 0000000000000000 0000000000003e80 ff00000000000000000000000000000000000000000000000000000000000000 000003f0 00 030000000000000000000000000000000000000000000000000000000000000555 11 020203e8 0401a0 060800000e0000010000 0a00000000000000000000000000000000000000000000000000000000000000 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", &mut test); + ext_from_hex("0080 c000000000000000000000000000000000000000000000000000000000000000 0000000000000000 0000000000003e80 ff00000000000000000000000000000000000000000000000000000000000000 000003f0 00 030000000000000000000000000000000000000000000000000000000000000555 11 020203e8 0401a0 060800000e0000010000 0a00000000000000000000000000000000000000000000000000000000000000 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", &mut test); // inbound read from peer id 0 of len 255 ext_from_hex("0300ff", &mut test); ext_from_hex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", &mut test); @@ -1248,9 +1212,6 @@ fn two_peer_forwarding_seed() -> Vec { // end of update_add_htlc from 0 to 1 via client and mac ext_from_hex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff ab00000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000", &mut test); - // One feerate request to check dust exposure - ext_from_hex("00fd", &mut test); - // inbound read from peer id 0 of len 18 ext_from_hex("030012", &mut test); // message header indicating message length 100 @@ -1258,7 +1219,7 @@ fn two_peer_forwarding_seed() -> Vec { // inbound read from peer id 0 of len 116 ext_from_hex("030074", &mut test); // commitment_signed and mac - ext_from_hex("0084 3d00000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000300100000000000000000000000000000000000000000000000000000000000000 0000 03000000000000000000000000000000", &mut test); + ext_from_hex("0084 c000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000cd0100000000000000000000000000000000000000000000000000000000000000 0000 03000000000000000000000000000000", &mut test); // client should now respond with revoke_and_ack and commitment_signed (CHECK 5/6: types 133 and 132 to peer 03000000) // inbound read from peer id 0 of len 18 @@ -1268,12 +1229,10 @@ fn two_peer_forwarding_seed() -> Vec { // inbound read from peer id 0 of len 115 ext_from_hex("030073", &mut test); // revoke_and_ack and mac - ext_from_hex("0085 3d00000000000000000000000000000000000000000000000000000000000000 0900000000000000000000000000000000000000000000000000000000000000 020b00000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000", &mut test); + ext_from_hex("0085 c000000000000000000000000000000000000000000000000000000000000000 0900000000000000000000000000000000000000000000000000000000000000 020b00000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000", &mut test); // process the now-pending HTLC forward ext_from_hex("07", &mut test); - // Four feerate requests to check dust exposure while forwarding the HTLC - ext_from_hex("00fd00fd00fd00fd", &mut test); // client now sends id 1 update_add_htlc and commitment_signed (CHECK 7: UpdateHTLCs event for node 03020000 with 1 HTLCs for channel 2f000000) // we respond with commitment_signed then revoke_and_ack (a weird, but valid, order) @@ -1284,7 +1243,7 @@ fn two_peer_forwarding_seed() -> Vec { // inbound read from peer id 1 of len 116 ext_from_hex("030174", &mut test); // commitment_signed and mac - ext_from_hex("0084 2900000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000e40001000000000000000000000000000000000000000000000000000000000000 0000 01000000000000000000000000000000", &mut test); + ext_from_hex("0084 c400000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000720001000000000000000000000000000000000000000000000000000000000000 0000 01000000000000000000000000000000", &mut test); // // inbound read from peer id 1 of len 18 ext_from_hex("030112", &mut test); @@ -1293,7 +1252,7 @@ fn two_peer_forwarding_seed() -> Vec { // inbound read from peer id 1 of len 115 ext_from_hex("030173", &mut test); // revoke_and_ack and mac - ext_from_hex("0085 2900000000000000000000000000000000000000000000000000000000000000 6600000000000000000000000000000000000000000000000000000000000000 026400000000000000000000000000000000000000000000000000000000000000 01000000000000000000000000000000", &mut test); + ext_from_hex("0085 c400000000000000000000000000000000000000000000000000000000000000 6600000000000000000000000000000000000000000000000000000000000000 026400000000000000000000000000000000000000000000000000000000000000 01000000000000000000000000000000", &mut test); // // inbound read from peer id 1 of len 18 ext_from_hex("030112", &mut test); @@ -1302,8 +1261,8 @@ fn two_peer_forwarding_seed() -> Vec { // inbound read from peer id 1 of len 90 ext_from_hex("03015a", &mut test); // update_fulfill_htlc and mac - ext_from_hex("0082 2900000000000000000000000000000000000000000000000000000000000000 0000000000000000 ff00888888888888888888888888888888888888888888888888888888888888 01000000000000000000000000000000", &mut test); - // client should immediately claim the pending HTLC from peer 0 (CHECK 8: SendFulfillHTLCs for node 03000000 with preimage ff00888888 for channel 3d000000) + ext_from_hex("0082 c400000000000000000000000000000000000000000000000000000000000000 0000000000000000 ff00888888888888888888888888888888888888888888888888888888888888 01000000000000000000000000000000", &mut test); + // client should immediately claim the pending HTLC from peer 0 (CHECK 8: SendFulfillHTLCs for node 03000000 with preimage ff00888888 for channel c0000000) // inbound read from peer id 1 of len 18 ext_from_hex("030112", &mut test); @@ -1312,7 +1271,7 @@ fn two_peer_forwarding_seed() -> Vec { // inbound read from peer id 1 of len 116 ext_from_hex("030174", &mut test); // commitment_signed and mac - ext_from_hex("0084 2900000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000330001000000000000000000000000000000000000000000000000000000000000 0000 01000000000000000000000000000000", &mut test); + ext_from_hex("0084 c400000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000700001000000000000000000000000000000000000000000000000000000000000 0000 01000000000000000000000000000000", &mut test); // inbound read from peer id 1 of len 18 ext_from_hex("030112", &mut test); @@ -1321,7 +1280,7 @@ fn two_peer_forwarding_seed() -> Vec { // inbound read from peer id 1 of len 115 ext_from_hex("030173", &mut test); // revoke_and_ack and mac - ext_from_hex("0085 2900000000000000000000000000000000000000000000000000000000000000 6700000000000000000000000000000000000000000000000000000000000000 026500000000000000000000000000000000000000000000000000000000000000 01000000000000000000000000000000", &mut test); + ext_from_hex("0085 c400000000000000000000000000000000000000000000000000000000000000 6700000000000000000000000000000000000000000000000000000000000000 026500000000000000000000000000000000000000000000000000000000000000 01000000000000000000000000000000", &mut test); // before responding to the commitment_signed generated above, send a new HTLC // inbound read from peer id 0 of len 18 @@ -1331,7 +1290,7 @@ fn two_peer_forwarding_seed() -> Vec { // inbound read from peer id 0 of len 255 ext_from_hex("0300ff", &mut test); // beginning of update_add_htlc from 0 to 1 via client - ext_from_hex("0080 3d00000000000000000000000000000000000000000000000000000000000000 0000000000000001 0000000000003e80 ff00000000000000000000000000000000000000000000000000000000000000 000003f0 00 030000000000000000000000000000000000000000000000000000000000000555 11 020203e8 0401a0 060800000e0000010000 0a00000000000000000000000000000000000000000000000000000000000000 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", &mut test); + ext_from_hex("0080 c000000000000000000000000000000000000000000000000000000000000000 0000000000000001 0000000000003e80 ff00000000000000000000000000000000000000000000000000000000000000 000003f0 00 030000000000000000000000000000000000000000000000000000000000000555 11 020203e8 0401a0 060800000e0000010000 0a00000000000000000000000000000000000000000000000000000000000000 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", &mut test); // inbound read from peer id 0 of len 255 ext_from_hex("0300ff", &mut test); ext_from_hex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", &mut test); @@ -1349,9 +1308,6 @@ fn two_peer_forwarding_seed() -> Vec { // end of update_add_htlc from 0 to 1 via client and mac ext_from_hex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff ab00000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000", &mut test); - // One feerate request to check dust exposure - ext_from_hex("00fd", &mut test); - // now respond to the update_fulfill_htlc+commitment_signed messages the client sent to peer 0 // inbound read from peer id 0 of len 18 ext_from_hex("030012", &mut test); @@ -1360,7 +1316,7 @@ fn two_peer_forwarding_seed() -> Vec { // inbound read from peer id 0 of len 115 ext_from_hex("030073", &mut test); // revoke_and_ack and mac - ext_from_hex("0085 3d00000000000000000000000000000000000000000000000000000000000000 0800000000000000000000000000000000000000000000000000000000000000 020a00000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000", &mut test); + ext_from_hex("0085 c000000000000000000000000000000000000000000000000000000000000000 0800000000000000000000000000000000000000000000000000000000000000 020a00000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000", &mut test); // client should now respond with revoke_and_ack and commitment_signed (CHECK 5/6 duplicates) // inbound read from peer id 0 of len 18 @@ -1370,7 +1326,7 @@ fn two_peer_forwarding_seed() -> Vec { // inbound read from peer id 0 of len 116 ext_from_hex("030074", &mut test); // commitment_signed and mac - ext_from_hex("0084 3d00000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000c30100000000000000000000000000000000000000000000000000000000000000 0000 03000000000000000000000000000000", &mut test); + ext_from_hex("0084 c000000000000000000000000000000000000000000000000000000000000000 000000000000000000000000000000000000000000000000000000000000003e0100000000000000000000000000000000000000000000000000000000000000 0000 03000000000000000000000000000000", &mut test); // inbound read from peer id 0 of len 18 ext_from_hex("030012", &mut test); @@ -1379,14 +1335,11 @@ fn two_peer_forwarding_seed() -> Vec { // inbound read from peer id 0 of len 115 ext_from_hex("030073", &mut test); // revoke_and_ack and mac - ext_from_hex("0085 3d00000000000000000000000000000000000000000000000000000000000000 0b00000000000000000000000000000000000000000000000000000000000000 020d00000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000", &mut test); + ext_from_hex("0085 c000000000000000000000000000000000000000000000000000000000000000 0b00000000000000000000000000000000000000000000000000000000000000 020d00000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000", &mut test); // process the now-pending HTLC forward ext_from_hex("07", &mut test); - // Four feerate requests to check dust exposure while forwarding the HTLC - ext_from_hex("00fd00fd00fd00fd", &mut test); - // client now sends id 1 update_add_htlc and commitment_signed (CHECK 7 duplicate) // we respond with revoke_and_ack, then commitment_signed, then update_fail_htlc @@ -1397,7 +1350,7 @@ fn two_peer_forwarding_seed() -> Vec { // inbound read from peer id 1 of len 116 ext_from_hex("030174", &mut test); // commitment_signed and mac - ext_from_hex("0084 2900000000000000000000000000000000000000000000000000000000000000 000000000000000000000000000000000000000000000000000000000000009c0001000000000000000000000000000000000000000000000000000000000000 0000 01000000000000000000000000000000", &mut test); + ext_from_hex("0084 c400000000000000000000000000000000000000000000000000000000000000 000000000000000000000000000000000000000000000000000000000000007e0001000000000000000000000000000000000000000000000000000000000000 0000 01000000000000000000000000000000", &mut test); // inbound read from peer id 1 of len 18 ext_from_hex("030112", &mut test); @@ -1406,7 +1359,7 @@ fn two_peer_forwarding_seed() -> Vec { // inbound read from peer id 1 of len 115 ext_from_hex("030173", &mut test); // revoke_and_ack and mac - ext_from_hex("0085 2900000000000000000000000000000000000000000000000000000000000000 6400000000000000000000000000000000000000000000000000000000000000 027000000000000000000000000000000000000000000000000000000000000000 01000000000000000000000000000000", &mut test); + ext_from_hex("0085 c400000000000000000000000000000000000000000000000000000000000000 6400000000000000000000000000000000000000000000000000000000000000 027000000000000000000000000000000000000000000000000000000000000000 01000000000000000000000000000000", &mut test); // inbound read from peer id 1 of len 18 ext_from_hex("030112", &mut test); @@ -1415,7 +1368,7 @@ fn two_peer_forwarding_seed() -> Vec { // inbound read from peer id 1 of len 60 ext_from_hex("03013c", &mut test); // update_fail_htlc and mac - ext_from_hex("0083 2900000000000000000000000000000000000000000000000000000000000000 0000000000000001 0000 01000000000000000000000000000000", &mut test); + ext_from_hex("0083 c400000000000000000000000000000000000000000000000000000000000000 0000000000000001 0000 01000000000000000000000000000000", &mut test); // inbound read from peer id 1 of len 18 ext_from_hex("030112", &mut test); @@ -1424,7 +1377,7 @@ fn two_peer_forwarding_seed() -> Vec { // inbound read from peer id 1 of len 116 ext_from_hex("030174", &mut test); // commitment_signed and mac - ext_from_hex("0084 2900000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000e20001000000000000000000000000000000000000000000000000000000000000 0000 01000000000000000000000000000000", &mut test); + ext_from_hex("0084 c400000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000410001000000000000000000000000000000000000000000000000000000000000 0000 01000000000000000000000000000000", &mut test); // inbound read from peer id 1 of len 18 ext_from_hex("030112", &mut test); @@ -1433,7 +1386,7 @@ fn two_peer_forwarding_seed() -> Vec { // inbound read from peer id 1 of len 115 ext_from_hex("030173", &mut test); // revoke_and_ack and mac - ext_from_hex("0085 2900000000000000000000000000000000000000000000000000000000000000 6500000000000000000000000000000000000000000000000000000000000000 027100000000000000000000000000000000000000000000000000000000000000 01000000000000000000000000000000", &mut test); + ext_from_hex("0085 c400000000000000000000000000000000000000000000000000000000000000 6500000000000000000000000000000000000000000000000000000000000000 027100000000000000000000000000000000000000000000000000000000000000 01000000000000000000000000000000", &mut test); // process the now-pending HTLC forward ext_from_hex("07", &mut test); @@ -1447,7 +1400,7 @@ fn two_peer_forwarding_seed() -> Vec { // inbound read from peer id 0 of len 115 ext_from_hex("030073", &mut test); // revoke_and_ack and mac - ext_from_hex("0085 3d00000000000000000000000000000000000000000000000000000000000000 0a00000000000000000000000000000000000000000000000000000000000000 020c00000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000", &mut test); + ext_from_hex("0085 c000000000000000000000000000000000000000000000000000000000000000 0a00000000000000000000000000000000000000000000000000000000000000 020c00000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000", &mut test); // inbound read from peer id 0 of len 18 ext_from_hex("030012", &mut test); @@ -1456,7 +1409,7 @@ fn two_peer_forwarding_seed() -> Vec { // inbound read from peer id 0 of len 116 ext_from_hex("030074", &mut test); // commitment_signed and mac - ext_from_hex("0084 3d00000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000320100000000000000000000000000000000000000000000000000000000000000 0000 03000000000000000000000000000000", &mut test); + ext_from_hex("0084 c000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000cf0100000000000000000000000000000000000000000000000000000000000000 0000 03000000000000000000000000000000", &mut test); // client should now respond with revoke_and_ack (CHECK 5 duplicate) // inbound read from peer id 0 of len 18 @@ -1466,7 +1419,7 @@ fn two_peer_forwarding_seed() -> Vec { // inbound read from peer id 0 of len 255 ext_from_hex("0300ff", &mut test); // beginning of update_add_htlc from 0 to 1 via client - ext_from_hex("0080 3d00000000000000000000000000000000000000000000000000000000000000 0000000000000002 00000000000b0838 ff00000000000000000000000000000000000000000000000000000000000000 000003f0 00 030000000000000000000000000000000000000000000000000000000000000555 12 02030927c1 0401a0 060800000e0000010000 0a00000000000000000000000000000000000000000000000000000000000000 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", &mut test); + ext_from_hex("0080 c000000000000000000000000000000000000000000000000000000000000000 0000000000000002 00000000000b0838 ff00000000000000000000000000000000000000000000000000000000000000 000003f0 00 030000000000000000000000000000000000000000000000000000000000000555 12 02030927c1 0401a0 060800000e0000010000 0a00000000000000000000000000000000000000000000000000000000000000 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", &mut test); // inbound read from peer id 0 of len 255 ext_from_hex("0300ff", &mut test); ext_from_hex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", &mut test); @@ -1484,9 +1437,6 @@ fn two_peer_forwarding_seed() -> Vec { // end of update_add_htlc from 0 to 1 via client and mac ext_from_hex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 5200000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000", &mut test); - // One feerate request to check dust exposure - ext_from_hex("00fd", &mut test); - // inbound read from peer id 0 of len 18 ext_from_hex("030012", &mut test); // message header indicating message length 164 @@ -1494,7 +1444,7 @@ fn two_peer_forwarding_seed() -> Vec { // inbound read from peer id 0 of len 180 ext_from_hex("0300b4", &mut test); // commitment_signed and mac - ext_from_hex("0084 3d00000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000750100000000000000000000000000000000000000000000000000000000000000 0001 00000000000000000000000000000000000000000000000000000000000000670500000000000000000000000000000000000000000000000000000000000006 03000000000000000000000000000000", &mut test); + ext_from_hex("0084 c000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000f60100000000000000000000000000000000000000000000000000000000000000 0001 000000000000000000000000000000000000000000000000000000000000009b05000000000000000000000000000000000000000000000000000000000000fb 03000000000000000000000000000000", &mut test); // client should now respond with revoke_and_ack and commitment_signed (CHECK 5/6 duplicates) // inbound read from peer id 0 of len 18 @@ -1504,47 +1454,31 @@ fn two_peer_forwarding_seed() -> Vec { // inbound read from peer id 0 of len 115 ext_from_hex("030073", &mut test); // revoke_and_ack and mac - ext_from_hex("0085 3d00000000000000000000000000000000000000000000000000000000000000 0d00000000000000000000000000000000000000000000000000000000000000 020f00000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000", &mut test); + ext_from_hex("0085 c000000000000000000000000000000000000000000000000000000000000000 0d00000000000000000000000000000000000000000000000000000000000000 020f00000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000", &mut test); // process the now-pending HTLC forward ext_from_hex("07", &mut test); - // Four feerate requests to check dust exposure while forwarding the HTLC - ext_from_hex("00fd00fd00fd00fd", &mut test); // client now sends id 1 update_add_htlc and commitment_signed (CHECK 7 duplicate) // connect a block with one transaction of len 125 ext_from_hex("0c007d", &mut test); - // the commitment transaction for channel 2900000000000000000000000000000000000000000000000000000000000000 - ext_from_hex("020000000129000000000000000000000000000000000000000000000000000000000000000000000000000000800258020000000000002200201f0000000000000000000000000000000000000000000000000000000000000013c00000000000001600143b0000000000000000000000000000000000000005000020", &mut test); - // Two feerate requests during block connection - ext_from_hex("00fd00fd", &mut test); + // the commitment transaction for channel c400000000000000000000000000000000000000000000000000000000000000 + ext_from_hex("0200000001c400000000000000000000000000000000000000000000000000000000000000000000000000000080025802000000000000220020940000000000000000000000000000000000000000000000000000000000000013c0000000000000160014d60000000000000000000000000000000000000005000020", &mut test); // // connect a block with one transaction of len 94 ext_from_hex("0c005e", &mut test); // the HTLC timeout transaction - ext_from_hex("0200000001200000000000000000000000000000000000000000000000000000000000000000000000000000000001a701000000000000220020e60000000000000000000000000000000000000000000000000000000000000000000000", &mut test); - // Two feerate requests during block connection - ext_from_hex("00fd00fd", &mut test); + ext_from_hex("0200000001ab0000000000000000000000000000000000000000000000000000000000000000000000000000000001a7010000000000002200206c0000000000000000000000000000000000000000000000000000000000000000000000", &mut test); // connect a block with no transactions ext_from_hex("0c0000", &mut test); - // Two feerate requests during block connection - ext_from_hex("00fd00fd", &mut test); // connect a block with no transactions ext_from_hex("0c0000", &mut test); - // Two feerate requests during block connection - ext_from_hex("00fd00fd", &mut test); // connect a block with no transactions ext_from_hex("0c0000", &mut test); - // Two feerate requests during block connection - ext_from_hex("00fd00fd", &mut test); // connect a block with no transactions ext_from_hex("0c0000", &mut test); - // Two feerate requests during block connection - ext_from_hex("00fd00fd", &mut test); // connect a block with no transactions ext_from_hex("0c0000", &mut test); - // Two feerate requests during block connection - ext_from_hex("00fd00fd", &mut test); // process the now-pending HTLC forward ext_from_hex("07", &mut test); @@ -1686,10 +1620,10 @@ mod tests { #[test] fn test_no_existing_test_breakage() { // To avoid accidentally causing all existing fuzz test cases to be useless by making minor - // changes (such as requesting feerate info in a new place), we run a pretty full - // step-through with two peers and HTLC forwarding here. Obviously this is pretty finicky, - // so this should be updated pretty liberally, but at least we'll know when changes occur. - // If nothing else, this test serves as a pretty great initial full_stack_target seed. + // changes, we run a pretty full step-through with two peers and HTLC forwarding here. + // Obviously this can be somewhat finicky, so this should be updated pretty liberally, but + // at least we'll know when changes occur. If nothing else, this test serves as a pretty + // great initial full_stack_target seed. let test = super::two_peer_forwarding_seed(); @@ -1700,32 +1634,31 @@ mod tests { // 1 assert_eq!(log_entries.get(&("lightning::ln::peer_handler".to_string(), "Handling SendAcceptChannel event in peer_handler for node 030000000000000000000000000000000000000000000000000000000000000002 for channel ff4f00f805273c1b203bb5ebf8436bfde57b3be8c2f5e95d9491dbb181909679".to_string())), Some(&1)); // 2 - assert_eq!(log_entries.get(&("lightning::ln::peer_handler".to_string(), "Handling SendFundingSigned event in peer_handler for node 030000000000000000000000000000000000000000000000000000000000000002 for channel 3d00000000000000000000000000000000000000000000000000000000000000".to_string())), Some(&1)); + assert_eq!(log_entries.get(&("lightning::ln::peer_handler".to_string(), "Handling SendFundingSigned event in peer_handler for node 030000000000000000000000000000000000000000000000000000000000000002 for channel c000000000000000000000000000000000000000000000000000000000000000".to_string())), Some(&1)); // 3 - assert_eq!(log_entries.get(&("lightning::ln::peer_handler".to_string(), "Handling SendChannelReady event in peer_handler for node 030000000000000000000000000000000000000000000000000000000000000002 for channel 3d00000000000000000000000000000000000000000000000000000000000000".to_string())), Some(&1)); + assert_eq!(log_entries.get(&("lightning::ln::peer_handler".to_string(), "Handling SendChannelReady event in peer_handler for node 030000000000000000000000000000000000000000000000000000000000000002 for channel c000000000000000000000000000000000000000000000000000000000000000".to_string())), Some(&1)); // 4 - assert_eq!(log_entries.get(&("lightning::ln::peer_handler".to_string(), "Handling SendChannelReady event in peer_handler for node 030200000000000000000000000000000000000000000000000000000000000000 for channel 2900000000000000000000000000000000000000000000000000000000000000".to_string())), Some(&1)); + assert_eq!(log_entries.get(&("lightning::ln::peer_handler".to_string(), "Handling SendChannelReady event in peer_handler for node 030200000000000000000000000000000000000000000000000000000000000000 for channel c400000000000000000000000000000000000000000000000000000000000000".to_string())), Some(&1)); // 5 - assert_eq!(log_entries.get(&("lightning::ln::peer_handler".to_string(), "Handling SendRevokeAndACK event in peer_handler for node 030000000000000000000000000000000000000000000000000000000000000002 for channel 3d00000000000000000000000000000000000000000000000000000000000000".to_string())), Some(&4)); + assert_eq!(log_entries.get(&("lightning::ln::peer_handler".to_string(), "Handling SendRevokeAndACK event in peer_handler for node 030000000000000000000000000000000000000000000000000000000000000002 for channel c000000000000000000000000000000000000000000000000000000000000000".to_string())), Some(&4)); // 6 - assert_eq!(log_entries.get(&("lightning::ln::peer_handler".to_string(), "Handling UpdateHTLCs event in peer_handler for node 030000000000000000000000000000000000000000000000000000000000000002 with 0 adds, 0 fulfills, 0 fails, 1 commits for channel 3d00000000000000000000000000000000000000000000000000000000000000".to_string())), Some(&3)); + assert_eq!(log_entries.get(&("lightning::ln::peer_handler".to_string(), "Handling UpdateHTLCs event in peer_handler for node 030000000000000000000000000000000000000000000000000000000000000002 with 0 adds, 0 fulfills, 0 fails, 1 commits for channel c000000000000000000000000000000000000000000000000000000000000000".to_string())), Some(&3)); // 7 - assert_eq!(log_entries.get(&("lightning::ln::peer_handler".to_string(), "Handling UpdateHTLCs event in peer_handler for node 030200000000000000000000000000000000000000000000000000000000000000 with 1 adds, 0 fulfills, 0 fails, 1 commits for channel 2900000000000000000000000000000000000000000000000000000000000000".to_string())), Some(&3)); + assert_eq!(log_entries.get(&("lightning::ln::peer_handler".to_string(), "Handling UpdateHTLCs event in peer_handler for node 030200000000000000000000000000000000000000000000000000000000000000 with 1 adds, 0 fulfills, 0 fails, 1 commits for channel c400000000000000000000000000000000000000000000000000000000000000".to_string())), Some(&3)); // 8 - assert_eq!(log_entries.get(&("lightning::ln::peer_handler".to_string(), "Handling UpdateHTLCs event in peer_handler for node 030000000000000000000000000000000000000000000000000000000000000002 with 0 adds, 1 fulfills, 0 fails, 1 commits for channel 3d00000000000000000000000000000000000000000000000000000000000000".to_string())), Some(&1)); + assert_eq!(log_entries.get(&("lightning::ln::peer_handler".to_string(), "Handling UpdateHTLCs event in peer_handler for node 030000000000000000000000000000000000000000000000000000000000000002 with 0 adds, 1 fulfills, 0 fails, 1 commits for channel c000000000000000000000000000000000000000000000000000000000000000".to_string())), Some(&1)); // 9 - assert_eq!(log_entries.get(&("lightning::ln::peer_handler".to_string(), "Handling UpdateHTLCs event in peer_handler for node 030000000000000000000000000000000000000000000000000000000000000002 with 0 adds, 0 fulfills, 1 fails, 1 commits for channel 3d00000000000000000000000000000000000000000000000000000000000000".to_string())), Some(&2)); + assert_eq!(log_entries.get(&("lightning::ln::peer_handler".to_string(), "Handling UpdateHTLCs event in peer_handler for node 030000000000000000000000000000000000000000000000000000000000000002 with 0 adds, 0 fulfills, 1 fails, 1 commits for channel c000000000000000000000000000000000000000000000000000000000000000".to_string())), Some(&2)); // 10 - assert_eq!(log_entries.get(&("lightning::chain::channelmonitor".to_string(), "Input spending counterparty commitment tx (0000000000000000000000000000000000000000000000000000000000000020:0) in 0000000000000000000000000000000000000000000000000000000000000060 resolves outbound HTLC with payment hash ff00000000000000000000000000000000000000000000000000000000000000 with timeout".to_string())), Some(&1)); + assert_eq!(log_entries.get(&("lightning::chain::channelmonitor".to_string(), "Input spending counterparty commitment tx (00000000000000000000000000000000000000000000000000000000000000ab:0) in 0000000000000000000000000000000000000000000000000000000000000061 resolves outbound HTLC with payment hash ff00000000000000000000000000000000000000000000000000000000000000 with timeout".to_string())), Some(&1)); } #[test] fn test_gossip_exchange_breakage() { // To avoid accidentally causing all existing fuzz test cases to be useless by making minor - // changes (such as requesting feerate info in a new place), we exchange some gossip - // messages. Obviously this is pretty finicky, so this should be updated pretty liberally, - // but at least we'll know when changes occur. - // This test serves as a pretty good full_stack_target seed. + // changes, we exchange some gossip messages. Obviously this is somewhat finicky, so this + // should be updated pretty liberally, but at least we'll know when changes occur. + // This test serves as a helpful additional full_stack_target seed. let test = super::gossip_exchange_seed(); diff --git a/fuzz/src/lib.rs b/fuzz/src/lib.rs index bbd05c5db5f..58956fa6b3b 100644 --- a/fuzz/src/lib.rs +++ b/fuzz/src/lib.rs @@ -42,6 +42,7 @@ pub mod process_network_graph; pub mod process_onion_failure; pub mod refund_deser; pub mod router; +pub mod static_invoice_deser; pub mod zbase32; pub mod msg_targets; diff --git a/fuzz/src/static_invoice_deser.rs b/fuzz/src/static_invoice_deser.rs new file mode 100644 index 00000000000..4256ad26760 --- /dev/null +++ b/fuzz/src/static_invoice_deser.rs @@ -0,0 +1,31 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +use crate::utils::test_logger; +use core::convert::TryFrom; +use lightning::offers::static_invoice::StaticInvoice; +use lightning::util::ser::Writeable; + +#[inline] +pub fn do_test(data: &[u8], _out: Out) { + if let Ok(static_invoice) = StaticInvoice::try_from(data.to_vec()) { + let mut bytes = Vec::with_capacity(data.len()); + static_invoice.write(&mut bytes).unwrap(); + assert_eq!(data, bytes); + } +} + +pub fn static_invoice_deser_test(data: &[u8], out: Out) { + do_test(data, out); +} + +#[no_mangle] +pub extern "C" fn static_invoice_deser_run(data: *const u8, datalen: usize) { + do_test(unsafe { std::slice::from_raw_parts(data, datalen) }, test_logger::DevNull {}); +} diff --git a/fuzz/targets.h b/fuzz/targets.h index 899f260ce51..99a88f59d5c 100644 --- a/fuzz/targets.h +++ b/fuzz/targets.h @@ -7,6 +7,7 @@ void invoice_deser_run(const unsigned char* data, size_t data_len); void invoice_request_deser_run(const unsigned char* data, size_t data_len); void offer_deser_run(const unsigned char* data, size_t data_len); void bolt11_deser_run(const unsigned char* data, size_t data_len); +void static_invoice_deser_run(const unsigned char* data, size_t data_len); void onion_message_run(const unsigned char* data, size_t data_len); void peer_crypt_run(const unsigned char* data, size_t data_len); void process_network_graph_run(const unsigned char* data, size_t data_len); diff --git a/lightning-block-sync/src/lib.rs b/lightning-block-sync/src/lib.rs index 3f981cd8786..281a05a6a98 100644 --- a/lightning-block-sync/src/lib.rs +++ b/lightning-block-sync/src/lib.rs @@ -78,7 +78,7 @@ pub trait BlockSource: Sync + Send { /// to allow for a more efficient lookup. /// /// [`get_header`]: Self::get_header - fn get_best_block(&self) -> AsyncBlockSourceResult<(BlockHash, Option)>; + fn get_best_block(&self) -> AsyncBlockSourceResult<'_, (BlockHash, Option)>; } /// Result type for `BlockSource` requests. diff --git a/lightning-invoice/src/lib.rs b/lightning-invoice/src/lib.rs index b814210b390..fda7c1003b9 100644 --- a/lightning-invoice/src/lib.rs +++ b/lightning-invoice/src/lib.rs @@ -777,6 +777,16 @@ impl Bolt11InvoiceDescription::Hash(hash) => self.description_hash(hash.0), } } + + /// Set the description or description hash. This function is only available if no description (hash) was set. + pub fn invoice_description_ref( + self, description_ref: Bolt11InvoiceDescriptionRef<'_>, + ) -> InvoiceBuilder { + match description_ref { + Bolt11InvoiceDescriptionRef::Direct(desc) => self.description(desc.clone().0 .0), + Bolt11InvoiceDescriptionRef::Hash(hash) => self.description_hash(hash.0), + } + } } impl @@ -1146,7 +1156,7 @@ impl RawBolt11Invoice { /// This is not exported to bindings users as there is not yet a manual mapping for a FilterMap pub fn known_tagged_fields( &self, - ) -> FilterMap, fn(&RawTaggedField) -> Option<&TaggedField>> { + ) -> FilterMap, fn(&RawTaggedField) -> Option<&TaggedField>> { // For 1.14.0 compatibility: closures' types can't be written an fn()->() in the // function's type signature. // TODO: refactor once impl Trait is available @@ -1468,7 +1478,7 @@ impl Bolt11Invoice { /// This is not exported to bindings users as there is not yet a manual mapping for a FilterMap pub fn tagged_fields( &self, - ) -> FilterMap, fn(&RawTaggedField) -> Option<&TaggedField>> { + ) -> FilterMap, fn(&RawTaggedField) -> Option<&TaggedField>> { self.signed_invoice.raw_invoice().known_tagged_fields() } @@ -1480,7 +1490,7 @@ impl Bolt11Invoice { /// Return the description or a hash of it for longer ones /// /// This is not exported to bindings users because we don't yet export Bolt11InvoiceDescription - pub fn description(&self) -> Bolt11InvoiceDescriptionRef { + pub fn description(&self) -> Bolt11InvoiceDescriptionRef<'_> { if let Some(direct) = self.signed_invoice.description() { return Bolt11InvoiceDescriptionRef::Direct(direct); } else if let Some(hash) = self.signed_invoice.description_hash() { diff --git a/lightning-liquidity/src/events/event_queue.rs b/lightning-liquidity/src/events/event_queue.rs index a2589beb4e2..f59d34ed34a 100644 --- a/lightning-liquidity/src/events/event_queue.rs +++ b/lightning-liquidity/src/events/event_queue.rs @@ -67,7 +67,7 @@ impl EventQueue { } // Returns an [`EventQueueNotifierGuard`] that will notify about new event when dropped. - pub fn notifier(&self) -> EventQueueNotifierGuard { + pub fn notifier(&self) -> EventQueueNotifierGuard<'_> { EventQueueNotifierGuard(self) } } diff --git a/lightning-liquidity/src/lsps0/client.rs b/lightning-liquidity/src/lsps0/client.rs index 5ae73005e61..f7e01b323f3 100644 --- a/lightning-liquidity/src/lsps0/client.rs +++ b/lightning-liquidity/src/lsps0/client.rs @@ -50,12 +50,14 @@ where /// specifcation](https://github.com/lightning/blips/blob/master/blip-0050.md#lsps-specification-support-query) /// for more information. pub fn list_protocols(&self, counterparty_node_id: &PublicKey) { + let mut message_queue_notifier = self.pending_messages.notifier(); + let msg = LSPS0Message::Request( utils::generate_request_id(&self.entropy_source), LSPS0Request::ListProtocols(LSPS0ListProtocolsRequest {}), ); - self.pending_messages.enqueue(counterparty_node_id, msg.into()); + message_queue_notifier.enqueue(counterparty_node_id, msg.into()); } fn handle_response( diff --git a/lightning-liquidity/src/lsps0/ser.rs b/lightning-liquidity/src/lsps0/ser.rs index aeb29422678..213e2760119 100644 --- a/lightning-liquidity/src/lsps0/ser.rs +++ b/lightning-liquidity/src/lsps0/ser.rs @@ -218,7 +218,7 @@ impl wire::Type for RawLSPSMessage { pub struct LSPSRequestId(pub String); /// An object representing datetimes as described in bLIP-50 / LSPS0. -#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)] +#[derive(Clone, Debug, Copy, PartialEq, Eq, Hash, Deserialize, Serialize)] #[serde(transparent)] pub struct LSPSDateTime(pub chrono::DateTime); @@ -240,9 +240,10 @@ impl LSPSDateTime { now_seconds_since_epoch > datetime_seconds_since_epoch } - /// Returns the time in seconds since the unix epoch. - pub fn abs_diff(&self, other: &Self) -> u64 { - self.0.timestamp().abs_diff(other.0.timestamp()) + /// Returns the absolute difference between two datetimes as a `Duration`. + pub fn duration_since(&self, other: &Self) -> Duration { + let diff_secs = self.0.timestamp().abs_diff(other.0.timestamp()); + Duration::from_secs(diff_secs) } /// Returns the time in seconds since the unix epoch. diff --git a/lightning-liquidity/src/lsps0/service.rs b/lightning-liquidity/src/lsps0/service.rs index 4a595ab3d2f..2b4e6782ce8 100644 --- a/lightning-liquidity/src/lsps0/service.rs +++ b/lightning-liquidity/src/lsps0/service.rs @@ -40,6 +40,8 @@ impl LSPS0ServiceHandler { fn handle_request( &self, request_id: LSPSRequestId, request: LSPS0Request, counterparty_node_id: &PublicKey, ) -> Result<(), lightning::ln::msgs::LightningError> { + let mut message_queue_notifier = self.pending_messages.notifier(); + match request { LSPS0Request::ListProtocols(_) => { let msg = LSPS0Message::Response( @@ -48,7 +50,7 @@ impl LSPS0ServiceHandler { protocols: self.protocols.clone(), }), ); - self.pending_messages.enqueue(counterparty_node_id, msg.into()); + message_queue_notifier.enqueue(counterparty_node_id, msg.into()); Ok(()) }, } diff --git a/lightning-liquidity/src/lsps1/client.rs b/lightning-liquidity/src/lsps1/client.rs index b1b7b6a2493..45008baaa77 100644 --- a/lightning-liquidity/src/lsps1/client.rs +++ b/lightning-liquidity/src/lsps1/client.rs @@ -90,6 +90,8 @@ where /// /// [`SupportedOptionsReady`]: crate::lsps1::event::LSPS1ClientEvent::SupportedOptionsReady pub fn request_supported_options(&self, counterparty_node_id: PublicKey) -> LSPSRequestId { + let mut message_queue_notifier = self.pending_messages.notifier(); + let request_id = crate::utils::generate_request_id(&self.entropy_source); { let mut outer_state_lock = self.per_peer_state.write().unwrap(); @@ -102,7 +104,7 @@ where let request = LSPS1Request::GetInfo(LSPS1GetInfoRequest {}); let msg = LSPS1Message::Request(request_id.clone(), request).into(); - self.pending_messages.enqueue(&counterparty_node_id, msg); + message_queue_notifier.enqueue(&counterparty_node_id, msg); request_id } @@ -198,27 +200,21 @@ where &self, counterparty_node_id: &PublicKey, order: LSPS1OrderParams, refund_onchain_address: Option
, ) -> LSPSRequestId { - let (request_id, request_msg) = { - let mut outer_state_lock = self.per_peer_state.write().unwrap(); - let inner_state_lock = outer_state_lock - .entry(*counterparty_node_id) - .or_insert(Mutex::new(PeerState::default())); - let mut peer_state_lock = inner_state_lock.lock().unwrap(); + let mut message_queue_notifier = self.pending_messages.notifier(); - let request_id = crate::utils::generate_request_id(&self.entropy_source); - let request = LSPS1Request::CreateOrder(LSPS1CreateOrderRequest { - order, - refund_onchain_address, - }); - let msg = LSPS1Message::Request(request_id.clone(), request).into(); - peer_state_lock.pending_create_order_requests.insert(request_id.clone()); + let mut outer_state_lock = self.per_peer_state.write().unwrap(); + let inner_state_lock = outer_state_lock + .entry(*counterparty_node_id) + .or_insert(Mutex::new(PeerState::default())); + let mut peer_state_lock = inner_state_lock.lock().unwrap(); - (request_id, Some(msg)) - }; + let request_id = crate::utils::generate_request_id(&self.entropy_source); + let request = + LSPS1Request::CreateOrder(LSPS1CreateOrderRequest { order, refund_onchain_address }); + let msg = LSPS1Message::Request(request_id.clone(), request).into(); + peer_state_lock.pending_create_order_requests.insert(request_id.clone()); - if let Some(msg) = request_msg { - self.pending_messages.enqueue(&counterparty_node_id, msg); - } + message_queue_notifier.enqueue(&counterparty_node_id, msg); request_id } @@ -322,26 +318,21 @@ where pub fn check_order_status( &self, counterparty_node_id: &PublicKey, order_id: LSPS1OrderId, ) -> LSPSRequestId { - let (request_id, request_msg) = { - let mut outer_state_lock = self.per_peer_state.write().unwrap(); - let inner_state_lock = outer_state_lock - .entry(*counterparty_node_id) - .or_insert(Mutex::new(PeerState::default())); - let mut peer_state_lock = inner_state_lock.lock().unwrap(); + let mut message_queue_notifier = self.pending_messages.notifier(); - let request_id = crate::utils::generate_request_id(&self.entropy_source); - peer_state_lock.pending_get_order_requests.insert(request_id.clone()); + let mut outer_state_lock = self.per_peer_state.write().unwrap(); + let inner_state_lock = outer_state_lock + .entry(*counterparty_node_id) + .or_insert(Mutex::new(PeerState::default())); + let mut peer_state_lock = inner_state_lock.lock().unwrap(); - let request = - LSPS1Request::GetOrder(LSPS1GetOrderRequest { order_id: order_id.clone() }); - let msg = LSPS1Message::Request(request_id.clone(), request).into(); + let request_id = crate::utils::generate_request_id(&self.entropy_source); + peer_state_lock.pending_get_order_requests.insert(request_id.clone()); - (request_id, Some(msg)) - }; + let request = LSPS1Request::GetOrder(LSPS1GetOrderRequest { order_id: order_id.clone() }); + let msg = LSPS1Message::Request(request_id.clone(), request).into(); - if let Some(msg) = request_msg { - self.pending_messages.enqueue(&counterparty_node_id, msg); - } + message_queue_notifier.enqueue(&counterparty_node_id, msg); request_id } diff --git a/lightning-liquidity/src/lsps1/msgs.rs b/lightning-liquidity/src/lsps1/msgs.rs index 9b9b94d7cd2..8402827a4a6 100644 --- a/lightning-liquidity/src/lsps1/msgs.rs +++ b/lightning-liquidity/src/lsps1/msgs.rs @@ -1,3 +1,12 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + //! Message, request, and other primitive types used to implement bLIP-51 / LSPS1. use alloc::string::String; diff --git a/lightning-liquidity/src/lsps1/service.rs b/lightning-liquidity/src/lsps1/service.rs index 28fe72ca905..aa10e735565 100644 --- a/lightning-liquidity/src/lsps1/service.rs +++ b/lightning-liquidity/src/lsps1/service.rs @@ -177,6 +177,8 @@ where fn handle_get_info_request( &self, request_id: LSPSRequestId, counterparty_node_id: &PublicKey, ) -> Result<(), LightningError> { + let mut message_queue_notifier = self.pending_messages.notifier(); + let response = LSPS1Response::GetInfo(LSPS1GetInfoResponse { options: self .config @@ -190,7 +192,7 @@ where }); let msg = LSPS1Message::Response(request_id, response).into(); - self.pending_messages.enqueue(counterparty_node_id, msg); + message_queue_notifier.enqueue(counterparty_node_id, msg); Ok(()) } @@ -198,7 +200,9 @@ where &self, request_id: LSPSRequestId, counterparty_node_id: &PublicKey, params: LSPS1CreateOrderRequest, ) -> Result<(), LightningError> { + let mut message_queue_notifier = self.pending_messages.notifier(); let event_queue_notifier = self.pending_events.notifier(); + if !is_valid(¶ms.order, &self.config.supported_options.as_ref().unwrap()) { let response = LSPS1Response::CreateOrderError(LSPSResponseError { code: LSPS1_CREATE_ORDER_REQUEST_ORDER_MISMATCH_ERROR_CODE, @@ -209,7 +213,7 @@ where )), }); let msg = LSPS1Message::Response(request_id, response).into(); - self.pending_messages.enqueue(counterparty_node_id, msg); + message_queue_notifier.enqueue(counterparty_node_id, msg); return Err(LightningError { err: format!( "Client order does not match any supported options: {:?}", @@ -250,66 +254,47 @@ where &self, request_id: LSPSRequestId, counterparty_node_id: &PublicKey, payment: LSPS1PaymentInfo, created_at: LSPSDateTime, ) -> Result<(), APIError> { - let (result, response) = { - let outer_state_lock = self.per_peer_state.read().unwrap(); - - match outer_state_lock.get(counterparty_node_id) { - Some(inner_state_lock) => { - let mut peer_state_lock = inner_state_lock.lock().unwrap(); - - match peer_state_lock.pending_requests.remove(&request_id) { - Some(LSPS1Request::CreateOrder(params)) => { - let order_id = self.generate_order_id(); - let channel = OutboundCRChannel::new( - params.order.clone(), - created_at.clone(), - order_id.clone(), - payment.clone(), - ); - - peer_state_lock.insert_outbound_channel(order_id.clone(), channel); - - let response = LSPS1Response::CreateOrder(LSPS1CreateOrderResponse { - order: params.order, - order_id, - order_state: LSPS1OrderState::Created, - created_at, - payment, - channel: None, - }); - - (Ok(()), Some(response)) - }, - - _ => ( - Err(APIError::APIMisuseError { - err: format!( - "No pending buy request for request_id: {:?}", - request_id - ), - }), - None, - ), - } - }, - None => ( - Err(APIError::APIMisuseError { - err: format!( - "No state for the counterparty exists: {:?}", - counterparty_node_id - ), - }), - None, - ), - } - }; + let mut message_queue_notifier = self.pending_messages.notifier(); - if let Some(response) = response { - let msg = LSPS1Message::Response(request_id, response).into(); - self.pending_messages.enqueue(counterparty_node_id, msg); - } + let outer_state_lock = self.per_peer_state.read().unwrap(); + match outer_state_lock.get(counterparty_node_id) { + Some(inner_state_lock) => { + let mut peer_state_lock = inner_state_lock.lock().unwrap(); + + match peer_state_lock.pending_requests.remove(&request_id) { + Some(LSPS1Request::CreateOrder(params)) => { + let order_id = self.generate_order_id(); + let channel = OutboundCRChannel::new( + params.order.clone(), + created_at, + order_id.clone(), + payment.clone(), + ); + + peer_state_lock.insert_outbound_channel(order_id.clone(), channel); + + let response = LSPS1Response::CreateOrder(LSPS1CreateOrderResponse { + order: params.order, + order_id, + order_state: LSPS1OrderState::Created, + created_at, + payment, + channel: None, + }); + let msg = LSPS1Message::Response(request_id, response).into(); + message_queue_notifier.enqueue(counterparty_node_id, msg); + Ok(()) + }, - result + _ => Err(APIError::APIMisuseError { + err: format!("No pending buy request for request_id: {:?}", request_id), + }), + } + }, + None => Err(APIError::APIMisuseError { + err: format!("No state for the counterparty exists: {:?}", counterparty_node_id), + }), + } } fn handle_get_order_request( @@ -376,54 +361,40 @@ where &self, request_id: LSPSRequestId, counterparty_node_id: PublicKey, order_id: LSPS1OrderId, order_state: LSPS1OrderState, channel: Option, ) -> Result<(), APIError> { - let (result, response) = { - let outer_state_lock = self.per_peer_state.read().unwrap(); + let mut message_queue_notifier = self.pending_messages.notifier(); - match outer_state_lock.get(&counterparty_node_id) { - Some(inner_state_lock) => { - let mut peer_state_lock = inner_state_lock.lock().unwrap(); + let outer_state_lock = self.per_peer_state.read().unwrap(); - if let Some(outbound_channel) = - peer_state_lock.outbound_channels_by_order_id.get_mut(&order_id) - { - let config = &outbound_channel.config; + match outer_state_lock.get(&counterparty_node_id) { + Some(inner_state_lock) => { + let mut peer_state_lock = inner_state_lock.lock().unwrap(); - let response = LSPS1Response::GetOrder(LSPS1CreateOrderResponse { - order_id, - order: config.order.clone(), - order_state, - created_at: config.created_at.clone(), - payment: config.payment.clone(), - channel, - }); - (Ok(()), Some(response)) - } else { - ( - Err(APIError::APIMisuseError { - err: format!("Channel with order_id {} not found", order_id.0), - }), - None, - ) - } - }, - None => ( + if let Some(outbound_channel) = + peer_state_lock.outbound_channels_by_order_id.get_mut(&order_id) + { + let config = &outbound_channel.config; + + let response = LSPS1Response::GetOrder(LSPS1CreateOrderResponse { + order_id, + order: config.order.clone(), + order_state, + created_at: config.created_at.clone(), + payment: config.payment.clone(), + channel, + }); + let msg = LSPS1Message::Response(request_id, response).into(); + message_queue_notifier.enqueue(&counterparty_node_id, msg); + Ok(()) + } else { Err(APIError::APIMisuseError { - err: format!( - "No existing state with counterparty {}", - counterparty_node_id - ), - }), - None, - ), - } - }; - - if let Some(response) = response { - let msg = LSPS1Message::Response(request_id, response).into(); - self.pending_messages.enqueue(&counterparty_node_id, msg); + err: format!("Channel with order_id {} not found", order_id.0), + }) + } + }, + None => Err(APIError::APIMisuseError { + err: format!("No existing state with counterparty {}", counterparty_node_id), + }), } - - result } fn generate_order_id(&self) -> LSPS1OrderId { diff --git a/lightning-liquidity/src/lsps2/client.rs b/lightning-liquidity/src/lsps2/client.rs index bbe313d6089..7008d42e345 100644 --- a/lightning-liquidity/src/lsps2/client.rs +++ b/lightning-liquidity/src/lsps2/client.rs @@ -3,7 +3,8 @@ // // This file is licensed under the Apache License, Version 2.0 or the MIT license -// , at your option. You may not use this file except in accordance with one or both of these +// , at your option. +// You may not use this file except in accordance with one or both of these // licenses. //! Contains the main bLIP-52 / LSPS2 client object, [`LSPS2ClientHandler`]. @@ -118,6 +119,8 @@ where pub fn request_opening_params( &self, counterparty_node_id: PublicKey, token: Option, ) -> LSPSRequestId { + let mut message_queue_notifier = self.pending_messages.notifier(); + let request_id = crate::utils::generate_request_id(&self.entropy_source); { @@ -131,7 +134,7 @@ where let request = LSPS2Request::GetInfo(LSPS2GetInfoRequest { token }); let msg = LSPS2Message::Request(request_id.clone(), request).into(); - self.pending_messages.enqueue(&counterparty_node_id, msg); + message_queue_notifier.enqueue(&counterparty_node_id, msg); request_id } @@ -160,6 +163,8 @@ where &self, counterparty_node_id: PublicKey, payment_size_msat: Option, opening_fee_params: LSPS2OpeningFeeParams, ) -> Result { + let mut message_queue_notifier = self.pending_messages.notifier(); + let request_id = crate::utils::generate_request_id(&self.entropy_source); { @@ -184,7 +189,7 @@ where let request = LSPS2Request::Buy(LSPS2BuyRequest { opening_fee_params, payment_size_msat }); let msg = LSPS2Message::Request(request_id.clone(), request).into(); - self.pending_messages.enqueue(&counterparty_node_id, msg); + message_queue_notifier.enqueue(&counterparty_node_id, msg); Ok(request_id) } diff --git a/lightning-liquidity/src/lsps2/msgs.rs b/lightning-liquidity/src/lsps2/msgs.rs index 84875d4ab7c..2a01d6ee32f 100644 --- a/lightning-liquidity/src/lsps2/msgs.rs +++ b/lightning-liquidity/src/lsps2/msgs.rs @@ -1,3 +1,12 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + //! Message, request, and other primitive types used to implement bLIP-52 / LSPS2. use alloc::string::String; @@ -72,7 +81,7 @@ impl LSPS2RawOpeningFeeParams { LSPS2OpeningFeeParams { min_fee_msat: self.min_fee_msat, proportional: self.proportional, - valid_until: self.valid_until.clone(), + valid_until: self.valid_until, min_lifetime: self.min_lifetime, max_client_to_self_delay: self.max_client_to_self_delay, min_payment_size_msat: self.min_payment_size_msat, @@ -235,7 +244,7 @@ mod tests { let raw = LSPS2RawOpeningFeeParams { min_fee_msat, proportional, - valid_until: valid_until.clone().into(), + valid_until: valid_until.into(), min_lifetime, max_client_to_self_delay, min_payment_size_msat, diff --git a/lightning-liquidity/src/lsps2/payment_queue.rs b/lightning-liquidity/src/lsps2/payment_queue.rs index 30413537a9c..d6474dc97a0 100644 --- a/lightning-liquidity/src/lsps2/payment_queue.rs +++ b/lightning-liquidity/src/lsps2/payment_queue.rs @@ -1,3 +1,12 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + use alloc::vec::Vec; use lightning::ln::channelmanager::InterceptId; @@ -8,7 +17,7 @@ use lightning_types::payment::PaymentHash; /// remaining payments forwarded. #[derive(Clone, Default, PartialEq, Eq, Debug)] pub(crate) struct PaymentQueue { - payments: Vec<(PaymentHash, Vec)>, + payments: Vec, } impl PaymentQueue { @@ -17,37 +26,48 @@ impl PaymentQueue { } pub(crate) fn add_htlc(&mut self, new_htlc: InterceptedHTLC) -> (u64, usize) { - let payment = self.payments.iter_mut().find(|(p, _)| p == &new_htlc.payment_hash); - if let Some((payment_hash, htlcs)) = payment { + let payment = + self.payments.iter_mut().find(|entry| entry.payment_hash == new_htlc.payment_hash); + if let Some(entry) = payment { // HTLCs within a payment should have the same payment hash. - debug_assert!(htlcs.iter().all(|htlc| htlc.payment_hash == *payment_hash)); + debug_assert!(entry.htlcs.iter().all(|htlc| htlc.payment_hash == entry.payment_hash)); // The given HTLC should not already be present. - debug_assert!(htlcs.iter().all(|htlc| htlc.intercept_id != new_htlc.intercept_id)); - htlcs.push(new_htlc); + debug_assert!(entry + .htlcs + .iter() + .all(|htlc| htlc.intercept_id != new_htlc.intercept_id)); + entry.htlcs.push(new_htlc); let total_expected_outbound_amount_msat = - htlcs.iter().map(|htlc| htlc.expected_outbound_amount_msat).sum(); - (total_expected_outbound_amount_msat, htlcs.len()) + entry.htlcs.iter().map(|htlc| htlc.expected_outbound_amount_msat).sum(); + (total_expected_outbound_amount_msat, entry.htlcs.len()) } else { let expected_outbound_amount_msat = new_htlc.expected_outbound_amount_msat; - self.payments.push((new_htlc.payment_hash, vec![new_htlc])); + let entry = + PaymentQueueEntry { payment_hash: new_htlc.payment_hash, htlcs: vec![new_htlc] }; + self.payments.push(entry); (expected_outbound_amount_msat, 1) } } - pub(crate) fn pop_greater_than_msat( - &mut self, amount_msat: u64, - ) -> Option<(PaymentHash, Vec)> { - let position = self.payments.iter().position(|(_payment_hash, htlcs)| { - htlcs.iter().map(|htlc| htlc.expected_outbound_amount_msat).sum::() >= amount_msat + pub(crate) fn pop_greater_than_msat(&mut self, amount_msat: u64) -> Option { + let position = self.payments.iter().position(|entry| { + entry.htlcs.iter().map(|htlc| htlc.expected_outbound_amount_msat).sum::() + >= amount_msat }); position.map(|position| self.payments.remove(position)) } pub(crate) fn clear(&mut self) -> Vec { - self.payments.drain(..).map(|(_k, v)| v).flatten().collect() + self.payments.drain(..).map(|entry| entry.htlcs).flatten().collect() } } +#[derive(Clone, PartialEq, Eq, Debug)] +pub(crate) struct PaymentQueueEntry { + pub(crate) payment_hash: PaymentHash, + pub(crate) htlcs: Vec, +} + #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub(crate) struct InterceptedHTLC { pub(crate) intercept_id: InterceptId, @@ -90,24 +110,23 @@ mod tests { }), (500_000_000, 2), ); - assert_eq!( - payment_queue.pop_greater_than_msat(500_000_000), - Some(( - PaymentHash([100; 32]), - vec![ - InterceptedHTLC { - intercept_id: InterceptId([0; 32]), - expected_outbound_amount_msat: 200_000_000, - payment_hash: PaymentHash([100; 32]), - }, - InterceptedHTLC { - intercept_id: InterceptId([2; 32]), - expected_outbound_amount_msat: 300_000_000, - payment_hash: PaymentHash([100; 32]), - }, - ] - )) - ); + + let expected_entry = PaymentQueueEntry { + payment_hash: PaymentHash([100; 32]), + htlcs: vec![ + InterceptedHTLC { + intercept_id: InterceptId([0; 32]), + expected_outbound_amount_msat: 200_000_000, + payment_hash: PaymentHash([100; 32]), + }, + InterceptedHTLC { + intercept_id: InterceptId([2; 32]), + expected_outbound_amount_msat: 300_000_000, + payment_hash: PaymentHash([100; 32]), + }, + ], + }; + assert_eq!(payment_queue.pop_greater_than_msat(500_000_000), Some(expected_entry),); assert_eq!( payment_queue.clear(), vec![InterceptedHTLC { diff --git a/lightning-liquidity/src/lsps2/service.rs b/lightning-liquidity/src/lsps2/service.rs index e1666b1d352..114ed8b250d 100644 --- a/lightning-liquidity/src/lsps2/service.rs +++ b/lightning-liquidity/src/lsps2/service.rs @@ -27,7 +27,7 @@ use crate::lsps2::payment_queue::{InterceptedHTLC, PaymentQueue}; use crate::lsps2::utils::{ compute_opening_fee, is_expired_opening_fee_params, is_valid_opening_fee_params, }; -use crate::message_queue::MessageQueue; +use crate::message_queue::{MessageQueue, MessageQueueNotifierGuard}; use crate::prelude::hash_map::Entry; use crate::prelude::{new_hash_map, HashMap}; use crate::sync::{Arc, Mutex, MutexGuard, RwLock}; @@ -242,12 +242,10 @@ impl OutboundJITChannelState { } => { let mut payment_queue = core::mem::take(payment_queue); payment_queue.add_htlc(htlc); - if let Some((_payment_hash, htlcs)) = - payment_queue.pop_greater_than_msat(*opening_fee_msat) - { + if let Some(entry) = payment_queue.pop_greater_than_msat(*opening_fee_msat) { let forward_payment = HTLCInterceptedAction::ForwardPayment( *channel_id, - FeePayment { htlcs, opening_fee_msat: *opening_fee_msat }, + FeePayment { htlcs: entry.htlcs, opening_fee_msat: *opening_fee_msat }, ); *self = OutboundJITChannelState::PendingPaymentForward { payment_queue, @@ -277,12 +275,10 @@ impl OutboundJITChannelState { ) -> Result { match self { OutboundJITChannelState::PendingChannelOpen { payment_queue, opening_fee_msat } => { - if let Some((_payment_hash, htlcs)) = - payment_queue.pop_greater_than_msat(*opening_fee_msat) - { + if let Some(entry) = payment_queue.pop_greater_than_msat(*opening_fee_msat) { let forward_payment = ForwardPaymentAction( channel_id, - FeePayment { opening_fee_msat: *opening_fee_msat, htlcs }, + FeePayment { htlcs: entry.htlcs, opening_fee_msat: *opening_fee_msat }, ); *self = OutboundJITChannelState::PendingPaymentForward { payment_queue: core::mem::take(payment_queue), @@ -311,12 +307,10 @@ impl OutboundJITChannelState { opening_fee_msat, channel_id, } => { - if let Some((_payment_hash, htlcs)) = - payment_queue.pop_greater_than_msat(*opening_fee_msat) - { + if let Some(entry) = payment_queue.pop_greater_than_msat(*opening_fee_msat) { let forward_payment = ForwardPaymentAction( *channel_id, - FeePayment { htlcs, opening_fee_msat: *opening_fee_msat }, + FeePayment { htlcs: entry.htlcs, opening_fee_msat: *opening_fee_msat }, ); *self = OutboundJITChannelState::PendingPaymentForward { payment_queue: core::mem::take(payment_queue), @@ -499,7 +493,7 @@ impl PeerState { } macro_rules! get_or_insert_peer_state_entry { - ($self: ident, $outer_state_lock: expr, $counterparty_node_id: expr) => {{ + ($self: ident, $outer_state_lock: expr, $message_queue_notifier: expr, $counterparty_node_id: expr) => {{ // Return an internal error and abort if we hit the maximum allowed number of total peers. let is_limited_by_max_total_peers = $outer_state_lock.len() >= MAX_TOTAL_PEERS; match $outer_state_lock.entry(*$counterparty_node_id) { @@ -511,8 +505,7 @@ macro_rules! get_or_insert_peer_state_entry { }; let msg = LSPSMessage::Invalid(error_response); - drop($outer_state_lock); - $self.pending_messages.enqueue($counterparty_node_id, msg); + $message_queue_notifier.enqueue($counterparty_node_id, msg); let err = format!( "Dropping request from peer {} due to reaching maximally allowed number of total peers: {}", @@ -581,51 +574,37 @@ where pub fn invalid_token_provided( &self, counterparty_node_id: &PublicKey, request_id: LSPSRequestId, ) -> Result<(), APIError> { - let (result, response) = { - let outer_state_lock = self.per_peer_state.read().unwrap(); + let mut message_queue_notifier = self.pending_messages.notifier(); - match outer_state_lock.get(counterparty_node_id) { - Some(inner_state_lock) => { - let mut peer_state_lock = inner_state_lock.lock().unwrap(); - - match self.remove_pending_request(&mut peer_state_lock, &request_id) { - Some(LSPS2Request::GetInfo(_)) => { - let response = LSPS2Response::GetInfoError(LSPSResponseError { - code: LSPS2_GET_INFO_REQUEST_UNRECOGNIZED_OR_STALE_TOKEN_ERROR_CODE, - message: "an unrecognized or stale token was provided".to_string(), - data: None, - }); - (Ok(()), Some(response)) - }, - _ => ( - Err(APIError::APIMisuseError { - err: format!( - "No pending get_info request for request_id: {:?}", - request_id - ), - }), - None, - ), - } - }, - None => ( - Err(APIError::APIMisuseError { + let outer_state_lock = self.per_peer_state.read().unwrap(); + + match outer_state_lock.get(counterparty_node_id) { + Some(inner_state_lock) => { + let mut peer_state_lock = inner_state_lock.lock().unwrap(); + + match self.remove_pending_request(&mut peer_state_lock, &request_id) { + Some(LSPS2Request::GetInfo(_)) => { + let response = LSPS2Response::GetInfoError(LSPSResponseError { + code: LSPS2_GET_INFO_REQUEST_UNRECOGNIZED_OR_STALE_TOKEN_ERROR_CODE, + message: "an unrecognized or stale token was provided".to_string(), + data: None, + }); + let msg = LSPS2Message::Response(request_id, response).into(); + message_queue_notifier.enqueue(counterparty_node_id, msg); + Ok(()) + }, + _ => Err(APIError::APIMisuseError { err: format!( - "No state for the counterparty exists: {:?}", - counterparty_node_id + "No pending get_info request for request_id: {:?}", + request_id ), }), - None, - ), - } - }; - - if let Some(response) = response { - let msg = LSPS2Message::Response(request_id, response).into(); - self.pending_messages.enqueue(counterparty_node_id, msg); + } + }, + None => Err(APIError::APIMisuseError { + err: format!("No state for the counterparty exists: {:?}", counterparty_node_id), + }), } - - result } /// Used by LSP to provide fee parameters to a client requesting a JIT Channel. @@ -637,62 +616,48 @@ where &self, counterparty_node_id: &PublicKey, request_id: LSPSRequestId, opening_fee_params_menu: Vec, ) -> Result<(), APIError> { - let (result, response) = { - let outer_state_lock = self.per_peer_state.read().unwrap(); + let mut message_queue_notifier = self.pending_messages.notifier(); - match outer_state_lock.get(counterparty_node_id) { - Some(inner_state_lock) => { - let mut peer_state_lock = inner_state_lock.lock().unwrap(); - - match self.remove_pending_request(&mut peer_state_lock, &request_id) { - Some(LSPS2Request::GetInfo(_)) => { - let mut opening_fee_params_menu: Vec = - opening_fee_params_menu - .into_iter() - .map(|param| { - param.into_opening_fee_params(&self.config.promise_secret) - }) - .collect(); - opening_fee_params_menu.sort_by(|a, b| { - match a.min_fee_msat.cmp(&b.min_fee_msat) { - CmpOrdering::Equal => a.proportional.cmp(&b.proportional), - other => other, - } - }); - let response = LSPS2Response::GetInfo(LSPS2GetInfoResponse { - opening_fee_params_menu, - }); - (Ok(()), Some(response)) - }, - _ => ( - Err(APIError::APIMisuseError { - err: format!( - "No pending get_info request for request_id: {:?}", - request_id - ), - }), - None, - ), - } - }, - None => ( - Err(APIError::APIMisuseError { + let outer_state_lock = self.per_peer_state.read().unwrap(); + + match outer_state_lock.get(counterparty_node_id) { + Some(inner_state_lock) => { + let mut peer_state_lock = inner_state_lock.lock().unwrap(); + + match self.remove_pending_request(&mut peer_state_lock, &request_id) { + Some(LSPS2Request::GetInfo(_)) => { + let mut opening_fee_params_menu: Vec = + opening_fee_params_menu + .into_iter() + .map(|param| { + param.into_opening_fee_params(&self.config.promise_secret) + }) + .collect(); + opening_fee_params_menu.sort_by(|a, b| { + match a.min_fee_msat.cmp(&b.min_fee_msat) { + CmpOrdering::Equal => a.proportional.cmp(&b.proportional), + other => other, + } + }); + let response = LSPS2Response::GetInfo(LSPS2GetInfoResponse { + opening_fee_params_menu, + }); + let msg = LSPS2Message::Response(request_id, response).into(); + message_queue_notifier.enqueue(counterparty_node_id, msg); + Ok(()) + }, + _ => Err(APIError::APIMisuseError { err: format!( - "No state for the counterparty exists: {:?}", - counterparty_node_id + "No pending get_info request for request_id: {:?}", + request_id ), }), - None, - ), - } - }; - - if let Some(response) = response { - let msg = LSPS2Message::Response(request_id, response).into(); - self.pending_messages.enqueue(counterparty_node_id, msg); + } + }, + None => Err(APIError::APIMisuseError { + err: format!("No state for the counterparty exists: {:?}", counterparty_node_id), + }), } - - result } /// Used by LSP to provide the client with the intercept scid and @@ -707,70 +672,52 @@ where &self, counterparty_node_id: &PublicKey, request_id: LSPSRequestId, intercept_scid: u64, cltv_expiry_delta: u32, client_trusts_lsp: bool, user_channel_id: u128, ) -> Result<(), APIError> { - let (result, response) = { - let outer_state_lock = self.per_peer_state.read().unwrap(); + let mut message_queue_notifier = self.pending_messages.notifier(); - match outer_state_lock.get(counterparty_node_id) { - Some(inner_state_lock) => { - let mut peer_state_lock = inner_state_lock.lock().unwrap(); + let outer_state_lock = self.per_peer_state.read().unwrap(); - match self.remove_pending_request(&mut peer_state_lock, &request_id) { - Some(LSPS2Request::Buy(buy_request)) => { - { - let mut peer_by_intercept_scid = - self.peer_by_intercept_scid.write().unwrap(); - peer_by_intercept_scid - .insert(intercept_scid, *counterparty_node_id); - } + match outer_state_lock.get(counterparty_node_id) { + Some(inner_state_lock) => { + let mut peer_state_lock = inner_state_lock.lock().unwrap(); - let outbound_jit_channel = OutboundJITChannel::new( - buy_request.payment_size_msat, - buy_request.opening_fee_params, - user_channel_id, - ); - - peer_state_lock - .intercept_scid_by_user_channel_id - .insert(user_channel_id, intercept_scid); - peer_state_lock - .insert_outbound_channel(intercept_scid, outbound_jit_channel); - - let response = LSPS2Response::Buy(LSPS2BuyResponse { - jit_channel_scid: intercept_scid.into(), - lsp_cltv_expiry_delta: cltv_expiry_delta, - client_trusts_lsp, - }); - (Ok(()), Some(response)) - }, - _ => ( - Err(APIError::APIMisuseError { - err: format!( - "No pending buy request for request_id: {:?}", - request_id - ), - }), - None, - ), - } - }, - None => ( - Err(APIError::APIMisuseError { - err: format!( - "No state for the counterparty exists: {:?}", - counterparty_node_id - ), - }), - None, - ), - } - }; + match self.remove_pending_request(&mut peer_state_lock, &request_id) { + Some(LSPS2Request::Buy(buy_request)) => { + { + let mut peer_by_intercept_scid = + self.peer_by_intercept_scid.write().unwrap(); + peer_by_intercept_scid.insert(intercept_scid, *counterparty_node_id); + } - if let Some(response) = response { - let msg = LSPS2Message::Response(request_id, response).into(); - self.pending_messages.enqueue(counterparty_node_id, msg); + let outbound_jit_channel = OutboundJITChannel::new( + buy_request.payment_size_msat, + buy_request.opening_fee_params, + user_channel_id, + ); + + peer_state_lock + .intercept_scid_by_user_channel_id + .insert(user_channel_id, intercept_scid); + peer_state_lock + .insert_outbound_channel(intercept_scid, outbound_jit_channel); + + let response = LSPS2Response::Buy(LSPS2BuyResponse { + jit_channel_scid: intercept_scid.into(), + lsp_cltv_expiry_delta: cltv_expiry_delta, + client_trusts_lsp, + }); + let msg = LSPS2Message::Response(request_id, response).into(); + message_queue_notifier.enqueue(counterparty_node_id, msg); + Ok(()) + }, + _ => Err(APIError::APIMisuseError { + err: format!("No pending buy request for request_id: {:?}", request_id), + }), + } + }, + None => Err(APIError::APIMisuseError { + err: format!("No state for the counterparty exists: {:?}", counterparty_node_id), + }), } - - result } /// Forward [`Event::HTLCIntercepted`] event parameters into this function. @@ -1202,42 +1149,40 @@ where &self, request_id: LSPSRequestId, counterparty_node_id: &PublicKey, params: LSPS2GetInfoRequest, ) -> Result<(), LightningError> { + let mut message_queue_notifier = self.pending_messages.notifier(); let event_queue_notifier = self.pending_events.notifier(); - let (result, response) = { - let mut outer_state_lock = self.per_peer_state.write().unwrap(); - let inner_state_lock = - get_or_insert_peer_state_entry!(self, outer_state_lock, counterparty_node_id); - let mut peer_state_lock = inner_state_lock.lock().unwrap(); - let request = LSPS2Request::GetInfo(params.clone()); - match self.insert_pending_request( - &mut peer_state_lock, - request_id.clone(), - *counterparty_node_id, - request, - ) { - (Ok(()), msg) => { - let event = LSPS2ServiceEvent::GetInfo { - request_id, - counterparty_node_id: *counterparty_node_id, - token: params.token, - }; - event_queue_notifier.enqueue(event); - (Ok(()), msg) - }, - (e, msg) => (e, msg), - } - }; - if let Some(msg) = response { - self.pending_messages.enqueue(counterparty_node_id, msg); - } + let mut outer_state_lock = self.per_peer_state.write().unwrap(); + let inner_state_lock = get_or_insert_peer_state_entry!( + self, + outer_state_lock, + message_queue_notifier, + counterparty_node_id + ); + let mut peer_state_lock = inner_state_lock.lock().unwrap(); + let request = LSPS2Request::GetInfo(params.clone()); + self.insert_pending_request( + &mut peer_state_lock, + &mut message_queue_notifier, + request_id.clone(), + *counterparty_node_id, + request, + )?; + + let event = LSPS2ServiceEvent::GetInfo { + request_id, + counterparty_node_id: *counterparty_node_id, + token: params.token, + }; + event_queue_notifier.enqueue(event); - result + Ok(()) } fn handle_buy_request( &self, request_id: LSPSRequestId, counterparty_node_id: &PublicKey, params: LSPS2BuyRequest, ) -> Result<(), LightningError> { + let mut message_queue_notifier = self.pending_messages.notifier(); let event_queue_notifier = self.pending_events.notifier(); if let Some(payment_size_msat) = params.payment_size_msat { if payment_size_msat < params.opening_fee_params.min_payment_size_msat { @@ -1247,7 +1192,7 @@ where data: None, }); let msg = LSPS2Message::Response(request_id, response).into(); - self.pending_messages.enqueue(counterparty_node_id, msg); + message_queue_notifier.enqueue(counterparty_node_id, msg); return Err(LightningError { err: "payment size is below our minimum supported payment size".to_string(), @@ -1262,7 +1207,7 @@ where data: None, }); let msg = LSPS2Message::Response(request_id, response).into(); - self.pending_messages.enqueue(counterparty_node_id, msg); + message_queue_notifier.enqueue(counterparty_node_id, msg); return Err(LightningError { err: "payment size is above our maximum supported payment size".to_string(), action: ErrorAction::IgnoreAndLog(Level::Info), @@ -1283,7 +1228,7 @@ where data: None, }); let msg = LSPS2Message::Response(request_id, response).into(); - self.pending_messages.enqueue(counterparty_node_id, msg); + message_queue_notifier.enqueue(counterparty_node_id, msg); return Err(LightningError { err: "payment size is too small to cover the opening fee".to_string(), action: ErrorAction::IgnoreAndLog(Level::Info), @@ -1297,7 +1242,7 @@ where data: None, }); let msg = LSPS2Message::Response(request_id, response).into(); - self.pending_messages.enqueue(counterparty_node_id, msg); + message_queue_notifier.enqueue(counterparty_node_id, msg); return Err(LightningError { err: "overflow error when calculating opening_fee".to_string(), action: ErrorAction::IgnoreAndLog(Level::Info), @@ -1314,90 +1259,90 @@ where data: None, }); let msg = LSPS2Message::Response(request_id, response).into(); - self.pending_messages.enqueue(counterparty_node_id, msg); + message_queue_notifier.enqueue(counterparty_node_id, msg); return Err(LightningError { err: "invalid opening fee parameters were supplied by client".to_string(), action: ErrorAction::IgnoreAndLog(Level::Info), }); } - let (result, response) = { - let mut outer_state_lock = self.per_peer_state.write().unwrap(); - let inner_state_lock = - get_or_insert_peer_state_entry!(self, outer_state_lock, counterparty_node_id); - let mut peer_state_lock = inner_state_lock.lock().unwrap(); - - let request = LSPS2Request::Buy(params.clone()); - match self.insert_pending_request( - &mut peer_state_lock, - request_id.clone(), - *counterparty_node_id, - request, - ) { - (Ok(()), msg) => { - let event = LSPS2ServiceEvent::BuyRequest { - request_id, - counterparty_node_id: *counterparty_node_id, - opening_fee_params: params.opening_fee_params, - payment_size_msat: params.payment_size_msat, - }; - event_queue_notifier.enqueue(event); - - (Ok(()), msg) - }, - (e, msg) => (e, msg), - } + let mut outer_state_lock = self.per_peer_state.write().unwrap(); + let inner_state_lock = get_or_insert_peer_state_entry!( + self, + outer_state_lock, + message_queue_notifier, + counterparty_node_id + ); + let mut peer_state_lock = inner_state_lock.lock().unwrap(); + + let request = LSPS2Request::Buy(params.clone()); + + self.insert_pending_request( + &mut peer_state_lock, + &mut message_queue_notifier, + request_id.clone(), + *counterparty_node_id, + request, + )?; + + let event = LSPS2ServiceEvent::BuyRequest { + request_id, + counterparty_node_id: *counterparty_node_id, + opening_fee_params: params.opening_fee_params, + payment_size_msat: params.payment_size_msat, }; + event_queue_notifier.enqueue(event); - if let Some(msg) = response { - self.pending_messages.enqueue(counterparty_node_id, msg); - } - - result + Ok(()) } fn insert_pending_request<'a>( - &self, peer_state_lock: &mut MutexGuard<'a, PeerState>, request_id: LSPSRequestId, + &self, peer_state_lock: &mut MutexGuard<'a, PeerState>, + message_queue_notifier: &mut MessageQueueNotifierGuard, request_id: LSPSRequestId, counterparty_node_id: PublicKey, request: LSPS2Request, - ) -> (Result<(), LightningError>, Option) { - let create_pending_request_limit_exceeded_response = |error_message: String| { - let error_details = LSPSResponseError { - code: LSPS0_CLIENT_REJECTED_ERROR_CODE, - message: "Reached maximum number of pending requests. Please try again later." - .to_string(), - data: None, - }; - let response = match &request { - LSPS2Request::GetInfo(_) => LSPS2Response::GetInfoError(error_details), - LSPS2Request::Buy(_) => LSPS2Response::BuyError(error_details), - }; - let msg = Some(LSPS2Message::Response(request_id.clone(), response).into()); + ) -> Result<(), LightningError> { + let create_pending_request_limit_exceeded_response = + |message_queue_notifier: &mut MessageQueueNotifierGuard, error_message: String| { + let error_details = LSPSResponseError { + code: LSPS0_CLIENT_REJECTED_ERROR_CODE, + message: "Reached maximum number of pending requests. Please try again later." + .to_string(), + data: None, + }; + let response = match &request { + LSPS2Request::GetInfo(_) => LSPS2Response::GetInfoError(error_details), + LSPS2Request::Buy(_) => LSPS2Response::BuyError(error_details), + }; + let msg = LSPS2Message::Response(request_id.clone(), response).into(); + message_queue_notifier.enqueue(&counterparty_node_id, msg); - let result = Err(LightningError { - err: error_message, - action: ErrorAction::IgnoreAndLog(Level::Debug), - }); - (result, msg) - }; + Err(LightningError { + err: error_message, + action: ErrorAction::IgnoreAndLog(Level::Debug), + }) + }; if self.total_pending_requests.load(Ordering::Relaxed) >= MAX_TOTAL_PENDING_REQUESTS { let error_message = format!( "Reached maximum number of total pending requests: {}", MAX_TOTAL_PENDING_REQUESTS ); - return create_pending_request_limit_exceeded_response(error_message); + return create_pending_request_limit_exceeded_response( + message_queue_notifier, + error_message, + ); } if peer_state_lock.pending_requests_and_channels() < MAX_PENDING_REQUESTS_PER_PEER { peer_state_lock.pending_requests.insert(request_id, request); self.total_pending_requests.fetch_add(1, Ordering::Relaxed); - (Ok(()), None) + Ok(()) } else { let error_message = format!( "Peer {} reached maximum number of pending requests: {}", counterparty_node_id, MAX_PENDING_REQUESTS_PER_PEER ); - create_pending_request_limit_exceeded_response(error_message) + create_pending_request_limit_exceeded_response(message_queue_notifier, error_message) } } diff --git a/lightning-liquidity/src/lsps2/utils.rs b/lightning-liquidity/src/lsps2/utils.rs index a2c4d65936d..e4620043424 100644 --- a/lightning-liquidity/src/lsps2/utils.rs +++ b/lightning-liquidity/src/lsps2/utils.rs @@ -1,3 +1,10 @@ +// This file is Copyright its original authors, visible in version control history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license , at your option. +// You may not use this file except in accordance with one or both of these licenses. + //! Utilities for implementing the bLIP-52 / LSPS2 standard. use crate::lsps2::msgs::LSPS2OpeningFeeParams; diff --git a/lightning-liquidity/src/lsps5/client.rs b/lightning-liquidity/src/lsps5/client.rs index 50b2c85ce1d..40e7d49913b 100644 --- a/lightning-liquidity/src/lsps5/client.rs +++ b/lightning-liquidity/src/lsps5/client.rs @@ -11,7 +11,7 @@ use crate::alloc::string::ToString; use crate::events::EventQueue; -use crate::lsps0::ser::{LSPSDateTime, LSPSMessage, LSPSProtocolMessageHandler, LSPSRequestId}; +use crate::lsps0::ser::{LSPSMessage, LSPSProtocolMessageHandler, LSPSRequestId}; use crate::lsps5::event::LSPS5ClientEvent; use crate::lsps5::msgs::{ LSPS5Message, LSPS5Request, LSPS5Response, ListWebhooksRequest, RemoveWebhookRequest, @@ -22,7 +22,6 @@ use crate::message_queue::MessageQueue; use crate::prelude::{new_hash_map, HashMap}; use crate::sync::{Arc, Mutex, RwLock}; use crate::utils::generate_request_id; -use crate::utils::time::TimeProvider; use super::msgs::{LSPS5AppName, LSPS5Error, LSPS5WebhookUrl}; @@ -32,78 +31,75 @@ use lightning::ln::msgs::{ErrorAction, LightningError}; use lightning::sign::EntropySource; use lightning::util::logger::Level; +use alloc::collections::VecDeque; use alloc::string::String; use core::ops::Deref; -use core::time::Duration; -/// Default maximum age in seconds for cached responses (1 hour). -pub const DEFAULT_RESPONSE_MAX_AGE_SECS: u64 = 3600; - -#[derive(Debug, Clone)] -/// Configuration for the LSPS5 client -pub struct LSPS5ClientConfig { - /// Maximum age in seconds for cached responses (default: [`DEFAULT_RESPONSE_MAX_AGE_SECS`]). - pub response_max_age_secs: Duration, +impl PartialEq for (LSPSRequestId, (LSPS5AppName, LSPS5WebhookUrl)) { + fn eq(&self, other: &LSPSRequestId) -> bool { + &self.0 == other + } } -impl Default for LSPS5ClientConfig { - fn default() -> Self { - Self { response_max_age_secs: Duration::from_secs(DEFAULT_RESPONSE_MAX_AGE_SECS) } +impl PartialEq for (LSPSRequestId, LSPS5AppName) { + fn eq(&self, other: &LSPSRequestId) -> bool { + &self.0 == other } } -struct PeerState -where - TP::Target: TimeProvider, -{ - pending_set_webhook_requests: - HashMap, - pending_list_webhooks_requests: HashMap, - pending_remove_webhook_requests: HashMap, - last_cleanup: Option, - max_age_secs: Duration, - time_provider: TP, +#[derive(Debug, Clone, Copy, Default)] +/// Configuration for the LSPS5 client +pub struct LSPS5ClientConfig {} + +struct PeerState { + pending_set_webhook_requests: VecDeque<(LSPSRequestId, (LSPS5AppName, LSPS5WebhookUrl))>, + pending_list_webhooks_requests: VecDeque, + pending_remove_webhook_requests: VecDeque<(LSPSRequestId, LSPS5AppName)>, } -impl PeerState -where - TP::Target: TimeProvider, -{ - fn new(max_age_secs: Duration, time_provider: TP) -> Self { +const MAX_PENDING_REQUESTS: usize = 5; + +impl PeerState { + fn new() -> Self { Self { - pending_set_webhook_requests: new_hash_map(), - pending_list_webhooks_requests: new_hash_map(), - pending_remove_webhook_requests: new_hash_map(), - last_cleanup: None, - max_age_secs, - time_provider, + pending_set_webhook_requests: VecDeque::with_capacity(MAX_PENDING_REQUESTS), + pending_list_webhooks_requests: VecDeque::with_capacity(MAX_PENDING_REQUESTS), + pending_remove_webhook_requests: VecDeque::with_capacity(MAX_PENDING_REQUESTS), } } - fn cleanup_expired_responses(&mut self) { - let now = - LSPSDateTime::new_from_duration_since_epoch(self.time_provider.duration_since_epoch()); - // Only run cleanup once per minute to avoid excessive processing - const CLEANUP_INTERVAL: Duration = Duration::from_secs(60); - if let Some(last_cleanup) = &self.last_cleanup { - let time_since_last_cleanup = Duration::from_secs(now.abs_diff(&last_cleanup)); - if time_since_last_cleanup < CLEANUP_INTERVAL { - return; - } + fn add_request(&mut self, item: T, queue_selector: F) + where + F: FnOnce(&mut Self) -> &mut VecDeque, + { + let queue = queue_selector(self); + if queue.len() == MAX_PENDING_REQUESTS { + queue.pop_front(); } + queue.push_back(item); + } - self.last_cleanup = Some(now.clone()); + fn find_and_remove_request( + &mut self, queue_selector: F, request_id: &LSPSRequestId, + ) -> Option + where + F: FnOnce(&mut Self) -> &mut VecDeque, + T: Clone, + for<'a> &'a T: PartialEq<&'a LSPSRequestId>, + { + let queue = queue_selector(self); + if let Some(pos) = queue.iter().position(|item| item == request_id) { + queue.remove(pos) + } else { + None + } + } - self.pending_set_webhook_requests.retain(|_, (_, _, timestamp)| { - Duration::from_secs(timestamp.abs_diff(&now)) < self.max_age_secs - }); - self.pending_list_webhooks_requests.retain(|_, timestamp| { - Duration::from_secs(timestamp.abs_diff(&now)) < self.max_age_secs - }); - self.pending_remove_webhook_requests.retain(|_, (_, timestamp)| { - Duration::from_secs(timestamp.abs_diff(&now)) < self.max_age_secs - }); + fn is_empty(&self) -> bool { + self.pending_set_webhook_requests.is_empty() + && self.pending_list_webhooks_requests.is_empty() + && self.pending_remove_webhook_requests.is_empty() } } @@ -128,51 +124,44 @@ where /// [`lsps5.list_webhooks`]: super::msgs::LSPS5Request::ListWebhooks /// [`lsps5.remove_webhook`]: super::msgs::LSPS5Request::RemoveWebhook /// [`LSPS5Validator`]: super::validator::LSPS5Validator -pub struct LSPS5ClientHandler +pub struct LSPS5ClientHandler where ES::Target: EntropySource, - TP::Target: TimeProvider, { pending_messages: Arc, pending_events: Arc, entropy_source: ES, - per_peer_state: RwLock>>>, - config: LSPS5ClientConfig, - time_provider: TP, + per_peer_state: RwLock>>, + _config: LSPS5ClientConfig, } -impl LSPS5ClientHandler +impl LSPS5ClientHandler where ES::Target: EntropySource, - TP::Target: TimeProvider, { /// Constructs an `LSPS5ClientHandler`. - pub(crate) fn new_with_time_provider( + pub(crate) fn new( entropy_source: ES, pending_messages: Arc, pending_events: Arc, - config: LSPS5ClientConfig, time_provider: TP, + _config: LSPS5ClientConfig, ) -> Self { Self { pending_messages, pending_events, entropy_source, per_peer_state: RwLock::new(new_hash_map()), - config, - time_provider, + _config, } } fn with_peer_state(&self, counterparty_node_id: PublicKey, f: F) -> R where - F: FnOnce(&mut PeerState) -> R, + F: FnOnce(&mut PeerState) -> R, { let mut outer_state_lock = self.per_peer_state.write().unwrap(); - let inner_state_lock = outer_state_lock.entry(counterparty_node_id).or_insert(Mutex::new( - PeerState::new(self.config.response_max_age_secs, self.time_provider.clone()), - )); + let inner_state_lock = + outer_state_lock.entry(counterparty_node_id).or_insert(Mutex::new(PeerState::new())); let mut peer_state_lock = inner_state_lock.lock().unwrap(); - peer_state_lock.cleanup_expired_responses(); - f(&mut *peer_state_lock) } @@ -205,6 +194,7 @@ where pub fn set_webhook( &self, counterparty_node_id: PublicKey, app_name: String, webhook_url: String, ) -> Result { + let mut message_queue_notifier = self.pending_messages.notifier(); let app_name = LSPS5AppName::from_string(app_name)?; let lsps_webhook_url = LSPS5WebhookUrl::from_string(webhook_url)?; @@ -212,15 +202,9 @@ where let request_id = generate_request_id(&self.entropy_source); self.with_peer_state(counterparty_node_id, |peer_state| { - peer_state.pending_set_webhook_requests.insert( - request_id.clone(), - ( - app_name.clone(), - lsps_webhook_url.clone(), - LSPSDateTime::new_from_duration_since_epoch( - self.time_provider.duration_since_epoch(), - ), - ), + peer_state.add_request( + (request_id.clone(), (app_name.clone(), lsps_webhook_url.clone())), + |s| &mut s.pending_set_webhook_requests, ); }); @@ -228,7 +212,7 @@ where LSPS5Request::SetWebhook(SetWebhookRequest { app_name, webhook: lsps_webhook_url }); let message = LSPS5Message::Request(request_id.clone(), request); - self.pending_messages.enqueue(&counterparty_node_id, LSPSMessage::LSPS5(message)); + message_queue_notifier.enqueue(&counterparty_node_id, LSPSMessage::LSPS5(message)); Ok(request_id) } @@ -250,17 +234,16 @@ where /// [`WebhooksListed`]: super::event::LSPS5ClientEvent::WebhooksListed /// [`LSPS5Response::ListWebhooks`]: super::msgs::LSPS5Response::ListWebhooks pub fn list_webhooks(&self, counterparty_node_id: PublicKey) -> LSPSRequestId { + let mut message_queue_notifier = self.pending_messages.notifier(); let request_id = generate_request_id(&self.entropy_source); - let now = - LSPSDateTime::new_from_duration_since_epoch(self.time_provider.duration_since_epoch()); self.with_peer_state(counterparty_node_id, |peer_state| { - peer_state.pending_list_webhooks_requests.insert(request_id.clone(), now); + peer_state.add_request(request_id.clone(), |s| &mut s.pending_list_webhooks_requests); }); let request = LSPS5Request::ListWebhooks(ListWebhooksRequest {}); let message = LSPS5Message::Request(request_id.clone(), request); - self.pending_messages.enqueue(&counterparty_node_id, LSPSMessage::LSPS5(message)); + message_queue_notifier.enqueue(&counterparty_node_id, LSPSMessage::LSPS5(message)); request_id } @@ -287,21 +270,20 @@ where pub fn remove_webhook( &self, counterparty_node_id: PublicKey, app_name: String, ) -> Result { + let mut message_queue_notifier = self.pending_messages.notifier(); let app_name = LSPS5AppName::from_string(app_name)?; let request_id = generate_request_id(&self.entropy_source); - let now = - LSPSDateTime::new_from_duration_since_epoch(self.time_provider.duration_since_epoch()); self.with_peer_state(counterparty_node_id, |peer_state| { - peer_state - .pending_remove_webhook_requests - .insert(request_id.clone(), (app_name.clone(), now)); + peer_state.add_request((request_id.clone(), app_name.clone()), |s| { + &mut s.pending_remove_webhook_requests + }); }); let request = LSPS5Request::RemoveWebhook(RemoveWebhookRequest { app_name }); let message = LSPS5Message::Request(request_id.clone(), request); - self.pending_messages.enqueue(&counterparty_node_id, LSPSMessage::LSPS5(message)); + message_queue_notifier.enqueue(&counterparty_node_id, LSPSMessage::LSPS5(message)); Ok(request_id) } @@ -326,9 +308,9 @@ where action: ErrorAction::IgnoreAndLog(Level::Debug), }); let event_queue_notifier = self.pending_events.notifier(); - let handle_response = |peer_state: &mut PeerState| { - if let Some((app_name, webhook_url, _)) = - peer_state.pending_set_webhook_requests.remove(&request_id) + let handle_response = |peer_state: &mut PeerState| { + if let Some((_, (app_name, webhook_url))) = peer_state + .find_and_remove_request(|s| &mut s.pending_set_webhook_requests, &request_id) { match &response { LSPS5Response::SetWebhook(r) => { @@ -360,7 +342,9 @@ where }); }, } - } else if peer_state.pending_list_webhooks_requests.remove(&request_id).is_some() { + } else if let Some(_) = peer_state + .find_and_remove_request(|s| &mut s.pending_list_webhooks_requests, &request_id) + { match &response { LSPS5Response::ListWebhooks(r) => { event_queue_notifier.enqueue(LSPS5ClientEvent::WebhooksListed { @@ -378,8 +362,8 @@ where }); }, } - } else if let Some((app_name, _)) = - peer_state.pending_remove_webhook_requests.remove(&request_id) + } else if let Some((_, app_name)) = peer_state + .find_and_remove_request(|s| &mut s.pending_remove_webhook_requests, &request_id) { match &response { LSPS5Response::RemoveWebhook(_) => { @@ -414,14 +398,31 @@ where } }; self.with_peer_state(*counterparty_node_id, handle_response); + + self.check_and_remove_empty_peer_state(counterparty_node_id); + result } + + fn check_and_remove_empty_peer_state(&self, counterparty_node_id: &PublicKey) { + let mut outer_state_lock = self.per_peer_state.write().unwrap(); + let should_remove = + if let Some(peer_state_mutex) = outer_state_lock.get(counterparty_node_id) { + let peer_state = peer_state_mutex.lock().unwrap(); + peer_state.is_empty() + } else { + false + }; + + if should_remove { + outer_state_lock.remove(counterparty_node_id); + } + } } -impl LSPSProtocolMessageHandler for LSPS5ClientHandler +impl LSPSProtocolMessageHandler for LSPS5ClientHandler where ES::Target: EntropySource, - TP::Target: TimeProvider, { type ProtocolMessage = LSPS5Message; const PROTOCOL_NUMBER: Option = Some(5); @@ -435,31 +436,40 @@ where #[cfg(all(test, feature = "time"))] mod tests { - use core::time::Duration; use super::*; - use crate::{ - lsps0::ser::LSPSRequestId, lsps5::msgs::SetWebhookResponse, tests::utils::TestEntropy, - utils::time::DefaultTimeProvider, - }; + use crate::{lsps0::ser::LSPSRequestId, lsps5::msgs::SetWebhookResponse}; use bitcoin::{key::Secp256k1, secp256k1::SecretKey}; + use core::sync::atomic::{AtomicU64, Ordering}; + + struct UniqueTestEntropy { + counter: AtomicU64, + } + + impl EntropySource for UniqueTestEntropy { + fn get_secure_random_bytes(&self) -> [u8; 32] { + let counter = self.counter.fetch_add(1, Ordering::SeqCst); + let mut bytes = [0u8; 32]; + bytes[0..8].copy_from_slice(&counter.to_be_bytes()); + bytes + } + } fn setup_test_client() -> ( - LSPS5ClientHandler, Arc>, + LSPS5ClientHandler>, Arc, Arc, PublicKey, PublicKey, ) { - let test_entropy_source = Arc::new(TestEntropy {}); + let test_entropy_source = Arc::new(UniqueTestEntropy { counter: AtomicU64::new(2) }); let message_queue = Arc::new(MessageQueue::new()); let event_queue = Arc::new(EventQueue::new()); - let client = LSPS5ClientHandler::new_with_time_provider( + let client = LSPS5ClientHandler::new( test_entropy_source, Arc::clone(&message_queue), Arc::clone(&event_queue), LSPS5ClientConfig::default(), - Arc::new(DefaultTimeProvider), ); let secp = Secp256k1::new(); @@ -486,10 +496,16 @@ mod tests { let outer_state_lock = client.per_peer_state.read().unwrap(); let peer_1_state = outer_state_lock.get(&peer_1).unwrap().lock().unwrap(); - assert!(peer_1_state.pending_set_webhook_requests.contains_key(&req_id_1)); + assert!(peer_1_state + .pending_set_webhook_requests + .iter() + .any(|(id, _)| id == &req_id_1)); let peer_2_state = outer_state_lock.get(&peer_2).unwrap().lock().unwrap(); - assert!(peer_2_state.pending_set_webhook_requests.contains_key(&req_id_2)); + assert!(peer_2_state + .pending_set_webhook_requests + .iter() + .any(|(id, _)| id == &req_id_2)); } } @@ -508,133 +524,162 @@ mod tests { { let outer_state_lock = client.per_peer_state.read().unwrap(); let peer_state = outer_state_lock.get(&peer).unwrap().lock().unwrap(); - assert_eq!( - peer_state.pending_set_webhook_requests.get(&set_req_id).unwrap(), - &( - lsps5_app_name.clone(), - lsps5_webhook_url, - peer_state.pending_set_webhook_requests.get(&set_req_id).unwrap().2.clone() - ) - ); + let set_request = peer_state + .pending_set_webhook_requests + .iter() + .find(|(id, _)| id == &set_req_id) + .unwrap(); + assert_eq!(&set_request.1, &(lsps5_app_name.clone(), lsps5_webhook_url)); - assert!(peer_state.pending_list_webhooks_requests.contains_key(&list_req_id)); + assert!(peer_state.pending_list_webhooks_requests.contains(&list_req_id)); - assert_eq!( - peer_state.pending_remove_webhook_requests.get(&remove_req_id).unwrap().0, - lsps5_app_name - ); + let remove_request = peer_state + .pending_remove_webhook_requests + .iter() + .find(|(id, _)| id == &remove_req_id) + .unwrap(); + assert_eq!(&remove_request.1, &lsps5_app_name); } } #[test] - fn test_handle_response_clears_pending_state() { - let (client, _, _, peer, _) = setup_test_client(); + fn test_unknown_request_id_handling() { + let (client, _message_queue, _, peer, _) = setup_test_client(); - let req_id = client + let _valid_req = client .set_webhook(peer, "test-app".to_string(), "https://example.com/hook".to_string()) .unwrap(); + let unknown_req_id = LSPSRequestId("unknown:request:id".to_string()); let response = LSPS5Response::SetWebhook(SetWebhookResponse { num_webhooks: 1, max_webhooks: 5, no_change: false, }); - let response_msg = LSPS5Message::Response(req_id.clone(), response); + let response_msg = LSPS5Message::Response(unknown_req_id, response); + + let result = client.handle_message(response_msg, &peer); + assert!(result.is_err()); + let error = result.unwrap_err(); + assert!(error.err.to_lowercase().contains("unknown request id")); + } + + #[test] + fn test_pending_request_eviction() { + let (client, _, _, peer, _) = setup_test_client(); + + let mut request_ids = Vec::new(); + for i in 0..MAX_PENDING_REQUESTS { + let req_id = client + .set_webhook(peer, format!("app-{}", i), format!("https://example.com/hook{}", i)) + .unwrap(); + request_ids.push(req_id); + } { let outer_state_lock = client.per_peer_state.read().unwrap(); let peer_state = outer_state_lock.get(&peer).unwrap().lock().unwrap(); - assert!(peer_state.pending_set_webhook_requests.contains_key(&req_id)); + for req_id in &request_ids { + assert!(peer_state.pending_set_webhook_requests.iter().any(|(id, _)| id == req_id)); + } + assert_eq!(peer_state.pending_set_webhook_requests.len(), MAX_PENDING_REQUESTS); } - client.handle_message(response_msg, &peer).unwrap(); + let new_req_id = client + .set_webhook(peer, "app-new".to_string(), "https://example.com/hook-new".to_string()) + .unwrap(); { let outer_state_lock = client.per_peer_state.read().unwrap(); let peer_state = outer_state_lock.get(&peer).unwrap().lock().unwrap(); - assert!(!peer_state.pending_set_webhook_requests.contains_key(&req_id)); - } - } - - #[test] - fn test_cleanup_expired_responses() { - let (client, _, _, _, _) = setup_test_client(); - let time_provider = &client.time_provider; - const OLD_APP_NAME: &str = "test-app-old"; - const NEW_APP_NAME: &str = "test-app-new"; - const WEBHOOK_URL: &str = "https://example.com/hook"; - let lsps5_old_app_name = LSPS5AppName::from_string(OLD_APP_NAME.to_string()).unwrap(); - let lsps5_new_app_name = LSPS5AppName::from_string(NEW_APP_NAME.to_string()).unwrap(); - let lsps5_webhook_url = LSPS5WebhookUrl::from_string(WEBHOOK_URL.to_string()).unwrap(); - let now = time_provider.duration_since_epoch(); - let mut peer_state = PeerState::>::new( - Duration::from_secs(1800), - Arc::clone(time_provider), - ); - peer_state.last_cleanup = Some(LSPSDateTime::new_from_duration_since_epoch( - now.checked_sub(Duration::from_secs(120)).unwrap(), - )); - - let old_request_id = LSPSRequestId("test:request:old".to_string()); - let new_request_id = LSPSRequestId("test:request:new".to_string()); - - // Add an old request (should be removed during cleanup) - peer_state.pending_set_webhook_requests.insert( - old_request_id.clone(), - ( - lsps5_old_app_name, - lsps5_webhook_url.clone(), - LSPSDateTime::new_from_duration_since_epoch( - now.checked_sub(Duration::from_secs(7200)).unwrap(), - ), - ), // 2 hours old - ); + assert_eq!(peer_state.pending_set_webhook_requests.len(), MAX_PENDING_REQUESTS); - // Add a recent request (should be kept) - peer_state.pending_set_webhook_requests.insert( - new_request_id.clone(), - ( - lsps5_new_app_name, - lsps5_webhook_url, - LSPSDateTime::new_from_duration_since_epoch( - now.checked_sub(Duration::from_secs(600)).unwrap(), - ), - ), // 10 minutes old - ); - - peer_state.cleanup_expired_responses(); + assert!(!peer_state + .pending_set_webhook_requests + .iter() + .any(|(id, _)| id == &request_ids[0])); - assert!(!peer_state.pending_set_webhook_requests.contains_key(&old_request_id)); - assert!(peer_state.pending_set_webhook_requests.contains_key(&new_request_id)); + for req_id in &request_ids[1..] { + assert!(peer_state.pending_set_webhook_requests.iter().any(|(id, _)| id == req_id)); + } - let cleanup_age = if let Some(last_cleanup) = peer_state.last_cleanup { - LSPSDateTime::new_from_duration_since_epoch(time_provider.duration_since_epoch()) - .abs_diff(&last_cleanup) - } else { - 0 - }; - assert!(cleanup_age < 10); + assert!(peer_state + .pending_set_webhook_requests + .iter() + .any(|(id, _)| id == &new_req_id)); + } } #[test] - fn test_unknown_request_id_handling() { - let (client, _message_queue, _, peer, _) = setup_test_client(); + fn test_peer_state_cleanup_and_recreation() { + let (client, _, _, peer, _) = setup_test_client(); - let _valid_req = client + let set_webhook_req_id = client .set_webhook(peer, "test-app".to_string(), "https://example.com/hook".to_string()) .unwrap(); - let unknown_req_id = LSPSRequestId("unknown:request:id".to_string()); - let response = LSPS5Response::SetWebhook(SetWebhookResponse { + let list_webhooks_req_id = client.list_webhooks(peer); + + { + let state = client.per_peer_state.read().unwrap(); + assert!(state.contains_key(&peer)); + let peer_state = state.get(&peer).unwrap().lock().unwrap(); + assert!(peer_state + .pending_set_webhook_requests + .iter() + .any(|(id, _)| id == &set_webhook_req_id)); + assert!(peer_state.pending_list_webhooks_requests.contains(&list_webhooks_req_id)); + } + + let set_webhook_response = LSPS5Response::SetWebhook(SetWebhookResponse { num_webhooks: 1, max_webhooks: 5, no_change: false, }); - let response_msg = LSPS5Message::Response(unknown_req_id, response); + let response_msg = LSPS5Message::Response(set_webhook_req_id.clone(), set_webhook_response); + // trigger cleanup but there is still a pending request + // so the peer state should not be removed + client.handle_message(response_msg, &peer).unwrap(); - let result = client.handle_message(response_msg, &peer); - assert!(result.is_err()); - let error = result.unwrap_err(); - assert!(error.err.to_lowercase().contains("unknown request id")); + { + let state = client.per_peer_state.read().unwrap(); + assert!(state.contains_key(&peer)); + let peer_state = state.get(&peer).unwrap().lock().unwrap(); + assert!(!peer_state + .pending_set_webhook_requests + .iter() + .any(|(id, _)| id == &set_webhook_req_id)); + assert!(peer_state.pending_list_webhooks_requests.contains(&list_webhooks_req_id)); + } + + let list_webhooks_response = + LSPS5Response::ListWebhooks(crate::lsps5::msgs::ListWebhooksResponse { + app_names: vec![], + max_webhooks: 5, + }); + let response_msg = LSPS5Message::Response(list_webhooks_req_id, list_webhooks_response); + + // now the pending request is handled, so the peer state should be removed + client.handle_message(response_msg, &peer).unwrap(); + + { + let state = client.per_peer_state.read().unwrap(); + assert!(!state.contains_key(&peer)); + } + + // check that it's possible to recreate the peer state by sending a new request + let new_req_id = client + .set_webhook(peer, "test-app-2".to_string(), "https://example.com/hook2".to_string()) + .unwrap(); + + { + let state = client.per_peer_state.read().unwrap(); + assert!(state.contains_key(&peer)); + let peer_state = state.get(&peer).unwrap().lock().unwrap(); + assert!(peer_state + .pending_set_webhook_requests + .iter() + .any(|(id, _)| id == &new_req_id)); + } } } diff --git a/lightning-liquidity/src/lsps5/event.rs b/lightning-liquidity/src/lsps5/event.rs index 7730428e5ce..f401c0e10ac 100644 --- a/lightning-liquidity/src/lsps5/event.rs +++ b/lightning-liquidity/src/lsps5/event.rs @@ -31,15 +31,13 @@ pub enum LSPS5ServiceEvent { /// The LSP should send an HTTP POST to the [`url`], using the /// JSON-serialized [`notification`] as the body and including the `headers`. /// If the HTTP request fails, the LSP may implement a retry policy according to its - /// implementation preferences, but must respect rate-limiting as defined in - /// [`notification_cooldown_hours`]. + /// implementation preferences. /// /// The notification is signed using the LSP's node ID to ensure authenticity /// when received by the client. The client verifies this signature using /// [`validate`], which guards against replay attacks and tampering. /// /// [`validate`]: super::validator::LSPS5Validator::validate - /// [`notification_cooldown_hours`]: super::service::LSPS5ServiceConfig::notification_cooldown_hours /// [`url`]: super::msgs::LSPS5WebhookUrl /// [`notification`]: super::msgs::WebhookNotification SendWebhookNotification { diff --git a/lightning-liquidity/src/lsps5/msgs.rs b/lightning-liquidity/src/lsps5/msgs.rs index ef0aaf4f5f4..ada1f263e03 100644 --- a/lightning-liquidity/src/lsps5/msgs.rs +++ b/lightning-liquidity/src/lsps5/msgs.rs @@ -51,6 +51,8 @@ pub const LSPS5_APP_NAME_NOT_FOUND_ERROR_CODE: i32 = 1010; pub const LSPS5_UNKNOWN_ERROR_CODE: i32 = 1000; /// An error occurred during serialization of LSPS5 webhook notification. pub const LSPS5_SERIALIZATION_ERROR_CODE: i32 = 1001; +/// A notification was sent too frequently. +pub const LSPS5_SLOW_DOWN_ERROR_CODE: i32 = 1002; pub(crate) const LSPS5_SET_WEBHOOK_METHOD_NAME: &str = "lsps5.set_webhook"; pub(crate) const LSPS5_LIST_WEBHOOKS_METHOD_NAME: &str = "lsps5.list_webhooks"; @@ -103,10 +105,18 @@ pub enum LSPS5ProtocolError { /// Error during serialization of LSPS5 webhook notification. SerializationError, + + /// A notification was sent too frequently. + /// + /// This error indicates that the LSP is sending notifications + /// too quickly, violating the notification cooldown [`NOTIFICATION_COOLDOWN_TIME`] + /// + /// [`NOTIFICATION_COOLDOWN_TIME`]: super::service::NOTIFICATION_COOLDOWN_TIME + SlowDownError, } impl LSPS5ProtocolError { - /// private code range so we never collide with the spec's codes + /// The error code for the LSPS5 protocol error. pub fn code(&self) -> i32 { match self { LSPS5ProtocolError::AppNameTooLong | LSPS5ProtocolError::WebhookUrlTooLong => { @@ -118,6 +128,7 @@ impl LSPS5ProtocolError { LSPS5ProtocolError::AppNameNotFound => LSPS5_APP_NAME_NOT_FOUND_ERROR_CODE, LSPS5ProtocolError::UnknownError => LSPS5_UNKNOWN_ERROR_CODE, LSPS5ProtocolError::SerializationError => LSPS5_SERIALIZATION_ERROR_CODE, + LSPS5ProtocolError::SlowDownError => LSPS5_SLOW_DOWN_ERROR_CODE, } } /// The error message for the LSPS5 protocol error. @@ -133,6 +144,7 @@ impl LSPS5ProtocolError { LSPS5ProtocolError::SerializationError => { "Error serializing LSPS5 webhook notification" }, + LSPS5ProtocolError::SlowDownError => "Notification sent too frequently", } } } diff --git a/lightning-liquidity/src/lsps5/service.rs b/lightning-liquidity/src/lsps5/service.rs index 984dd5d0575..72c3d83b3fe 100644 --- a/lightning-liquidity/src/lsps5/service.rs +++ b/lightning-liquidity/src/lsps5/service.rs @@ -17,9 +17,8 @@ use crate::lsps5::msgs::{ SetWebhookRequest, SetWebhookResponse, WebhookNotification, WebhookNotificationMethod, }; use crate::message_queue::MessageQueue; -use crate::prelude::hash_map::Entry; use crate::prelude::*; -use crate::sync::{Arc, Mutex}; +use crate::sync::{Arc, Mutex, RwLock, RwLockWriteGuard}; use crate::utils::time::TimeProvider; use bitcoin::secp256k1::PublicKey; @@ -47,12 +46,16 @@ pub const PRUNE_STALE_WEBHOOKS_INTERVAL_DAYS: Duration = Duration::from_secs(24 /// A stored webhook. #[derive(Debug, Clone)] -struct StoredWebhook { +struct Webhook { _app_name: LSPS5AppName, url: LSPS5WebhookUrl, _counterparty_node_id: PublicKey, + // Timestamp used for tracking when the webhook was created / updated, or when the last notification was sent. + // This is used to determine if the webhook is stale and should be pruned. last_used: LSPSDateTime, - last_notification_sent: HashMap, + // Timestamp when we last sent a notification to the client. This is used to enforce + // notification cooldowns. + last_notification_sent: Option, } /// Server-side configuration options for LSPS5 Webhook Registration. @@ -60,8 +63,18 @@ struct StoredWebhook { pub struct LSPS5ServiceConfig { /// Maximum number of webhooks allowed per client. pub max_webhooks_per_client: u32, - /// Minimum time between sending the same notification type in hours (default: 24) - pub notification_cooldown_hours: Duration, +} + +/// Default maximum number of webhooks allowed per client. +pub const DEFAULT_MAX_WEBHOOKS_PER_CLIENT: u32 = 10; +/// Default notification cooldown time in minutes. +pub const NOTIFICATION_COOLDOWN_TIME: Duration = Duration::from_secs(60); // 1 minute + +// Default configuration for LSPS5 service. +impl Default for LSPS5ServiceConfig { + fn default() -> Self { + Self { max_webhooks_per_client: DEFAULT_MAX_WEBHOOKS_PER_CLIENT } + } } /// Service-side handler for the [`bLIP-55 / LSPS5`] webhook registration protocol. @@ -78,8 +91,6 @@ pub struct LSPS5ServiceConfig { /// - `lsps5.remove_webhook` -> delete a named webhook or return [`app_name_not_found`] error. /// - Prune stale webhooks after a client has no open channels and no activity for at least /// [`MIN_WEBHOOK_RETENTION_DAYS`]. -/// - Rate-limit repeat notifications of the same method to a client by -/// [`notification_cooldown_hours`]. /// - Sign and enqueue outgoing webhook notifications: /// - Construct JSON-RPC 2.0 Notification objects [`WebhookNotification`], /// - Timestamp and LN-style zbase32-sign each payload, @@ -94,7 +105,6 @@ pub struct LSPS5ServiceConfig { /// [`bLIP-55 / LSPS5`]: https://github.com/lightning/blips/pull/55/files /// [`max_webhooks_per_client`]: super::service::LSPS5ServiceConfig::max_webhooks_per_client /// [`app_name_not_found`]: super::msgs::LSPS5ProtocolError::AppNameNotFound -/// [`notification_cooldown_hours`]: super::service::LSPS5ServiceConfig::notification_cooldown_hours /// [`WebhookNotification`]: super::msgs::WebhookNotification /// [`LSPS5ServiceEvent::SendWebhookNotification`]: super::event::LSPS5ServiceEvent::SendWebhookNotification /// [`app_name`]: super::msgs::LSPS5AppName @@ -106,7 +116,7 @@ where TP::Target: TimeProvider, { config: LSPS5ServiceConfig, - webhooks: Mutex>>, + per_peer_state: RwLock>, event_queue: Arc, pending_messages: Arc, time_provider: TP, @@ -129,7 +139,7 @@ where assert!(config.max_webhooks_per_client > 0, "`max_webhooks_per_client` must be > 0"); Self { config, - webhooks: Mutex::new(new_hash_map()), + per_peer_state: RwLock::new(new_hash_map()), event_queue, pending_messages, time_provider, @@ -139,18 +149,26 @@ where } } - fn check_prune_stale_webhooks(&self) { + fn check_prune_stale_webhooks<'a>( + &self, outer_state_lock: &mut RwLockWriteGuard<'a, HashMap>, + ) { + let mut last_pruning = self.last_pruning.lock().unwrap(); let now = LSPSDateTime::new_from_duration_since_epoch(self.time_provider.duration_since_epoch()); - let should_prune = { - let last_pruning = self.last_pruning.lock().unwrap(); - last_pruning.as_ref().map_or(true, |last_time| { - now.abs_diff(&last_time) > PRUNE_STALE_WEBHOOKS_INTERVAL_DAYS.as_secs() - }) - }; + + let should_prune = last_pruning.as_ref().map_or(true, |last_time| { + now.duration_since(&last_time) > PRUNE_STALE_WEBHOOKS_INTERVAL_DAYS + }); if should_prune { - self.prune_stale_webhooks(); + outer_state_lock.retain(|client_id, peer_state| { + if self.client_has_open_channel(client_id) { + // Don't prune clients with open channels + return true; + } + !peer_state.prune_stale_webhooks(now) + }); + *last_pruning = Some(now); } } @@ -158,61 +176,57 @@ where &self, counterparty_node_id: PublicKey, request_id: LSPSRequestId, params: SetWebhookRequest, ) -> Result<(), LightningError> { - self.check_prune_stale_webhooks(); + let mut message_queue_notifier = self.pending_messages.notifier(); + + let mut outer_state_lock = self.per_peer_state.write().unwrap(); - let mut webhooks = self.webhooks.lock().unwrap(); + let peer_state = + outer_state_lock.entry(counterparty_node_id).or_insert_with(PeerState::default); - let client_webhooks = webhooks.entry(counterparty_node_id).or_insert_with(new_hash_map); let now = LSPSDateTime::new_from_duration_since_epoch(self.time_provider.duration_since_epoch()); - let num_webhooks = client_webhooks.len(); + let num_webhooks = peer_state.webhooks_len(); let mut no_change = false; - match client_webhooks.entry(params.app_name.clone()) { - Entry::Occupied(mut entry) => { - no_change = entry.get().url == params.webhook; - let (last_used, last_notification_sent) = if no_change { - (entry.get().last_used.clone(), entry.get().last_notification_sent.clone()) - } else { - (now, new_hash_map()) - }; - entry.insert(StoredWebhook { - _app_name: params.app_name.clone(), - url: params.webhook.clone(), - _counterparty_node_id: counterparty_node_id, - last_used, - last_notification_sent, - }); - }, - Entry::Vacant(entry) => { - if num_webhooks >= self.config.max_webhooks_per_client as usize { - let error = LSPS5ProtocolError::TooManyWebhooks; - let msg = LSPS5Message::Response( - request_id, - LSPS5Response::SetWebhookError(error.clone().into()), - ) - .into(); - self.pending_messages.enqueue(&counterparty_node_id, msg); - return Err(LightningError { - err: error.message().into(), - action: ErrorAction::IgnoreAndLog(Level::Info), - }); - } - entry.insert(StoredWebhook { - _app_name: params.app_name.clone(), - url: params.webhook.clone(), - _counterparty_node_id: counterparty_node_id, - last_used: now, - last_notification_sent: new_hash_map(), + if let Some(webhook) = peer_state.webhook_mut(¶ms.app_name) { + no_change = webhook.url == params.webhook; + if !no_change { + // The URL was updated. + webhook.url = params.webhook.clone(); + webhook.last_used = now; + webhook.last_notification_sent = None; + } + } else { + if num_webhooks >= self.config.max_webhooks_per_client as usize { + let error = LSPS5ProtocolError::TooManyWebhooks; + let msg = LSPS5Message::Response( + request_id, + LSPS5Response::SetWebhookError(error.clone().into()), + ) + .into(); + message_queue_notifier.enqueue(&counterparty_node_id, msg); + return Err(LightningError { + err: error.message().into(), + action: ErrorAction::IgnoreAndLog(Level::Info), }); - }, + } + + let webhook = Webhook { + _app_name: params.app_name.clone(), + url: params.webhook.clone(), + _counterparty_node_id: counterparty_node_id, + last_used: now, + last_notification_sent: None, + }; + + peer_state.insert_webhook(params.app_name.clone(), webhook); } if !no_change { self.send_webhook_registered_notification( counterparty_node_id, - params.app_name, + params.app_name.clone(), params.webhook, ) .map_err(|e| { @@ -221,7 +235,7 @@ where LSPS5Response::SetWebhookError(e.clone().into()), ) .into(); - self.pending_messages.enqueue(&counterparty_node_id, msg); + message_queue_notifier.enqueue(&counterparty_node_id, msg); LightningError { err: e.message().into(), action: ErrorAction::IgnoreAndLog(Level::Info), @@ -232,13 +246,13 @@ where let msg = LSPS5Message::Response( request_id, LSPS5Response::SetWebhook(SetWebhookResponse { - num_webhooks: client_webhooks.len() as u32, + num_webhooks: peer_state.webhooks_len() as u32, max_webhooks: self.config.max_webhooks_per_client, no_change, }), ) .into(); - self.pending_messages.enqueue(&counterparty_node_id, msg); + message_queue_notifier.enqueue(&counterparty_node_id, msg); Ok(()) } @@ -246,20 +260,17 @@ where &self, counterparty_node_id: PublicKey, request_id: LSPSRequestId, _params: ListWebhooksRequest, ) -> Result<(), LightningError> { - self.check_prune_stale_webhooks(); + let mut message_queue_notifier = self.pending_messages.notifier(); - let webhooks = self.webhooks.lock().unwrap(); - - let app_names = webhooks - .get(&counterparty_node_id) - .map(|client_webhooks| client_webhooks.keys().cloned().collect::>()) - .unwrap_or_else(Vec::new); + let outer_state_lock = self.per_peer_state.read().unwrap(); + let app_names = + outer_state_lock.get(&counterparty_node_id).map(|p| p.app_names()).unwrap_or_default(); let max_webhooks = self.config.max_webhooks_per_client; let response = ListWebhooksResponse { app_names, max_webhooks }; let msg = LSPS5Message::Response(request_id, LSPS5Response::ListWebhooks(response)).into(); - self.pending_messages.enqueue(&counterparty_node_id, msg); + message_queue_notifier.enqueue(&counterparty_node_id, msg); Ok(()) } @@ -268,17 +279,17 @@ where &self, counterparty_node_id: PublicKey, request_id: LSPSRequestId, params: RemoveWebhookRequest, ) -> Result<(), LightningError> { - self.check_prune_stale_webhooks(); + let mut message_queue_notifier = self.pending_messages.notifier(); - let mut webhooks = self.webhooks.lock().unwrap(); + let mut outer_state_lock = self.per_peer_state.write().unwrap(); - if let Some(client_webhooks) = webhooks.get_mut(&counterparty_node_id) { - if client_webhooks.remove(¶ms.app_name).is_some() { + if let Some(peer_state) = outer_state_lock.get_mut(&counterparty_node_id) { + if peer_state.remove_webhook(¶ms.app_name) { let response = RemoveWebhookResponse {}; let msg = LSPS5Message::Response(request_id, LSPS5Response::RemoveWebhook(response)) .into(); - self.pending_messages.enqueue(&counterparty_node_id, msg); + message_queue_notifier.enqueue(&counterparty_node_id, msg); return Ok(()); } @@ -291,7 +302,7 @@ where ) .into(); - self.pending_messages.enqueue(&counterparty_node_id, msg); + message_queue_notifier.enqueue(&counterparty_node_id, msg); return Err(LightningError { err: error.message().into(), action: ErrorAction::IgnoreAndLog(Level::Info), @@ -312,10 +323,14 @@ where /// This builds a [`WebhookNotificationMethod::LSPS5PaymentIncoming`] webhook notification, signs it with your /// node key, and enqueues HTTP POSTs to all registered webhook URLs for that client. /// + /// This may fail if a similar notification was sent too recently, + /// violating the notification cooldown period defined in [`NOTIFICATION_COOLDOWN_TIME`]. + /// /// # Parameters /// - `client_id`: the client's node-ID whose webhooks should be invoked. /// /// [`WebhookNotificationMethod::LSPS5PaymentIncoming`]: super::msgs::WebhookNotificationMethod::LSPS5PaymentIncoming + /// [`NOTIFICATION_COOLDOWN_TIME`]: super::service::NOTIFICATION_COOLDOWN_TIME pub fn notify_payment_incoming(&self, client_id: PublicKey) -> Result<(), LSPS5ProtocolError> { let notification = WebhookNotification::payment_incoming(); self.send_notifications_to_client_webhooks(client_id, notification) @@ -329,11 +344,15 @@ where /// the `timeout` block height, signs it, and enqueues HTTP POSTs to the client's /// registered webhooks. /// + /// This may fail if a similar notification was sent too recently, + /// violating the notification cooldown period defined in [`NOTIFICATION_COOLDOWN_TIME`]. + /// /// # Parameters /// - `client_id`: the client's node-ID whose webhooks should be invoked. /// - `timeout`: the block height at which the channel contract will expire. /// /// [`WebhookNotificationMethod::LSPS5ExpirySoon`]: super::msgs::WebhookNotificationMethod::LSPS5ExpirySoon + /// [`NOTIFICATION_COOLDOWN_TIME`]: super::service::NOTIFICATION_COOLDOWN_TIME pub fn notify_expiry_soon( &self, client_id: PublicKey, timeout: u32, ) -> Result<(), LSPS5ProtocolError> { @@ -347,10 +366,14 @@ where /// liquidity for `client_id`. Builds a [`WebhookNotificationMethod::LSPS5LiquidityManagementRequest`] notification, /// signs it, and sends it to all of the client's registered webhook URLs. /// + /// This may fail if a similar notification was sent too recently, + /// violating the notification cooldown period defined in [`NOTIFICATION_COOLDOWN_TIME`]. + /// /// # Parameters /// - `client_id`: the client's node-ID whose webhooks should be invoked. /// /// [`WebhookNotificationMethod::LSPS5LiquidityManagementRequest`]: super::msgs::WebhookNotificationMethod::LSPS5LiquidityManagementRequest + /// [`NOTIFICATION_COOLDOWN_TIME`]: super::service::NOTIFICATION_COOLDOWN_TIME pub fn notify_liquidity_management_request( &self, client_id: PublicKey, ) -> Result<(), LSPS5ProtocolError> { @@ -364,10 +387,14 @@ where /// for `client_id` while the client is offline. Builds a [`WebhookNotificationMethod::LSPS5OnionMessageIncoming`] /// notification, signs it, and enqueues HTTP POSTs to each registered webhook. /// + /// This may fail if a similar notification was sent too recently, + /// violating the notification cooldown period defined in [`NOTIFICATION_COOLDOWN_TIME`]. + /// /// # Parameters /// - `client_id`: the client's node-ID whose webhooks should be invoked. /// /// [`WebhookNotificationMethod::LSPS5OnionMessageIncoming`]: super::msgs::WebhookNotificationMethod::LSPS5OnionMessageIncoming + /// [`NOTIFICATION_COOLDOWN_TIME`]: super::service::NOTIFICATION_COOLDOWN_TIME pub fn notify_onion_message_incoming( &self, client_id: PublicKey, ) -> Result<(), LSPS5ProtocolError> { @@ -378,34 +405,40 @@ where fn send_notifications_to_client_webhooks( &self, client_id: PublicKey, notification: WebhookNotification, ) -> Result<(), LSPS5ProtocolError> { - let mut webhooks = self.webhooks.lock().unwrap(); - - let client_webhooks = match webhooks.get_mut(&client_id) { - Some(webhooks) if !webhooks.is_empty() => webhooks, - _ => return Ok(()), + let mut outer_state_lock = self.per_peer_state.write().unwrap(); + let peer_state = if let Some(peer_state) = outer_state_lock.get_mut(&client_id) { + peer_state + } else { + return Ok(()); }; let now = LSPSDateTime::new_from_duration_since_epoch(self.time_provider.duration_since_epoch()); - for (app_name, webhook) in client_webhooks.iter_mut() { - if webhook - .last_notification_sent - .get(¬ification.method) - .map(|last_sent| now.clone().abs_diff(&last_sent)) - .map_or(true, |duration| { - duration >= self.config.notification_cooldown_hours.as_secs() - }) { - webhook.last_notification_sent.insert(notification.method.clone(), now.clone()); - webhook.last_used = now.clone(); - self.send_notification( - client_id, - app_name.clone(), - webhook.url.clone(), - notification.clone(), - )?; + // We must avoid sending multiple notifications of the same method + // (other than lsps5.webhook_registered) close in time. + if notification.method != WebhookNotificationMethod::LSPS5WebhookRegistered { + let rate_limit_applies = peer_state.webhooks().iter().any(|(_, webhook)| { + webhook.last_notification_sent.as_ref().map_or(false, |last_sent| { + now.duration_since(&last_sent) < NOTIFICATION_COOLDOWN_TIME + }) + }); + + if rate_limit_applies { + return Err(LSPS5ProtocolError::SlowDownError); } } + + for (app_name, webhook) in peer_state.webhooks_mut().iter_mut() { + self.send_notification( + client_id, + app_name.clone(), + webhook.url.clone(), + notification.clone(), + )?; + webhook.last_used = now; + webhook.last_notification_sent = Some(now); + } Ok(()) } @@ -454,26 +487,6 @@ where .map_err(|_| LSPS5ProtocolError::UnknownError) } - fn prune_stale_webhooks(&self) { - let now = - LSPSDateTime::new_from_duration_since_epoch(self.time_provider.duration_since_epoch()); - let mut webhooks = self.webhooks.lock().unwrap(); - - webhooks.retain(|client_id, client_webhooks| { - if !self.client_has_open_channel(client_id) { - client_webhooks.retain(|_, webhook| { - now.abs_diff(&webhook.last_used) < MIN_WEBHOOK_RETENTION_DAYS.as_secs() - }); - !client_webhooks.is_empty() - } else { - true - } - }); - - let mut last_pruning = self.last_pruning.lock().unwrap(); - *last_pruning = Some(now); - } - fn client_has_open_channel(&self, client_id: &PublicKey) -> bool { self.channel_manager .get_cm() @@ -481,6 +494,22 @@ where .iter() .any(|c| c.is_usable && c.counterparty.node_id == *client_id) } + + pub(crate) fn peer_connected(&self, counterparty_node_id: &PublicKey) { + let mut outer_state_lock = self.per_peer_state.write().unwrap(); + if let Some(peer_state) = outer_state_lock.get_mut(counterparty_node_id) { + peer_state.reset_notification_cooldown(); + } + self.check_prune_stale_webhooks(&mut outer_state_lock); + } + + pub(crate) fn peer_disconnected(&self, counterparty_node_id: &PublicKey) { + let mut outer_state_lock = self.per_peer_state.write().unwrap(); + if let Some(peer_state) = outer_state_lock.get_mut(counterparty_node_id) { + peer_state.reset_notification_cooldown(); + } + self.check_prune_stale_webhooks(&mut outer_state_lock); + } } impl LSPSProtocolMessageHandler for LSPS5ServiceHandler @@ -524,3 +553,69 @@ where } } } + +#[derive(Debug, Default)] +struct PeerState { + webhooks: Vec<(LSPS5AppName, Webhook)>, +} + +impl PeerState { + fn webhook_mut(&mut self, name: &LSPS5AppName) -> Option<&mut Webhook> { + self.webhooks.iter_mut().find_map(|(n, h)| if n == name { Some(h) } else { None }) + } + + fn webhooks(&self) -> &Vec<(LSPS5AppName, Webhook)> { + &self.webhooks + } + + fn webhooks_mut(&mut self) -> &mut Vec<(LSPS5AppName, Webhook)> { + &mut self.webhooks + } + + fn webhooks_len(&self) -> usize { + self.webhooks.len() + } + + fn app_names(&self) -> Vec { + self.webhooks.iter().map(|(n, _)| n).cloned().collect() + } + + fn insert_webhook(&mut self, name: LSPS5AppName, hook: Webhook) { + for (n, h) in self.webhooks.iter_mut() { + if *n == name { + *h = hook; + return; + } + } + + self.webhooks.push((name, hook)); + } + + fn remove_webhook(&mut self, name: &LSPS5AppName) -> bool { + let mut removed = false; + self.webhooks.retain(|(n, _)| { + if n != name { + true + } else { + removed = true; + false + } + }); + removed + } + + fn reset_notification_cooldown(&mut self) { + for (_, h) in self.webhooks.iter_mut() { + h.last_notification_sent = None; + } + } + + // Returns whether the entire state is empty and can be pruned. + fn prune_stale_webhooks(&mut self, now: LSPSDateTime) -> bool { + self.webhooks.retain(|(_, webhook)| { + now.duration_since(&webhook.last_used) < MIN_WEBHOOK_RETENTION_DAYS + }); + + self.webhooks.is_empty() + } +} diff --git a/lightning-liquidity/src/manager.rs b/lightning-liquidity/src/manager.rs index 6d412bd966a..4cf97786d02 100644 --- a/lightning-liquidity/src/manager.rs +++ b/lightning-liquidity/src/manager.rs @@ -1,3 +1,12 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + use alloc::string::ToString; use alloc::vec::Vec; @@ -181,7 +190,7 @@ pub struct LiquidityManager< lsps2_service_handler: Option>, lsps2_client_handler: Option>, lsps5_service_handler: Option>, - lsps5_client_handler: Option>, + lsps5_client_handler: Option>, service_config: Option, _client_config: Option, best_block: RwLock>, @@ -276,12 +285,11 @@ where let lsps5_client_handler = client_config.as_ref().and_then(|config| { config.lsps5_client_config.as_ref().map(|config| { - LSPS5ClientHandler::new_with_time_provider( + LSPS5ClientHandler::new( entropy_source.clone(), Arc::clone(&pending_messages), Arc::clone(&pending_events), config.clone(), - time_provider.clone(), ) }) }); @@ -411,7 +419,7 @@ where /// Returns a reference to the LSPS5 client-side handler. /// /// The returned hendler allows to initiate the LSPS5 client-side flow. That is, it allows to - pub fn lsps5_client_handler(&self) -> Option<&LSPS5ClientHandler> { + pub fn lsps5_client_handler(&self) -> Option<&LSPS5ClientHandler> { self.lsps5_client_handler.as_ref() } @@ -633,13 +641,15 @@ where LSPSMessage::from_str_with_id_map(&msg.payload, &mut request_id_to_method_map) } .map_err(|_| { + let mut message_queue_notifier = self.pending_messages.notifier(); + let error = LSPSResponseError { code: JSONRPC_INVALID_MESSAGE_ERROR_CODE, message: JSONRPC_INVALID_MESSAGE_ERROR_MESSAGE.to_string(), data: None, }; - self.pending_messages.enqueue(&sender_node_id, LSPSMessage::Invalid(error)); + message_queue_notifier.enqueue(&sender_node_id, LSPSMessage::Invalid(error)); self.ignored_peers.write().unwrap().insert(sender_node_id); let err = format!( "Failed to deserialize invalid LSPS message. Ignoring peer {} from now on.", @@ -711,10 +721,19 @@ where if let Some(lsps2_service_handler) = self.lsps2_service_handler.as_ref() { lsps2_service_handler.peer_disconnected(counterparty_node_id); } + + if let Some(lsps5_service_handler) = self.lsps5_service_handler.as_ref() { + lsps5_service_handler.peer_disconnected(&counterparty_node_id); + } } fn peer_connected( - &self, _: bitcoin::secp256k1::PublicKey, _: &lightning::ln::msgs::Init, _: bool, + &self, counterparty_node_id: bitcoin::secp256k1::PublicKey, _: &lightning::ln::msgs::Init, + _: bool, ) -> Result<(), ()> { + if let Some(lsps5_service_handler) = self.lsps5_service_handler.as_ref() { + lsps5_service_handler.peer_connected(&counterparty_node_id); + } + Ok(()) } } diff --git a/lightning-liquidity/src/message_queue.rs b/lightning-liquidity/src/message_queue.rs index 58060862f07..d097573cf04 100644 --- a/lightning-liquidity/src/message_queue.rs +++ b/lightning-liquidity/src/message_queue.rs @@ -1,3 +1,12 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + //! Holds types and traits used to implement message queues for [`LSPSMessage`]s. use alloc::collections::VecDeque; @@ -33,11 +42,29 @@ impl MessageQueue { self.pending_msgs_notifier.get_future() } - pub(crate) fn enqueue(&self, counterparty_node_id: &PublicKey, msg: LSPSMessage) { - { - let mut queue = self.queue.lock().unwrap(); - queue.push_back((*counterparty_node_id, msg)); + pub(crate) fn notifier(&self) -> MessageQueueNotifierGuard<'_> { + MessageQueueNotifierGuard { msg_queue: self, buffer: VecDeque::new() } + } +} + +// A guard type that will process buffered messages and wake the background processor when dropped. +#[must_use] +pub(crate) struct MessageQueueNotifierGuard<'a> { + msg_queue: &'a MessageQueue, + buffer: VecDeque<(PublicKey, LSPSMessage)>, +} + +impl<'a> MessageQueueNotifierGuard<'a> { + pub fn enqueue(&mut self, counterparty_node_id: &PublicKey, msg: LSPSMessage) { + self.buffer.push_back((*counterparty_node_id, msg)); + } +} + +impl<'a> Drop for MessageQueueNotifierGuard<'a> { + fn drop(&mut self) { + if !self.buffer.is_empty() { + self.msg_queue.queue.lock().unwrap().append(&mut self.buffer); + self.msg_queue.pending_msgs_notifier.notify(); } - self.pending_msgs_notifier.notify(); } } diff --git a/lightning-liquidity/src/utils/mod.rs b/lightning-liquidity/src/utils/mod.rs index 21cd59d2b6d..637c62c0d81 100644 --- a/lightning-liquidity/src/utils/mod.rs +++ b/lightning-liquidity/src/utils/mod.rs @@ -7,6 +7,8 @@ use lightning::sign::EntropySource; use crate::lsps0::ser::LSPSRequestId; +pub mod time; + /// Converts a human-readable string representation of a short channel ID (SCID) pub fn scid_from_human_readable_string(human_readable_scid: &str) -> Result { let mut parts = human_readable_scid.split('x'); @@ -56,5 +58,3 @@ mod tests { assert_eq!(vout_from_scid(scid), vout); } } - -pub mod time; diff --git a/lightning-liquidity/tests/lsps0_integration_tests.rs b/lightning-liquidity/tests/lsps0_integration_tests.rs index 7fd7a7c185f..423d49785f2 100644 --- a/lightning-liquidity/tests/lsps0_integration_tests.rs +++ b/lightning-liquidity/tests/lsps0_integration_tests.rs @@ -23,7 +23,6 @@ use lightning::ln::functional_test_utils::{ use lightning::ln::peer_handler::CustomMessageHandler; use std::sync::Arc; -use std::time::Duration; #[test] fn list_protocols_integration_test() { @@ -36,10 +35,7 @@ fn list_protocols_integration_test() { let lsps2_service_config = LSPS2ServiceConfig { promise_secret }; #[cfg(lsps1_service)] let lsps1_service_config = LSPS1ServiceConfig { supported_options: None, token: None }; - let lsps5_service_config = LSPS5ServiceConfig { - max_webhooks_per_client: 10, - notification_cooldown_hours: Duration::from_secs(3600), - }; + let lsps5_service_config = LSPS5ServiceConfig::default(); let service_config = LiquidityServiceConfig { #[cfg(lsps1_service)] lsps1_service_config: Some(lsps1_service_config), diff --git a/lightning-liquidity/tests/lsps5_integration_tests.rs b/lightning-liquidity/tests/lsps5_integration_tests.rs index aa85cf0a1d8..e526d3eda5e 100644 --- a/lightning-liquidity/tests/lsps5_integration_tests.rs +++ b/lightning-liquidity/tests/lsps5_integration_tests.rs @@ -7,6 +7,7 @@ use common::{create_service_and_client_nodes, get_lsps_message, LSPSNodes}; use lightning::ln::functional_test_utils::{ create_chanmon_cfgs, create_network, create_node_cfgs, create_node_chanmgrs, Node, }; +use lightning::ln::msgs::Init; use lightning::ln::peer_handler::CustomMessageHandler; use lightning::util::hash_tables::{HashMap, HashSet}; use lightning_liquidity::events::LiquidityEvent; @@ -17,7 +18,9 @@ use lightning_liquidity::lsps5::msgs::{ LSPS5AppName, LSPS5ClientError, LSPS5ProtocolError, LSPS5WebhookUrl, WebhookNotification, WebhookNotificationMethod, }; -use lightning_liquidity::lsps5::service::LSPS5ServiceConfig; +use lightning_liquidity::lsps5::service::{ + LSPS5ServiceConfig, DEFAULT_MAX_WEBHOOKS_PER_CLIENT, NOTIFICATION_COOLDOWN_TIME, +}; use lightning_liquidity::lsps5::service::{ MIN_WEBHOOK_RETENTION_DAYS, PRUNE_STALE_WEBHOOKS_INTERVAL_DAYS, }; @@ -27,18 +30,10 @@ use lightning_liquidity::{LiquidityClientConfig, LiquidityServiceConfig}; use std::sync::{Arc, RwLock}; use std::time::Duration; -/// Default maximum number of webhooks allowed per client. -pub(crate) const DEFAULT_MAX_WEBHOOKS_PER_CLIENT: u32 = 10; -/// Default notification cooldown time in hours. -pub(crate) const DEFAULT_NOTIFICATION_COOLDOWN_HOURS: Duration = Duration::from_secs(24 * 60 * 60); - pub(crate) fn lsps5_test_setup<'a, 'b, 'c>( nodes: Vec>, time_provider: Arc, ) -> (LSPSNodes<'a, 'b, 'c>, LSPS5Validator) { - let lsps5_service_config = LSPS5ServiceConfig { - max_webhooks_per_client: DEFAULT_MAX_WEBHOOKS_PER_CLIENT, - notification_cooldown_hours: DEFAULT_NOTIFICATION_COOLDOWN_HOURS, - }; + let lsps5_service_config = LSPS5ServiceConfig::default(); let service_config = LiquidityServiceConfig { #[cfg(lsps1_service)] lsps1_service_config: None, @@ -416,11 +411,13 @@ fn webhook_error_handling_test() { #[test] fn webhook_notification_delivery_test() { + let mock_time_provider = Arc::new(MockTimeProvider::new(1000)); + let time_provider = Arc::::clone(&mock_time_provider); let chanmon_cfgs = create_chanmon_cfgs(2); let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); let nodes = create_network(2, &node_cfgs, &node_chanmgrs); - let (lsps_nodes, validator) = lsps5_test_setup(nodes, Arc::new(DefaultTimeProvider)); + let (lsps_nodes, validator) = lsps5_test_setup(nodes, time_provider); let LSPSNodes { service_node, client_node } = lsps_nodes; let service_node_id = service_node.inner.node.get_our_node_id(); let client_node_id = client_node.inner.node.get_our_node_id(); @@ -504,6 +501,8 @@ fn webhook_notification_delivery_test() { "No event should be emitted due to cooldown" ); + mock_time_provider.advance_time(NOTIFICATION_COOLDOWN_TIME.as_secs() + 1); + let timeout_block = 700000; // Some future block height let _ = service_handler.notify_expiry_soon(client_node_id, timeout_block); @@ -724,11 +723,13 @@ fn idempotency_set_webhook_test() { #[test] fn replay_prevention_test() { + let mock_time_provider = Arc::new(MockTimeProvider::new(1000)); + let time_provider = Arc::::clone(&mock_time_provider); let chanmon_cfgs = create_chanmon_cfgs(2); let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); let nodes = create_network(2, &node_cfgs, &node_chanmgrs); - let (lsps_nodes, validator) = lsps5_test_setup(nodes, Arc::new(DefaultTimeProvider)); + let (lsps_nodes, validator) = lsps5_test_setup(nodes, time_provider); let LSPSNodes { service_node, client_node } = lsps_nodes; let service_node_id = service_node.inner.node.get_our_node_id(); let client_node_id = client_node.inner.node.get_our_node_id(); @@ -779,6 +780,9 @@ fn replay_prevention_test() { // Fill up the validator's signature cache to push out the original signature. for i in 0..MAX_RECENT_SIGNATURES { + // Advance time, allowing for another notification + mock_time_provider.advance_time(NOTIFICATION_COOLDOWN_TIME.as_secs() + 1); + let timeout_block = 700000 + i as u32; let _ = service_handler.notify_expiry_soon(client_node_id, timeout_block); let event = service_node.liquidity_manager.next_event().unwrap(); @@ -857,7 +861,15 @@ fn stale_webhooks() { MIN_WEBHOOK_RETENTION_DAYS.as_secs() + PRUNE_STALE_WEBHOOKS_INTERVAL_DAYS.as_secs(), ); - // LIST calls prune before executing -> should be empty after advancing time + // LIST should be empty after advancing time and reconnection + service_node.liquidity_manager.peer_disconnected(client_node_id); + let init_msg = Init { + features: lightning_types::features::InitFeatures::empty(), + remote_network_address: None, + networks: None, + }; + service_node.liquidity_manager.peer_connected(client_node_id, &init_msg, false).unwrap(); + let _ = client_handler.list_webhooks(service_node_id); let list_req2 = get_lsps_message!(client_node, service_node_id); service_node.liquidity_manager.handle_custom_message(list_req2, client_node_id).unwrap(); @@ -876,11 +888,13 @@ fn stale_webhooks() { #[test] fn test_all_notifications() { + let mock_time_provider = Arc::new(MockTimeProvider::new(1000)); + let time_provider = Arc::::clone(&mock_time_provider); let chanmon_cfgs = create_chanmon_cfgs(2); let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); let nodes = create_network(2, &node_cfgs, &node_chanmgrs); - let (lsps_nodes, validator) = lsps5_test_setup(nodes, Arc::new(DefaultTimeProvider)); + let (lsps_nodes, validator) = lsps5_test_setup(nodes, time_provider); let LSPSNodes { service_node, client_node } = lsps_nodes; let service_node_id = service_node.inner.node.get_our_node_id(); let client_node_id = client_node.inner.node.get_our_node_id(); @@ -899,9 +913,16 @@ fn test_all_notifications() { // consume initial SendWebhookNotification let _ = service_node.liquidity_manager.next_event().unwrap(); + mock_time_provider.advance_time(NOTIFICATION_COOLDOWN_TIME.as_secs() + 1); let _ = service_handler.notify_onion_message_incoming(client_node_id); + + mock_time_provider.advance_time(NOTIFICATION_COOLDOWN_TIME.as_secs() + 1); let _ = service_handler.notify_payment_incoming(client_node_id); + + mock_time_provider.advance_time(NOTIFICATION_COOLDOWN_TIME.as_secs() + 1); let _ = service_handler.notify_expiry_soon(client_node_id, 1000); + + mock_time_provider.advance_time(NOTIFICATION_COOLDOWN_TIME.as_secs() + 1); let _ = service_handler.notify_liquidity_management_request(client_node_id); let expected_notifications = vec![ @@ -1053,3 +1074,163 @@ fn test_notify_without_webhooks_does_nothing() { let _ = service_handler.notify_onion_message_incoming(client_node_id); assert!(service_node.liquidity_manager.next_event().is_none()); } + +#[test] +fn test_notifications_and_peer_connected_resets_cooldown() { + let mock_time_provider = Arc::new(MockTimeProvider::new(1000)); + let time_provider = Arc::::clone(&mock_time_provider); + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + let (lsps_nodes, _) = lsps5_test_setup(nodes, time_provider); + let LSPSNodes { service_node, client_node } = lsps_nodes; + let service_node_id = service_node.inner.node.get_our_node_id(); + let client_node_id = client_node.inner.node.get_our_node_id(); + + let client_handler = client_node.liquidity_manager.lsps5_client_handler().unwrap(); + let service_handler = service_node.liquidity_manager.lsps5_service_handler().unwrap(); + + let app_name = "CooldownTestApp"; + let webhook_url = "https://www.example.org/cooldown"; + let _ = client_handler + .set_webhook(service_node_id, app_name.to_string(), webhook_url.to_string()) + .expect("Register webhook request should succeed"); + let set_req = get_lsps_message!(client_node, service_node_id); + service_node.liquidity_manager.handle_custom_message(set_req, client_node_id).unwrap(); + + let _ = service_node.liquidity_manager.next_event().unwrap(); + + let resp = get_lsps_message!(service_node, client_node_id); + client_node.liquidity_manager.handle_custom_message(resp, service_node_id).unwrap(); + let _ = client_node.liquidity_manager.next_event().unwrap(); + + // 1. First notification should be sent + let _ = service_handler.notify_payment_incoming(client_node_id); + let event = service_node.liquidity_manager.next_event().unwrap(); + match event { + LiquidityEvent::LSPS5Service(LSPS5ServiceEvent::SendWebhookNotification { + notification, + .. + }) => { + assert_eq!(notification.method, WebhookNotificationMethod::LSPS5PaymentIncoming); + }, + _ => panic!("Expected SendWebhookNotification event"), + } + + // 2. Second notification before cooldown should NOT be sent + let result = service_handler.notify_payment_incoming(client_node_id); + let error = result.unwrap_err(); + assert_eq!(error, LSPS5ProtocolError::SlowDownError); + assert!( + service_node.liquidity_manager.next_event().is_none(), + "Should not emit event due to cooldown" + ); + + // 3. Advance time past cooldown and ensure payment_incoming can be sent again + mock_time_provider.advance_time(NOTIFICATION_COOLDOWN_TIME.as_secs() + 1); + + let _ = service_handler.notify_payment_incoming(client_node_id); + let event = service_node.liquidity_manager.next_event().unwrap(); + match event { + LiquidityEvent::LSPS5Service(LSPS5ServiceEvent::SendWebhookNotification { + notification, + .. + }) => { + assert_eq!(notification.method, WebhookNotificationMethod::LSPS5PaymentIncoming); + }, + _ => panic!("Expected SendWebhookNotification event after cooldown"), + } + + // 4. Can't send payment_incoming notification again immediately after cooldown + let result = service_handler.notify_payment_incoming(client_node_id); + + let error = result.unwrap_err(); + assert_eq!(error, LSPS5ProtocolError::SlowDownError); + + assert!( + service_node.liquidity_manager.next_event().is_none(), + "Should not emit event due to cooldown" + ); + + // 5. After peer_connected, notification should be sent again immediately + let init_msg = Init { + features: lightning_types::features::InitFeatures::empty(), + remote_network_address: None, + networks: None, + }; + service_node.liquidity_manager.peer_connected(client_node_id, &init_msg, false).unwrap(); + let _ = service_handler.notify_payment_incoming(client_node_id); + let event = service_node.liquidity_manager.next_event().unwrap(); + match event { + LiquidityEvent::LSPS5Service(LSPS5ServiceEvent::SendWebhookNotification { + notification, + .. + }) => { + assert_eq!(notification.method, WebhookNotificationMethod::LSPS5PaymentIncoming); + }, + _ => panic!("Expected SendWebhookNotification event after peer_connected"), + } +} + +#[test] +fn webhook_update_affects_future_notifications() { + let mock_time_provider = Arc::new(MockTimeProvider::new(1000)); + let time_provider = Arc::::clone(&mock_time_provider); + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + let (lsps_nodes, _) = lsps5_test_setup(nodes, time_provider); + let LSPSNodes { service_node, client_node } = lsps_nodes; + let service_node_id = service_node.inner.node.get_our_node_id(); + let client_node_id = client_node.inner.node.get_our_node_id(); + let client_handler = client_node.liquidity_manager.lsps5_client_handler().unwrap(); + let service_handler = service_node.liquidity_manager.lsps5_service_handler().unwrap(); + + let app = "UpdateTestApp".to_string(); + let url_v1 = "https://example.org/v1".to_string(); + let url_v2 = "https://example.org/v2".to_string(); + + // register v1 + client_handler.set_webhook(service_node_id, app.clone(), url_v1).unwrap(); + let req = get_lsps_message!(client_node, service_node_id); + service_node.liquidity_manager.handle_custom_message(req, client_node_id).unwrap(); + let _ = service_node.liquidity_manager.next_event().unwrap(); // initial webhook_registered + let resp = get_lsps_message!(service_node, client_node_id); + client_node.liquidity_manager.handle_custom_message(resp, service_node_id).unwrap(); + let _ = client_node.liquidity_manager.next_event().unwrap(); + + // update to v2 + client_handler.set_webhook(service_node_id, app, url_v2.clone()).unwrap(); + let upd_req = get_lsps_message!(client_node, service_node_id); + service_node.liquidity_manager.handle_custom_message(upd_req, client_node_id).unwrap(); + let update_event = service_node.liquidity_manager.next_event().unwrap(); + match update_event { + LiquidityEvent::LSPS5Service(LSPS5ServiceEvent::SendWebhookNotification { + url, .. + }) => { + assert_eq!(url.as_str(), url_v2); + }, + _ => panic!("Expected webhook_registered for update"), + } + let upd_resp = get_lsps_message!(service_node, client_node_id); + client_node.liquidity_manager.handle_custom_message(upd_resp, service_node_id).unwrap(); + let _ = client_node.liquidity_manager.next_event().unwrap(); + + // Advance past cooldown and send a notification again + mock_time_provider.advance_time(NOTIFICATION_COOLDOWN_TIME.as_secs() + 1); + service_handler.notify_payment_incoming(client_node_id).unwrap(); + let ev = service_node.liquidity_manager.next_event().unwrap(); + match ev { + LiquidityEvent::LSPS5Service(LSPS5ServiceEvent::SendWebhookNotification { + url, + notification, + .. + }) => { + assert_eq!(notification.method, WebhookNotificationMethod::LSPS5PaymentIncoming); + assert_eq!(url.as_str(), url_v2, "Should target updated URL"); + }, + _ => panic!("Expected SendWebhookNotification after update"), + } +} diff --git a/lightning-transaction-sync/Cargo.toml b/lightning-transaction-sync/Cargo.toml index 3b92bbef76e..48100144d01 100644 --- a/lightning-transaction-sync/Cargo.toml +++ b/lightning-transaction-sync/Cargo.toml @@ -36,15 +36,15 @@ lightning-macros = { version = "0.2", path = "../lightning-macros", default-feat bitcoin = { version = "0.32.2", default-features = false } futures = { version = "0.3", optional = true } esplora-client = { version = "0.12", default-features = false, optional = true } -electrum-client = { version = "0.23.1", optional = true, default-features = false, features = ["proxy"] } +electrum-client = { version = "0.24.0", optional = true, default-features = false, features = ["proxy"] } [dev-dependencies] lightning = { version = "0.2.0", path = "../lightning", default-features = false, features = ["std", "_test_utils"] } tokio = { version = "1.35.0", features = ["macros"] } [target.'cfg(not(target_os = "windows"))'.dev-dependencies] -electrsd = { version = "0.34.0", default-features = false, features = ["legacy"] } -corepc-node = { version = "0.7.0", default-features = false, features = ["28_0"] } +electrsd = { version = "0.35.0", default-features = false, features = ["legacy"] } +corepc-node = { version = "0.8.0", default-features = false, features = ["28_0"] } [lints.rust.unexpected_cfgs] level = "forbid" diff --git a/lightning/src/blinded_path/message.rs b/lightning/src/blinded_path/message.rs index 7db5dc00b05..954247d936d 100644 --- a/lightning/src/blinded_path/message.rs +++ b/lightning/src/blinded_path/message.rs @@ -353,7 +353,7 @@ pub enum OffersContext { StaticInvoiceRequested { /// An identifier for the async recipient for whom we as a static invoice server are serving /// [`StaticInvoice`]s. Used paired with the - /// [`OffersContext::StaticInvoiceRequested::invoice_id`] when looking up a corresponding + /// [`OffersContext::StaticInvoiceRequested::invoice_slot`] when looking up a corresponding /// [`StaticInvoice`] to return to the payer if the recipient is offline. This id was previously /// provided via [`AsyncPaymentsContext::ServeStaticInvoice::recipient_id`]. /// @@ -364,15 +364,15 @@ pub enum OffersContext { /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest recipient_id: Vec, - /// A random unique identifier for a specific [`StaticInvoice`] that the recipient previously + /// The slot number for a specific [`StaticInvoice`] that the recipient previously /// requested be served on their behalf. Useful when paired with the /// [`OffersContext::StaticInvoiceRequested::recipient_id`] to pull that specific invoice from /// the database when payers send an [`InvoiceRequest`]. This id was previously - /// provided via [`AsyncPaymentsContext::ServeStaticInvoice::invoice_id`]. + /// provided via [`AsyncPaymentsContext::ServeStaticInvoice::invoice_slot`]. /// /// [`StaticInvoice`]: crate::offers::static_invoice::StaticInvoice /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest - invoice_id: u128, + invoice_slot: u16, /// The time as duration since the Unix epoch at which this path expires and messages sent over /// it should be ignored. @@ -448,6 +448,14 @@ pub enum AsyncPaymentsContext { /// [`OfferPathsRequest`]: crate::onion_message::async_payments::OfferPathsRequest /// [`OfferPaths`]: crate::onion_message::async_payments::OfferPaths OfferPaths { + /// The "slot" in the static invoice server's database that the invoice corresponding to these + /// offer paths should go into, originally set by us in [`OfferPathsRequest::invoice_slot`]. This + /// value allows us as the recipient to replace a specific invoice that is stored by the server, + /// which is useful for limiting the number of invoices stored by the server while also keeping + /// all the invoices persisted with the server fresh. + /// + /// [`OfferPathsRequest::invoice_slot`]: crate::onion_message::async_payments::OfferPathsRequest::invoice_slot + invoice_slot: u16, /// The time as duration since the Unix epoch at which this path expires and messages sent over /// it should be ignored. /// @@ -466,7 +474,7 @@ pub enum AsyncPaymentsContext { /// An identifier for the async recipient that is requesting that a [`StaticInvoice`] be served /// on their behalf. /// - /// Useful when surfaced alongside the below `invoice_id` when payers send an + /// Useful when surfaced alongside the below `invoice_slot` when payers send an /// [`InvoiceRequest`], to pull the specific static invoice from the database. /// /// Also useful to rate limit the invoices being persisted on behalf of a particular recipient. @@ -477,15 +485,15 @@ pub enum AsyncPaymentsContext { /// [`StaticInvoice`]: crate::offers::static_invoice::StaticInvoice /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest recipient_id: Vec, - /// A random identifier for the specific [`StaticInvoice`] that the recipient is requesting be + /// The slot number for the specific [`StaticInvoice`] that the recipient is requesting be /// served on their behalf. Useful when surfaced alongside the above `recipient_id` when payers /// send an [`InvoiceRequest`], to pull the specific static invoice from the database. This id /// will be provided back to us as the static invoice server via - /// [`OffersContext::StaticInvoiceRequested::invoice_id`]. + /// [`OffersContext::StaticInvoiceRequested::invoice_slot`]. /// /// [`StaticInvoice`]: crate::offers::static_invoice::StaticInvoice /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest - invoice_id: u128, + invoice_slot: u16, /// The time as duration since the Unix epoch at which this path expires and messages sent over /// it should be ignored. /// @@ -506,10 +514,9 @@ pub enum AsyncPaymentsContext { /// [`StaticInvoice`]: crate::offers::static_invoice::StaticInvoice /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest offer_id: OfferId, - /// The time as duration since the Unix epoch at which this path expires and messages sent over - /// it should be ignored. If we receive confirmation of an invoice over this path after its - /// expiry, it may be outdated and a new invoice update should be sent instead. - path_absolute_expiry: core::time::Duration, + /// The time as duration since the Unix epoch at which the invoice corresponding to this path + /// was created. Useful to know when an invoice needs replacement. + invoice_created_at: core::time::Duration, }, /// Context contained within the reply [`BlindedMessagePath`] we put in outbound /// [`HeldHtlcAvailable`] messages, provided back to us in corresponding [`ReleaseHeldHtlc`] @@ -560,7 +567,7 @@ impl_writeable_tlv_based_enum!(OffersContext, }, (3, StaticInvoiceRequested) => { (0, recipient_id, required), - (2, invoice_id, required), + (2, invoice_slot, required), (4, path_absolute_expiry, required), }, ); @@ -574,10 +581,11 @@ impl_writeable_tlv_based_enum!(AsyncPaymentsContext, }, (2, OfferPaths) => { (0, path_absolute_expiry, required), + (2, invoice_slot, required), }, (3, StaticInvoicePersisted) => { (0, offer_id, required), - (2, path_absolute_expiry, required), + (2, invoice_created_at, required), }, (4, OfferPathsRequest) => { (0, recipient_id, required), @@ -585,7 +593,7 @@ impl_writeable_tlv_based_enum!(AsyncPaymentsContext, }, (5, ServeStaticInvoice) => { (0, recipient_id, required), - (2, invoice_id, required), + (2, invoice_slot, required), (4, path_absolute_expiry, required), }, ); diff --git a/lightning/src/chain/chainmonitor.rs b/lightning/src/chain/chainmonitor.rs index 0de372813b3..386ef0a60c5 100644 --- a/lightning/src/chain/chainmonitor.rs +++ b/lightning/src/chain/chainmonitor.rs @@ -28,6 +28,8 @@ use bitcoin::hash_types::{BlockHash, Txid}; use crate::chain; use crate::chain::chaininterface::{BroadcasterInterface, FeeEstimator}; +#[cfg(peer_storage)] +use crate::chain::channelmonitor::write_chanmon_internal; use crate::chain::channelmonitor::{ Balance, ChannelMonitor, ChannelMonitorUpdate, MonitorEvent, TransactionOutputs, WithChannelMonitor, @@ -36,8 +38,11 @@ use crate::chain::transaction::{OutPoint, TransactionData}; use crate::chain::{ChannelMonitorUpdateStatus, Filter, WatchedOutput}; use crate::events::{self, Event, EventHandler, ReplayEvent}; use crate::ln::channel_state::ChannelDetails; -use crate::ln::msgs::{self, BaseMessageHandler, Init, MessageSendEvent, SendOnlyMessageHandler}; -use crate::ln::our_peer_storage::DecryptedOurPeerStorage; +#[cfg(peer_storage)] +use crate::ln::msgs::PeerStorage; +use crate::ln::msgs::{BaseMessageHandler, Init, MessageSendEvent, SendOnlyMessageHandler}; +#[cfg(peer_storage)] +use crate::ln::our_peer_storage::{DecryptedOurPeerStorage, PeerStorageMonitorHolder}; use crate::ln::types::ChannelId; use crate::prelude::*; use crate::sign::ecdsa::EcdsaChannelSigner; @@ -47,8 +52,12 @@ use crate::types::features::{InitFeatures, NodeFeatures}; use crate::util::errors::APIError; use crate::util::logger::{Logger, WithContext}; use crate::util::persist::MonitorName; +#[cfg(peer_storage)] +use crate::util::ser::{VecWriter, Writeable}; use crate::util::wakers::{Future, Notifier}; use bitcoin::secp256k1::PublicKey; +#[cfg(peer_storage)] +use core::iter::Cycle; use core::ops::Deref; use core::sync::atomic::{AtomicUsize, Ordering}; @@ -264,7 +273,7 @@ pub struct ChainMonitor< logger: L, fee_estimator: F, persister: P, - entropy_source: ES, + _entropy_source: ES, /// "User-provided" (ie persistence-completion/-failed) [`MonitorEvent`]s. These came directly /// from the user and not from a [`ChannelMonitor`]. pending_monitor_events: Mutex, PublicKey)>>, @@ -278,6 +287,7 @@ pub struct ChainMonitor< /// Messages to send to the peer. This is currently used to distribute PeerStorage to channel partners. pending_send_only_events: Mutex>, + #[cfg(peer_storage)] our_peerstorage_encryption_key: PeerStorageKey, } @@ -477,7 +487,7 @@ where /// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager pub fn new( chain_source: Option, broadcaster: T, logger: L, feeest: F, persister: P, - entropy_source: ES, our_peerstorage_encryption_key: PeerStorageKey, + _entropy_source: ES, _our_peerstorage_encryption_key: PeerStorageKey, ) -> Self { Self { monitors: RwLock::new(new_hash_map()), @@ -486,12 +496,13 @@ where logger, fee_estimator: feeest, persister, - entropy_source, + _entropy_source, pending_monitor_events: Mutex::new(Vec::new()), highest_chain_height: AtomicUsize::new(0), event_notifier: Notifier::new(), pending_send_only_events: Mutex::new(Vec::new()), - our_peerstorage_encryption_key, + #[cfg(peer_storage)] + our_peerstorage_encryption_key: _our_peerstorage_encryption_key, } } @@ -804,23 +815,90 @@ where /// This function collects the counterparty node IDs from all monitors into a `HashSet`, /// ensuring unique IDs are returned. + #[cfg(peer_storage)] fn all_counterparty_node_ids(&self) -> HashSet { let mon = self.monitors.read().unwrap(); mon.values().map(|monitor| monitor.monitor.get_counterparty_node_id()).collect() } + #[cfg(peer_storage)] fn send_peer_storage(&self, their_node_id: PublicKey) { - // TODO: Serialize `ChannelMonitor`s inside `our_peer_storage`. + let mut monitors_list: Vec = Vec::new(); + let random_bytes = self._entropy_source.get_secure_random_bytes(); + + const MAX_PEER_STORAGE_SIZE: usize = 65531; + const USIZE_LEN: usize = core::mem::size_of::(); + let mut random_bytes_cycle_iter = random_bytes.iter().cycle(); + + let mut current_size = 0; + let monitors_lock = self.monitors.read().unwrap(); + let mut channel_ids = monitors_lock.keys().copied().collect(); + + fn next_random_id( + channel_ids: &mut Vec, + random_bytes_cycle_iter: &mut Cycle>, + ) -> Option { + if channel_ids.is_empty() { + return None; + } + let random_idx = { + let mut usize_bytes = [0u8; USIZE_LEN]; + usize_bytes.iter_mut().for_each(|b| { + *b = *random_bytes_cycle_iter.next().expect("A cycle never ends") + }); + // Take one more to introduce a slight misalignment. + random_bytes_cycle_iter.next().expect("A cycle never ends"); + usize::from_le_bytes(usize_bytes) % channel_ids.len() + }; + Some(channel_ids.swap_remove(random_idx)) + } + + while let Some(channel_id) = next_random_id(&mut channel_ids, &mut random_bytes_cycle_iter) + { + let monitor_holder = if let Some(monitor_holder) = monitors_lock.get(&channel_id) { + monitor_holder + } else { + debug_assert!( + false, + "Tried to access non-existing monitor, this should never happen" + ); + break; + }; - let random_bytes = self.entropy_source.get_secure_random_bytes(); - let serialised_channels = Vec::new(); + let mut serialized_channel = VecWriter(Vec::new()); + let min_seen_secret = monitor_holder.monitor.get_min_seen_secret(); + let counterparty_node_id = monitor_holder.monitor.get_counterparty_node_id(); + { + let inner_lock = monitor_holder.monitor.inner.lock().unwrap(); + + write_chanmon_internal(&inner_lock, true, &mut serialized_channel) + .expect("can not write Channel Monitor for peer storage message"); + } + let peer_storage_monitor = PeerStorageMonitorHolder { + channel_id, + min_seen_secret, + counterparty_node_id, + monitor_bytes: serialized_channel.0, + }; + + let serialized_length = peer_storage_monitor.serialized_length(); + + if current_size + serialized_length > MAX_PEER_STORAGE_SIZE { + continue; + } else { + current_size += serialized_length; + monitors_list.push(peer_storage_monitor); + } + } + + let serialised_channels = monitors_list.encode(); let our_peer_storage = DecryptedOurPeerStorage::new(serialised_channels); let cipher = our_peer_storage.encrypt(&self.our_peerstorage_encryption_key, &random_bytes); log_debug!(self.logger, "Sending Peer Storage to {}", log_pubkey!(their_node_id)); let send_peer_storage_event = MessageSendEvent::SendPeerStorage { node_id: their_node_id, - msg: msgs::PeerStorage { data: cipher.into_vec() }, + msg: PeerStorage { data: cipher.into_vec() }, }; self.pending_send_only_events.lock().unwrap().push(send_peer_storage_event) @@ -920,6 +998,7 @@ where ) }); + #[cfg(peer_storage)] // Send peer storage everytime a new block arrives. for node_id in self.all_counterparty_node_ids() { self.send_peer_storage(node_id); @@ -1021,6 +1100,7 @@ where ) }); + #[cfg(peer_storage)] // Send peer storage everytime a new block arrives. for node_id in self.all_counterparty_node_ids() { self.send_peer_storage(node_id); @@ -1490,8 +1570,8 @@ mod tests { assert_eq!(close_tx.len(), 1); mine_transaction(&nodes[2], &close_tx[0]); - check_added_monitors(&nodes[2], 1); check_closed_broadcast(&nodes[2], 1, true); + check_added_monitors(&nodes[2], 1); let closure_reason = ClosureReason::CommitmentTxConfirmed; check_closed_event!(&nodes[2], 1, closure_reason, false, [node_a_id], 1000000); diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index ddb1e31f645..d46cfaa636c 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -205,6 +205,10 @@ pub enum MonitorEvent { /// channel. HolderForceClosed(OutPoint), + /// Indicates that we've detected a commitment transaction (either holder's or counterparty's) + /// be included in a block and should consider the channel closed. + CommitmentTxConfirmed(()), + /// Indicates a [`ChannelMonitor`] update has completed. See /// [`ChannelMonitorUpdateStatus::InProgress`] for more information on how this is used. /// @@ -236,6 +240,7 @@ impl_writeable_tlv_based_enum_upgradable_legacy!(MonitorEvent, (4, channel_id, required), }, ; + (1, CommitmentTxConfirmed), (2, HTLCEvent), (4, HolderForceClosed), // 6 was `UpdateFailed` until LDK 0.0.117 @@ -565,6 +570,13 @@ enum OnchainEvent { /// output (and generate a SpendableOutput event). on_to_local_output_csv: Option, }, + /// An alternative funding transaction (due to a splice/RBF) has confirmed but can no longer be + /// locked now as the monitor is no longer allowing updates. Note that we wait to promote the + /// corresponding `FundingScope` until we see a + /// [`ChannelMonitorUpdateStep::RenegotiatedFundingLocked`], but this event is only applicable + /// once [`ChannelMonitor::no_further_updates_allowed`] returns true. We promote the + /// `FundingScope` once the funding transaction is irrevocably confirmed. + AlternativeFundingConfirmation {}, } impl Writeable for OnchainEventEntry { @@ -609,6 +621,7 @@ impl_writeable_tlv_based_enum_upgradable!(OnchainEvent, (1, MaturingOutput) => { (0, descriptor, required), }, + (2, AlternativeFundingConfirmation) => {}, (3, FundingSpendConfirmation) => { (0, on_local_output_csv, option), (1, commitment_tx_to_counterparty_output, option), @@ -618,7 +631,6 @@ impl_writeable_tlv_based_enum_upgradable!(OnchainEvent, (2, preimage, option), (4, on_to_local_output_csv, option), }, - ); #[derive(Clone, Debug, PartialEq, Eq)] @@ -1099,6 +1111,10 @@ impl FundingScope { fn is_splice(&self) -> bool { self.channel_parameters.splice_parent_funding_txid.is_some() } + + fn channel_type_features(&self) -> &ChannelTypeFeatures { + &self.channel_parameters.channel_type_features + } } impl_writeable_tlv_based!(FundingScope, { @@ -1280,6 +1296,32 @@ pub(crate) struct ChannelMonitorImpl { // commitment transactions, their ordering with respect to each other must remain the same. current_holder_htlc_data: CommitmentHTLCData, prev_holder_htlc_data: Option, + + // Upon confirmation, tracks the txid and confirmation height of a renegotiated funding + // transaction found in `Self::pending_funding`. Used to determine which commitment we should + // broadcast when necessary. + // + // "Alternative" in this context means a `FundingScope` other than the currently locked one + // found at `Self::funding`. We don't use the term "renegotiated", as the currently locked + // `FundingScope` could be one that was renegotiated. + alternative_funding_confirmed: Option<(Txid, u32)>, +} + +// Returns a `&FundingScope` for the one we are currently observing/handling commitment transactions +// for on the chain. +macro_rules! get_confirmed_funding_scope { + ($self: expr) => { + $self + .alternative_funding_confirmed + .map(|(alternative_funding_txid, _)| { + $self + .pending_funding + .iter() + .find(|funding| funding.funding_txid() == alternative_funding_txid) + .expect("FundingScope for confirmed alternative funding must exist") + }) + .unwrap_or(&$self.funding) + }; } // Macro helper to access holder commitment HTLC data (including both non-dust and dust) while @@ -1288,24 +1330,28 @@ pub(crate) struct ChannelMonitorImpl { // a function) over `self`. #[rustfmt::skip] macro_rules! holder_commitment_htlcs { - ($self: expr, CURRENT) => { - $self.funding.current_holder_commitment_tx.nondust_htlcs().iter() + ($self: expr, CURRENT) => {{ + let funding = get_confirmed_funding_scope!($self); + funding.current_holder_commitment_tx.nondust_htlcs().iter() .chain($self.current_holder_htlc_data.dust_htlcs.iter().map(|(htlc, _)| htlc)) - }; + }}; ($self: expr, CURRENT_WITH_SOURCES) => {{ + let funding = get_confirmed_funding_scope!($self); holder_commitment_htlcs!( - &$self.funding.current_holder_commitment_tx, &$self.current_holder_htlc_data + &funding.current_holder_commitment_tx, &$self.current_holder_htlc_data ) }}; ($self: expr, PREV) => {{ - $self.funding.prev_holder_commitment_tx.as_ref().map(|tx| { + let funding = get_confirmed_funding_scope!($self); + funding.prev_holder_commitment_tx.as_ref().map(|tx| { let dust_htlcs = $self.prev_holder_htlc_data.as_ref().unwrap().dust_htlcs.iter() .map(|(htlc, _)| htlc); tx.nondust_htlcs().iter().chain(dust_htlcs) }) }}; ($self: expr, PREV_WITH_SOURCES) => {{ - $self.funding.prev_holder_commitment_tx.as_ref().map(|tx| { + let funding = get_confirmed_funding_scope!($self); + funding.prev_holder_commitment_tx.as_ref().map(|tx| { holder_commitment_htlcs!(tx, $self.prev_holder_htlc_data.as_ref().unwrap()) }) }}; @@ -1356,208 +1402,255 @@ impl Writeable for ChannelMonitor { const SERIALIZATION_VERSION: u8 = 1; const MIN_SERIALIZATION_VERSION: u8 = 1; -impl Writeable for ChannelMonitorImpl { - #[rustfmt::skip] - fn write(&self, writer: &mut W) -> Result<(), Error> { - write_ver_prefix!(writer, SERIALIZATION_VERSION, MIN_SERIALIZATION_VERSION); - - self.latest_update_id.write(writer)?; - - // Set in initial Channel-object creation, so should always be set by now: - U48(self.commitment_transaction_number_obscure_factor).write(writer)?; - - self.destination_script.write(writer)?; - if let Some(ref broadcasted_holder_revokable_script) = self.broadcasted_holder_revokable_script { - writer.write_all(&[0; 1])?; - broadcasted_holder_revokable_script.0.write(writer)?; - broadcasted_holder_revokable_script.1.write(writer)?; - broadcasted_holder_revokable_script.2.write(writer)?; - } else { - writer.write_all(&[1; 1])?; - } +/// Utility function for writing [`ChannelMonitor`] to prevent code duplication in [`ChainMonitor`] while sending Peer Storage. +/// +/// NOTE: `is_stub` is true only when we are using this to serialise for Peer Storage. +/// +/// TODO: Determine which fields of each `ChannelMonitor` should be included in Peer Storage, and which should be omitted. +/// +/// [`ChainMonitor`]: crate::chain::chainmonitor::ChainMonitor +pub(crate) fn write_chanmon_internal( + channel_monitor: &ChannelMonitorImpl, _is_stub: bool, writer: &mut W, +) -> Result<(), Error> { + write_ver_prefix!(writer, SERIALIZATION_VERSION, MIN_SERIALIZATION_VERSION); - self.counterparty_payment_script.write(writer)?; - match &self.shutdown_script { - Some(script) => script.write(writer)?, - None => ScriptBuf::new().write(writer)?, - } + channel_monitor.latest_update_id.write(writer)?; - self.channel_keys_id.write(writer)?; - self.holder_revocation_basepoint.write(writer)?; - let funding_outpoint = self.get_funding_txo(); - writer.write_all(&funding_outpoint.txid[..])?; - writer.write_all(&funding_outpoint.index.to_be_bytes())?; - let redeem_script = self.funding.channel_parameters.make_funding_redeemscript(); - let script_pubkey = redeem_script.to_p2wsh(); - script_pubkey.write(writer)?; - self.funding.current_counterparty_commitment_txid.write(writer)?; - self.funding.prev_counterparty_commitment_txid.write(writer)?; - - self.counterparty_commitment_params.write(writer)?; - redeem_script.write(writer)?; - self.funding.channel_parameters.channel_value_satoshis.write(writer)?; + // Set in initial Channel-object creation, so should always be set by now: + U48(channel_monitor.commitment_transaction_number_obscure_factor).write(writer)?; - match self.their_cur_per_commitment_points { - Some((idx, pubkey, second_option)) => { - writer.write_all(&byte_utils::be48_to_array(idx))?; - writer.write_all(&pubkey.serialize())?; - match second_option { - Some(second_pubkey) => { - writer.write_all(&second_pubkey.serialize())?; - }, - None => { - writer.write_all(&[0; 33])?; - }, - } - }, - None => { - writer.write_all(&byte_utils::be48_to_array(0))?; - }, - } - - writer.write_all(&self.on_holder_tx_csv.to_be_bytes())?; + channel_monitor.destination_script.write(writer)?; + if let Some(ref broadcasted_holder_revokable_script) = + channel_monitor.broadcasted_holder_revokable_script + { + writer.write_all(&[0; 1])?; + broadcasted_holder_revokable_script.0.write(writer)?; + broadcasted_holder_revokable_script.1.write(writer)?; + broadcasted_holder_revokable_script.2.write(writer)?; + } else { + writer.write_all(&[1; 1])?; + } + + channel_monitor.counterparty_payment_script.write(writer)?; + match &channel_monitor.shutdown_script { + Some(script) => script.write(writer)?, + None => ScriptBuf::new().write(writer)?, + } + + channel_monitor.channel_keys_id.write(writer)?; + channel_monitor.holder_revocation_basepoint.write(writer)?; + let funding_outpoint = channel_monitor.get_funding_txo(); + writer.write_all(&funding_outpoint.txid[..])?; + writer.write_all(&funding_outpoint.index.to_be_bytes())?; + let redeem_script = channel_monitor.funding.channel_parameters.make_funding_redeemscript(); + let script_pubkey = redeem_script.to_p2wsh(); + script_pubkey.write(writer)?; + channel_monitor.funding.current_counterparty_commitment_txid.write(writer)?; + channel_monitor.funding.prev_counterparty_commitment_txid.write(writer)?; + + channel_monitor.counterparty_commitment_params.write(writer)?; + redeem_script.write(writer)?; + channel_monitor.funding.channel_parameters.channel_value_satoshis.write(writer)?; + + match channel_monitor.their_cur_per_commitment_points { + Some((idx, pubkey, second_option)) => { + writer.write_all(&byte_utils::be48_to_array(idx))?; + writer.write_all(&pubkey.serialize())?; + match second_option { + Some(second_pubkey) => { + writer.write_all(&second_pubkey.serialize())?; + }, + None => { + writer.write_all(&[0; 33])?; + }, + } + }, + None => { + writer.write_all(&byte_utils::be48_to_array(0))?; + }, + } - self.commitment_secrets.write(writer)?; + writer.write_all(&channel_monitor.on_holder_tx_csv.to_be_bytes())?; - #[rustfmt::skip] - macro_rules! serialize_htlc_in_commitment { - ($htlc_output: expr) => { - writer.write_all(&[$htlc_output.offered as u8; 1])?; - writer.write_all(&$htlc_output.amount_msat.to_be_bytes())?; - writer.write_all(&$htlc_output.cltv_expiry.to_be_bytes())?; - writer.write_all(&$htlc_output.payment_hash.0[..])?; - $htlc_output.transaction_output_index.write(writer)?; - } - } + channel_monitor.commitment_secrets.write(writer)?; - writer.write_all(&(self.funding.counterparty_claimable_outpoints.len() as u64).to_be_bytes())?; - for (ref txid, ref htlc_infos) in self.funding.counterparty_claimable_outpoints.iter() { - writer.write_all(&txid[..])?; - writer.write_all(&(htlc_infos.len() as u64).to_be_bytes())?; - for &(ref htlc_output, ref htlc_source) in htlc_infos.iter() { - debug_assert!(htlc_source.is_none() || Some(**txid) == self.funding.current_counterparty_commitment_txid - || Some(**txid) == self.funding.prev_counterparty_commitment_txid, - "HTLC Sources for all revoked commitment transactions should be none!"); - serialize_htlc_in_commitment!(htlc_output); - htlc_source.as_ref().map(|b| b.as_ref()).write(writer)?; - } + #[rustfmt::skip] + macro_rules! serialize_htlc_in_commitment { + ($htlc_output: expr) => { + writer.write_all(&[$htlc_output.offered as u8; 1])?; + writer.write_all(&$htlc_output.amount_msat.to_be_bytes())?; + writer.write_all(&$htlc_output.cltv_expiry.to_be_bytes())?; + writer.write_all(&$htlc_output.payment_hash.0[..])?; + $htlc_output.transaction_output_index.write(writer)?; } + } - writer.write_all(&(self.counterparty_commitment_txn_on_chain.len() as u64).to_be_bytes())?; - for (ref txid, commitment_number) in self.counterparty_commitment_txn_on_chain.iter() { - writer.write_all(&txid[..])?; - writer.write_all(&byte_utils::be48_to_array(*commitment_number))?; + writer.write_all( + &(channel_monitor.funding.counterparty_claimable_outpoints.len() as u64).to_be_bytes(), + )?; + for (ref txid, ref htlc_infos) in + channel_monitor.funding.counterparty_claimable_outpoints.iter() + { + writer.write_all(&txid[..])?; + writer.write_all(&(htlc_infos.len() as u64).to_be_bytes())?; + for &(ref htlc_output, ref htlc_source) in htlc_infos.iter() { + debug_assert!( + htlc_source.is_none() + || Some(**txid) == channel_monitor.funding.current_counterparty_commitment_txid + || Some(**txid) == channel_monitor.funding.prev_counterparty_commitment_txid, + "HTLC Sources for all revoked commitment transactions should be none!" + ); + serialize_htlc_in_commitment!(htlc_output); + htlc_source.as_ref().map(|b| b.as_ref()).write(writer)?; } + } - writer.write_all(&(self.counterparty_hash_commitment_number.len() as u64).to_be_bytes())?; - for (ref payment_hash, commitment_number) in self.counterparty_hash_commitment_number.iter() { - writer.write_all(&payment_hash.0[..])?; - writer.write_all(&byte_utils::be48_to_array(*commitment_number))?; - } + writer.write_all( + &(channel_monitor.counterparty_commitment_txn_on_chain.len() as u64).to_be_bytes(), + )?; + for (ref txid, commitment_number) in channel_monitor.counterparty_commitment_txn_on_chain.iter() + { + writer.write_all(&txid[..])?; + writer.write_all(&byte_utils::be48_to_array(*commitment_number))?; + } - if let Some(holder_commitment_tx) = &self.funding.prev_holder_commitment_tx { - writer.write_all(&[1; 1])?; - write_legacy_holder_commitment_data( - writer, holder_commitment_tx, &self.prev_holder_htlc_data.as_ref().unwrap(), - )?; - } else { - writer.write_all(&[0; 1])?; - } + writer.write_all( + &(channel_monitor.counterparty_hash_commitment_number.len() as u64).to_be_bytes(), + )?; + for (ref payment_hash, commitment_number) in + channel_monitor.counterparty_hash_commitment_number.iter() + { + writer.write_all(&payment_hash.0[..])?; + writer.write_all(&byte_utils::be48_to_array(*commitment_number))?; + } + if let Some(holder_commitment_tx) = &channel_monitor.funding.prev_holder_commitment_tx { + writer.write_all(&[1; 1])?; write_legacy_holder_commitment_data( - writer, &self.funding.current_holder_commitment_tx, &self.current_holder_htlc_data, + writer, + holder_commitment_tx, + &channel_monitor.prev_holder_htlc_data.as_ref().unwrap(), )?; + } else { + writer.write_all(&[0; 1])?; + } - writer.write_all(&byte_utils::be48_to_array(self.current_counterparty_commitment_number))?; - writer.write_all(&byte_utils::be48_to_array(self.current_holder_commitment_number))?; + write_legacy_holder_commitment_data( + writer, + &channel_monitor.funding.current_holder_commitment_tx, + &channel_monitor.current_holder_htlc_data, + )?; - writer.write_all(&(self.payment_preimages.len() as u64).to_be_bytes())?; - for (payment_preimage, _) in self.payment_preimages.values() { - writer.write_all(&payment_preimage.0[..])?; - } + writer.write_all(&byte_utils::be48_to_array( + channel_monitor.current_counterparty_commitment_number, + ))?; + writer + .write_all(&byte_utils::be48_to_array(channel_monitor.current_holder_commitment_number))?; - writer.write_all(&(self.pending_monitor_events.iter().filter(|ev| match ev { - MonitorEvent::HTLCEvent(_) => true, - MonitorEvent::HolderForceClosed(_) => true, - MonitorEvent::HolderForceClosedWithInfo { .. } => true, - _ => false, - }).count() as u64).to_be_bytes())?; - for event in self.pending_monitor_events.iter() { - match event { - MonitorEvent::HTLCEvent(upd) => { - 0u8.write(writer)?; - upd.write(writer)?; - }, - MonitorEvent::HolderForceClosed(_) => 1u8.write(writer)?, - // `HolderForceClosedWithInfo` replaced `HolderForceClosed` in v0.0.122. To keep - // backwards compatibility, we write a `HolderForceClosed` event along with the - // `HolderForceClosedWithInfo` event. This is deduplicated in the reader. - MonitorEvent::HolderForceClosedWithInfo { .. } => 1u8.write(writer)?, - _ => {}, // Covered in the TLV writes below - } - } + writer.write_all(&(channel_monitor.payment_preimages.len() as u64).to_be_bytes())?; + for (payment_preimage, _) in channel_monitor.payment_preimages.values() { + writer.write_all(&payment_preimage.0[..])?; + } - writer.write_all(&(self.pending_events.len() as u64).to_be_bytes())?; - for event in self.pending_events.iter() { - event.write(writer)?; + writer.write_all( + &(channel_monitor + .pending_monitor_events + .iter() + .filter(|ev| match ev { + MonitorEvent::HTLCEvent(_) => true, + MonitorEvent::HolderForceClosed(_) => true, + MonitorEvent::HolderForceClosedWithInfo { .. } => true, + _ => false, + }) + .count() as u64) + .to_be_bytes(), + )?; + for event in channel_monitor.pending_monitor_events.iter() { + match event { + MonitorEvent::HTLCEvent(upd) => { + 0u8.write(writer)?; + upd.write(writer)?; + }, + MonitorEvent::HolderForceClosed(_) => 1u8.write(writer)?, + // `HolderForceClosedWithInfo` replaced `HolderForceClosed` in v0.0.122. To keep + // backwards compatibility, we write a `HolderForceClosed` event along with the + // `HolderForceClosedWithInfo` event. This is deduplicated in the reader. + MonitorEvent::HolderForceClosedWithInfo { .. } => 1u8.write(writer)?, + _ => {}, // Covered in the TLV writes below } + } - self.best_block.block_hash.write(writer)?; - writer.write_all(&self.best_block.height.to_be_bytes())?; + writer.write_all(&(channel_monitor.pending_events.len() as u64).to_be_bytes())?; + for event in channel_monitor.pending_events.iter() { + event.write(writer)?; + } - writer.write_all(&(self.onchain_events_awaiting_threshold_conf.len() as u64).to_be_bytes())?; - for ref entry in self.onchain_events_awaiting_threshold_conf.iter() { - entry.write(writer)?; - } + channel_monitor.best_block.block_hash.write(writer)?; + writer.write_all(&channel_monitor.best_block.height.to_be_bytes())?; - (self.outputs_to_watch.len() as u64).write(writer)?; - for (txid, idx_scripts) in self.outputs_to_watch.iter() { - txid.write(writer)?; - (idx_scripts.len() as u64).write(writer)?; - for (idx, script) in idx_scripts.iter() { - idx.write(writer)?; - script.write(writer)?; - } + writer.write_all( + &(channel_monitor.onchain_events_awaiting_threshold_conf.len() as u64).to_be_bytes(), + )?; + for ref entry in channel_monitor.onchain_events_awaiting_threshold_conf.iter() { + entry.write(writer)?; + } + + (channel_monitor.outputs_to_watch.len() as u64).write(writer)?; + for (txid, idx_scripts) in channel_monitor.outputs_to_watch.iter() { + txid.write(writer)?; + (idx_scripts.len() as u64).write(writer)?; + for (idx, script) in idx_scripts.iter() { + idx.write(writer)?; + script.write(writer)?; } - self.onchain_tx_handler.write(writer)?; + } - self.lockdown_from_offchain.write(writer)?; - self.holder_tx_signed.write(writer)?; + channel_monitor.onchain_tx_handler.write(writer)?; - // If we have a `HolderForceClosedWithInfo` event, we need to write the `HolderForceClosed` for backwards compatibility. - let pending_monitor_events = match self.pending_monitor_events.iter().find(|ev| match ev { + channel_monitor.lockdown_from_offchain.write(writer)?; + channel_monitor.holder_tx_signed.write(writer)?; + + // If we have a `HolderForceClosedWithInfo` event, we need to write the `HolderForceClosed` for backwards compatibility. + let pending_monitor_events = + match channel_monitor.pending_monitor_events.iter().find(|ev| match ev { MonitorEvent::HolderForceClosedWithInfo { .. } => true, _ => false, }) { Some(MonitorEvent::HolderForceClosedWithInfo { outpoint, .. }) => { - let mut pending_monitor_events = self.pending_monitor_events.clone(); + let mut pending_monitor_events = channel_monitor.pending_monitor_events.clone(); pending_monitor_events.push(MonitorEvent::HolderForceClosed(*outpoint)); pending_monitor_events - } - _ => self.pending_monitor_events.clone(), + }, + _ => channel_monitor.pending_monitor_events.clone(), }; - write_tlv_fields!(writer, { - (1, self.funding_spend_confirmed, option), - (3, self.htlcs_resolved_on_chain, required_vec), - (5, pending_monitor_events, required_vec), - (7, self.funding_spend_seen, required), - (9, self.counterparty_node_id, required), - (11, self.confirmed_commitment_tx_counterparty_output, option), - (13, self.spendable_txids_confirmed, required_vec), - (15, self.counterparty_fulfilled_htlcs, required), - (17, self.initial_counterparty_commitment_info, option), - (19, self.channel_id, required), - (21, self.balances_empty_height, option), - (23, self.holder_pays_commitment_tx_fee, option), - (25, self.payment_preimages, required), - (27, self.first_negotiated_funding_txo, required), - (29, self.initial_counterparty_commitment_tx, option), - (31, self.funding.channel_parameters, required), - (32, self.pending_funding, optional_vec), - }); + write_tlv_fields!(writer, { + (1, channel_monitor.funding_spend_confirmed, option), + (3, channel_monitor.htlcs_resolved_on_chain, required_vec), + (5, pending_monitor_events, required_vec), + (7, channel_monitor.funding_spend_seen, required), + (9, channel_monitor.counterparty_node_id, required), + (11, channel_monitor.confirmed_commitment_tx_counterparty_output, option), + (13, channel_monitor.spendable_txids_confirmed, required_vec), + (15, channel_monitor.counterparty_fulfilled_htlcs, required), + (17, channel_monitor.initial_counterparty_commitment_info, option), + (19, channel_monitor.channel_id, required), + (21, channel_monitor.balances_empty_height, option), + (23, channel_monitor.holder_pays_commitment_tx_fee, option), + (25, channel_monitor.payment_preimages, required), + (27, channel_monitor.first_negotiated_funding_txo, required), + (29, channel_monitor.initial_counterparty_commitment_tx, option), + (31, channel_monitor.funding.channel_parameters, required), + (32, channel_monitor.pending_funding, optional_vec), + (34, channel_monitor.alternative_funding_confirmed, option), + }); - Ok(()) + Ok(()) +} + +impl Writeable for ChannelMonitorImpl { + fn write(&self, writer: &mut W) -> Result<(), Error> { + write_chanmon_internal(self, false, writer) } } @@ -1780,6 +1873,8 @@ impl ChannelMonitor { // There are never any HTLCs in the initial commitment transaction current_holder_htlc_data: CommitmentHTLCData::new(), prev_holder_htlc_data: None, + + alternative_funding_confirmed: None, }) } @@ -2081,6 +2176,10 @@ impl ChannelMonitor { /// to the commitment transaction being revoked, this will return a signed transaction, but /// the signature will not be valid. /// + /// Note that due to splicing, this can also return an `Err` when the counterparty commitment + /// this transaction is attempting to claim is no longer valid because the corresponding funding + /// transaction was spliced. + /// /// [`EcdsaChannelSigner::sign_justice_revoked_output`]: crate::sign::ecdsa::EcdsaChannelSigner::sign_justice_revoked_output /// [`Persist`]: crate::chain::chainmonitor::Persist #[rustfmt::skip] @@ -2385,7 +2484,8 @@ impl ChannelMonitor { pub fn get_spendable_outputs(&self, tx: &Transaction, confirmation_height: u32) -> Vec { let inner = self.inner.lock().unwrap(); let current_height = inner.best_block.height; - let mut spendable_outputs = inner.get_spendable_outputs(tx); + let funding = get_confirmed_funding_scope!(inner); + let mut spendable_outputs = inner.get_spendable_outputs(&funding, tx); spendable_outputs.retain(|descriptor| { let mut conf_threshold = current_height.saturating_sub(ANTI_REORG_DELAY) + 1; if let SpendableOutputDescriptor::DelayedPaymentOutput(descriptor) = descriptor { @@ -2992,17 +3092,19 @@ impl ChannelMonitor { } } - let txid = confirmed_txid.unwrap(); - if Some(txid) == us.funding.current_counterparty_commitment_txid || Some(txid) == us.funding.prev_counterparty_commitment_txid { - walk_htlcs!(false, us.funding.counterparty_claimable_outpoints.get(&txid).unwrap().iter().filter_map(|(a, b)| { + let commitment_txid = confirmed_txid.unwrap(); + let funding_spent = get_confirmed_funding_scope!(us); + + if Some(commitment_txid) == funding_spent.current_counterparty_commitment_txid || Some(commitment_txid) == funding_spent.prev_counterparty_commitment_txid { + walk_htlcs!(false, funding_spent.counterparty_claimable_outpoints.get(&commitment_txid).unwrap().iter().filter_map(|(a, b)| { if let &Some(ref source) = b { Some((a, Some(&**source))) } else { None } })); - } else if txid == us.funding.current_holder_commitment_tx.trust().txid() { + } else if commitment_txid == funding_spent.current_holder_commitment_tx.trust().txid() { walk_htlcs!(true, holder_commitment_htlcs!(us, CURRENT_WITH_SOURCES)); - } else if let Some(prev_commitment_tx) = &us.funding.prev_holder_commitment_tx { - if txid == prev_commitment_tx.trust().txid() { + } else if let Some(prev_commitment_tx) = &funding_spent.prev_holder_commitment_tx { + if commitment_txid == prev_commitment_tx.trust().txid() { walk_htlcs!(true, holder_commitment_htlcs!(us, PREV_WITH_SOURCES).unwrap()); } } @@ -3539,12 +3641,13 @@ impl ChannelMonitorImpl { } else { return; }; + let funding_spent = get_confirmed_funding_scope!(self); // If the channel is force closed, try to claim the output from this preimage. // First check if a counterparty commitment transaction has been broadcasted: macro_rules! claim_htlcs { ($commitment_number: expr, $txid: expr, $htlcs: expr) => { - let (htlc_claim_reqs, _) = self.get_counterparty_output_claim_info($commitment_number, $txid, None, $htlcs, confirmed_spend_height); + let (htlc_claim_reqs, _) = self.get_counterparty_output_claim_info(funding_spent, $commitment_number, $txid, None, $htlcs, confirmed_spend_height); let conf_target = self.closure_conf_target(); self.onchain_tx_handler.update_claims_view_from_requests( htlc_claim_reqs, self.best_block.height, self.best_block.height, broadcaster, @@ -3552,10 +3655,10 @@ impl ChannelMonitorImpl { ); } } - if let Some(txid) = self.funding.current_counterparty_commitment_txid { + if let Some(txid) = funding_spent.current_counterparty_commitment_txid { if txid == confirmed_spend_txid { if let Some(commitment_number) = self.counterparty_commitment_txn_on_chain.get(&txid) { - claim_htlcs!(*commitment_number, txid, self.funding.counterparty_claimable_outpoints.get(&txid)); + claim_htlcs!(*commitment_number, txid, funding_spent.counterparty_claimable_outpoints.get(&txid)); } else { debug_assert!(false); log_error!(logger, "Detected counterparty commitment tx on-chain without tracking commitment number"); @@ -3563,10 +3666,10 @@ impl ChannelMonitorImpl { return; } } - if let Some(txid) = self.funding.prev_counterparty_commitment_txid { + if let Some(txid) = funding_spent.prev_counterparty_commitment_txid { if txid == confirmed_spend_txid { if let Some(commitment_number) = self.counterparty_commitment_txn_on_chain.get(&txid) { - claim_htlcs!(*commitment_number, txid, self.funding.counterparty_claimable_outpoints.get(&txid)); + claim_htlcs!(*commitment_number, txid, funding_spent.counterparty_claimable_outpoints.get(&txid)); } else { debug_assert!(false); log_error!(logger, "Detected counterparty commitment tx on-chain without tracking commitment number"); @@ -3581,9 +3684,9 @@ impl ChannelMonitorImpl { // *we* sign a holder commitment transaction, not when e.g. a watchtower broadcasts one of our // holder commitment transactions. if self.broadcasted_holder_revokable_script.is_some() { - let holder_commitment_tx = if self.funding.current_holder_commitment_tx.trust().txid() == confirmed_spend_txid { - Some(&self.funding.current_holder_commitment_tx) - } else if let Some(prev_holder_commitment_tx) = &self.funding.prev_holder_commitment_tx { + let holder_commitment_tx = if funding_spent.current_holder_commitment_tx.trust().txid() == confirmed_spend_txid { + Some(&funding_spent.current_holder_commitment_tx) + } else if let Some(prev_holder_commitment_tx) = &funding_spent.prev_holder_commitment_tx { if prev_holder_commitment_tx.trust().txid() == confirmed_spend_txid { Some(prev_holder_commitment_tx) } else { @@ -3596,7 +3699,9 @@ impl ChannelMonitorImpl { // Assume that the broadcasted commitment transaction confirmed in the current best // block. Even if not, its a reasonable metric for the bump criteria on the HTLC // transactions. - let (claim_reqs, _) = self.get_broadcasted_holder_claims(holder_commitment_tx, self.best_block.height); + let (claim_reqs, _) = self.get_broadcasted_holder_claims( + funding_spent, holder_commitment_tx, self.best_block.height, + ); let conf_target = self.closure_conf_target(); self.onchain_tx_handler.update_claims_view_from_requests( claim_reqs, self.best_block.height, self.best_block.height, broadcaster, @@ -3607,25 +3712,30 @@ impl ChannelMonitorImpl { } #[rustfmt::skip] - fn generate_claimable_outpoints_and_watch_outputs(&mut self, reason: ClosureReason) -> (Vec, Vec) { - let holder_commitment_tx = &self.funding.current_holder_commitment_tx; + fn generate_claimable_outpoints_and_watch_outputs( + &mut self, generate_monitor_event_with_reason: Option, + ) -> (Vec, Vec) { + let funding = get_confirmed_funding_scope!(self); + let holder_commitment_tx = &funding.current_holder_commitment_tx; let funding_outp = HolderFundingOutput::build( holder_commitment_tx.clone(), - self.funding.channel_parameters.clone(), + funding.channel_parameters.clone(), ); - let funding_outpoint = self.get_funding_txo(); + let funding_outpoint = funding.funding_outpoint(); let commitment_package = PackageTemplate::build_package( funding_outpoint.txid.clone(), funding_outpoint.index as u32, PackageSolvingData::HolderFundingOutput(funding_outp), self.best_block.height, ); let mut claimable_outpoints = vec![commitment_package]; - let event = MonitorEvent::HolderForceClosedWithInfo { - reason, - outpoint: funding_outpoint, - channel_id: self.channel_id, - }; - self.pending_monitor_events.push(event); + if let Some(reason) = generate_monitor_event_with_reason { + let event = MonitorEvent::HolderForceClosedWithInfo { + reason, + outpoint: funding_outpoint, + channel_id: self.channel_id, + }; + self.pending_monitor_events.push(event); + } // Although we aren't signing the transaction directly here, the transaction will be signed // in the claim that is queued to OnchainTxHandler. We set holder_tx_signed here to reject @@ -3635,12 +3745,12 @@ impl ChannelMonitorImpl { // We can't broadcast our HTLC transactions while the commitment transaction is // unconfirmed. We'll delay doing so until we detect the confirmed commitment in // `transactions_confirmed`. - if !self.channel_type_features().supports_anchors_zero_fee_htlc_tx() { + if !funding.channel_type_features().supports_anchors_zero_fee_htlc_tx() { // Because we're broadcasting a commitment transaction, we should construct the package // assuming it gets confirmed in the next block. Sadly, we have code which considers // "not yet confirmed" things as discardable, so we cannot do that here. let (mut new_outpoints, _) = self.get_broadcasted_holder_claims( - holder_commitment_tx, self.best_block.height, + funding, holder_commitment_tx, self.best_block.height, ); let new_outputs = self.get_broadcasted_holder_watch_outputs(holder_commitment_tx); if !new_outputs.is_empty() { @@ -3664,7 +3774,7 @@ impl ChannelMonitorImpl { broadcasted_latest_txn: Some(true), message: "ChannelMonitor-initiated commitment transaction broadcast".to_owned(), }; - let (claimable_outpoints, _) = self.generate_claimable_outpoints_and_watch_outputs(reason); + let (claimable_outpoints, _) = self.generate_claimable_outpoints_and_watch_outputs(Some(reason)); let conf_target = self.closure_conf_target(); self.onchain_tx_handler.update_claims_view_from_requests( claimable_outpoints, self.best_block.height, self.best_block.height, broadcaster, @@ -3815,6 +3925,12 @@ impl ChannelMonitorImpl { for funding in self.pending_funding.drain(..) { self.outputs_to_watch.remove(&funding.funding_txid()); } + if let Some((alternative_funding_txid, _)) = self.alternative_funding_confirmed.take() { + // In exceedingly rare cases, it's possible there was a reorg that caused a potential funding to + // be locked in that this `ChannelMonitor` has not yet seen. Thus, we avoid a runtime assertion + // and only assert in debug mode. + debug_assert_eq!(alternative_funding_txid, new_funding_txid); + } Ok(()) } @@ -4126,8 +4242,14 @@ impl ChannelMonitorImpl { to_countersignatory_value, )| { let nondust_htlcs = vec![]; + // Since we're expected to only reach here during the initial persistence of a + // monitor (i.e., via [`Persist::persist_new_channel`]), we expect to only have + // one `FundingScope` present. + debug_assert!(self.pending_funding.is_empty()); + let channel_parameters = &self.funding.channel_parameters; let commitment_tx = self.build_counterparty_commitment_tx( + channel_parameters, INITIAL_COMMITMENT_NUMBER, &their_per_commitment_point, to_broadcaster_value, @@ -4145,11 +4267,12 @@ impl ChannelMonitorImpl { #[rustfmt::skip] fn build_counterparty_commitment_tx( - &self, commitment_number: u64, their_per_commitment_point: &PublicKey, - to_broadcaster_value: u64, to_countersignatory_value: u64, feerate_per_kw: u32, + &self, channel_parameters: &ChannelTransactionParameters, commitment_number: u64, + their_per_commitment_point: &PublicKey, to_broadcaster_value: u64, + to_countersignatory_value: u64, feerate_per_kw: u32, nondust_htlcs: Vec ) -> CommitmentTransaction { - let channel_parameters = &self.funding.channel_parameters.as_counterparty_broadcastable(); + let channel_parameters = &channel_parameters.as_counterparty_broadcastable(); CommitmentTransaction::new(commitment_number, their_per_commitment_point, to_broadcaster_value, to_countersignatory_value, feerate_per_kw, nondust_htlcs, channel_parameters, &self.onchain_tx_handler.secp_ctx) } @@ -4171,9 +4294,20 @@ impl ChannelMonitorImpl { htlc.transaction_output_index.map(|_| htlc).cloned() }).collect::>(); - let commitment_tx = self.build_counterparty_commitment_tx(commitment_number, - &their_per_commitment_point, to_broadcaster_value, - to_countersignatory_value, feerate_per_kw, nondust_htlcs); + // This monitor update variant is only applicable while there's a single + // `FundingScope` active, otherwise we expect to see + // `LatestCounterpartyCommitment` instead. + debug_assert!(self.pending_funding.is_empty()); + let channel_parameters = &self.funding.channel_parameters; + let commitment_tx = self.build_counterparty_commitment_tx( + channel_parameters, + commitment_number, + &their_per_commitment_point, + to_broadcaster_value, + to_countersignatory_value, + feerate_per_kw, + nondust_htlcs, + ); debug_assert_eq!(commitment_tx.trust().txid(), commitment_txid); @@ -4206,7 +4340,17 @@ impl ChannelMonitorImpl { let revokeable_redeemscript = chan_utils::get_revokeable_redeemscript(&revocation_pubkey, self.counterparty_commitment_params.on_counterparty_tx_csv, &delayed_key); - let channel_parameters = &self.funding.channel_parameters; + let commitment_txid = &justice_tx.input[input_idx].previous_output.txid; + // Since there may be multiple counterparty commitment transactions for the same commitment + // number due to splicing, we have to locate the matching `FundingScope::channel_parameters` + // to provide the signer. Since this is intended to be called during + // `Persist::update_persisted_channel`, the monitor should have already had the update + // applied. + let channel_parameters = core::iter::once(&self.funding) + .chain(&self.pending_funding) + .find(|funding| funding.counterparty_claimable_outpoints.contains_key(commitment_txid)) + .map(|funding| &funding.channel_parameters) + .ok_or(())?; let sig = self.onchain_tx_handler.signer.sign_justice_revoked_output( &channel_parameters, &justice_tx, input_idx, value, &per_commitment_key, &self.onchain_tx_handler.secp_ctx, @@ -4242,7 +4386,7 @@ impl ChannelMonitorImpl { /// Returns packages to claim the revoked output(s) and general information about the output that /// is to the counterparty in the commitment transaction. #[rustfmt::skip] - fn check_spend_counterparty_transaction(&mut self, tx: &Transaction, height: u32, block_hash: &BlockHash, logger: &L) + fn check_spend_counterparty_transaction(&mut self, commitment_txid: Txid, commitment_tx: &Transaction, height: u32, block_hash: &BlockHash, logger: &L) -> (Vec, CommitmentTxCounterpartyOutputInfo) where L::Target: Logger { // Most secp and related errors trying to create keys means we have no hope of constructing @@ -4250,8 +4394,8 @@ impl ChannelMonitorImpl { let mut claimable_outpoints = Vec::new(); let mut to_counterparty_output_info = None; - let commitment_txid = tx.compute_txid(); //TODO: This is gonna be a performance bottleneck for watchtowers! - let per_commitment_option = self.funding.counterparty_claimable_outpoints.get(&commitment_txid); + let funding_spent = get_confirmed_funding_scope!(self); + let per_commitment_option = funding_spent.counterparty_claimable_outpoints.get(&commitment_txid); macro_rules! ignore_error { ( $thing : expr ) => { @@ -4262,8 +4406,11 @@ impl ChannelMonitorImpl { }; } - let commitment_number = 0xffffffffffff - ((((tx.input[0].sequence.0 as u64 & 0xffffff) << 3*8) | (tx.lock_time.to_consensus_u32() as u64 & 0xffffff)) ^ self.commitment_transaction_number_obscure_factor); + let funding_txid_spent = commitment_tx.input[0].previous_output.txid; + let commitment_number = 0xffffffffffff - ((((commitment_tx.input[0].sequence.0 as u64 & 0xffffff) << 3*8) | (commitment_tx.lock_time.to_consensus_u32() as u64 & 0xffffff)) ^ self.commitment_transaction_number_obscure_factor); if commitment_number >= self.get_min_seen_secret() { + assert_eq!(funding_spent.funding_txid(), funding_txid_spent); + let secret = self.get_secret(commitment_number).unwrap(); let per_commitment_key = ignore_error!(SecretKey::from_slice(&secret)); let per_commitment_point = PublicKey::from_secret_key(&self.onchain_tx_handler.secp_ctx, &per_commitment_key); @@ -4274,13 +4421,12 @@ impl ChannelMonitorImpl { let revokeable_p2wsh = revokeable_redeemscript.to_p2wsh(); // First, process non-htlc outputs (to_holder & to_counterparty) - for (idx, outp) in tx.output.iter().enumerate() { + for (idx, outp) in commitment_tx.output.iter().enumerate() { if outp.script_pubkey == revokeable_p2wsh { let revk_outp = RevokedOutput::build( per_commitment_point, per_commitment_key, outp.value, - self.funding.channel_parameters.channel_type_features.supports_anchors_zero_fee_htlc_tx(), - self.funding.channel_parameters.clone(), - height, + funding_spent.channel_type_features().supports_anchors_zero_fee_htlc_tx(), + funding_spent.channel_parameters.clone(), height, ); let justice_package = PackageTemplate::build_package( commitment_txid, idx as u32, @@ -4297,15 +4443,14 @@ impl ChannelMonitorImpl { if let Some(per_commitment_claimable_data) = per_commitment_option { for (htlc, _) in per_commitment_claimable_data { if let Some(transaction_output_index) = htlc.transaction_output_index { - if transaction_output_index as usize >= tx.output.len() || - tx.output[transaction_output_index as usize].value != htlc.to_bitcoin_amount() { + if transaction_output_index as usize >= commitment_tx.output.len() || + commitment_tx.output[transaction_output_index as usize].value != htlc.to_bitcoin_amount() { // per_commitment_data is corrupt or our commitment signing key leaked! return (claimable_outpoints, to_counterparty_output_info); } let revk_htlc_outp = RevokedHTLCOutput::build( per_commitment_point, per_commitment_key, htlc.clone(), - self.funding.channel_parameters.clone(), - height, + funding_spent.channel_parameters.clone(), height, ); let counterparty_spendable_height = if htlc.offered { htlc.cltv_expiry @@ -4330,7 +4475,7 @@ impl ChannelMonitorImpl { self.counterparty_commitment_txn_on_chain.insert(commitment_txid, commitment_number); if let Some(per_commitment_claimable_data) = per_commitment_option { - fail_unbroadcast_htlcs!(self, "revoked_counterparty", commitment_txid, tx, height, + fail_unbroadcast_htlcs!(self, "revoked_counterparty", commitment_txid, commitment_tx, height, block_hash, per_commitment_claimable_data.iter().map(|(htlc, htlc_source)| (htlc, htlc_source.as_ref().map(|htlc_source| htlc_source.as_ref())) ), logger); @@ -4340,11 +4485,13 @@ impl ChannelMonitorImpl { // commitment transactions. Thus, we can only debug-assert here when not // fuzzing. debug_assert!(cfg!(fuzzing), "We should have per-commitment option for any recognized old commitment txn"); - fail_unbroadcast_htlcs!(self, "revoked counterparty", commitment_txid, tx, height, + fail_unbroadcast_htlcs!(self, "revoked counterparty", commitment_txid, commitment_tx, height, block_hash, [].iter().map(|reference| *reference), logger); } } } else if let Some(per_commitment_claimable_data) = per_commitment_option { + assert_eq!(funding_spent.funding_txid(), funding_txid_spent); + // While this isn't useful yet, there is a potential race where if a counterparty // revokes a state at the same time as the commitment transaction for that state is // confirmed, and the watchtower receives the block before the user, the user could @@ -4355,25 +4502,26 @@ impl ChannelMonitorImpl { self.counterparty_commitment_txn_on_chain.insert(commitment_txid, commitment_number); log_info!(logger, "Got broadcast of non-revoked counterparty commitment transaction {}", commitment_txid); - fail_unbroadcast_htlcs!(self, "counterparty", commitment_txid, tx, height, block_hash, + fail_unbroadcast_htlcs!(self, "counterparty", commitment_txid, commitment_tx, height, block_hash, per_commitment_claimable_data.iter().map(|(htlc, htlc_source)| (htlc, htlc_source.as_ref().map(|htlc_source| htlc_source.as_ref())) ), logger); let (htlc_claim_reqs, counterparty_output_info) = - self.get_counterparty_output_claim_info(commitment_number, commitment_txid, Some(tx), per_commitment_option, Some(height)); + self.get_counterparty_output_claim_info(funding_spent, commitment_number, commitment_txid, Some(commitment_tx), per_commitment_option, Some(height)); to_counterparty_output_info = counterparty_output_info; for req in htlc_claim_reqs { claimable_outpoints.push(req); } - } + (claimable_outpoints, to_counterparty_output_info) } /// Returns the HTLC claim package templates and the counterparty output info #[rustfmt::skip] fn get_counterparty_output_claim_info( - &self, commitment_number: u64, commitment_txid: Txid, tx: Option<&Transaction>, + &self, funding_spent: &FundingScope, commitment_number: u64, commitment_txid: Txid, + tx: Option<&Transaction>, per_commitment_option: Option<&Vec<(HTLCOutputInCommitment, Option>)>>, confirmation_height: Option, ) -> (Vec, CommitmentTxCounterpartyOutputInfo) { @@ -4435,7 +4583,7 @@ impl ChannelMonitorImpl { CounterpartyOfferedHTLCOutput::build( *per_commitment_point, preimage.unwrap(), htlc.clone(), - self.funding.channel_parameters.clone(), + funding_spent.channel_parameters.clone(), confirmation_height, ) ) @@ -4444,7 +4592,7 @@ impl ChannelMonitorImpl { CounterpartyReceivedHTLCOutput::build( *per_commitment_point, htlc.clone(), - self.funding.channel_parameters.clone(), + funding_spent.channel_parameters.clone(), confirmation_height, ) ) @@ -4470,6 +4618,9 @@ impl ChannelMonitorImpl { }; let per_commitment_point = PublicKey::from_secret_key(&self.onchain_tx_handler.secp_ctx, &per_commitment_key); + let funding_spent = get_confirmed_funding_scope!(self); + debug_assert!(funding_spent.counterparty_claimable_outpoints.contains_key(commitment_txid)); + let htlc_txid = tx.compute_txid(); let mut claimable_outpoints = vec![]; let mut outputs_to_watch = None; @@ -4488,7 +4639,7 @@ impl ChannelMonitorImpl { log_error!(logger, "Got broadcast of revoked counterparty HTLC transaction, spending {}:{}", htlc_txid, idx); let revk_outp = RevokedOutput::build( per_commitment_point, per_commitment_key, tx.output[idx].value, false, - self.funding.channel_parameters.clone(), + funding_spent.channel_parameters.clone(), height, ); let justice_package = PackageTemplate::build_package( @@ -4507,7 +4658,7 @@ impl ChannelMonitorImpl { #[rustfmt::skip] fn get_broadcasted_holder_htlc_descriptors( - &self, holder_tx: &HolderCommitmentTransaction, + &self, funding: &FundingScope, holder_tx: &HolderCommitmentTransaction, ) -> Vec { let tx = holder_tx.trust(); let mut htlcs = Vec::with_capacity(holder_tx.nondust_htlcs().len()); @@ -4525,11 +4676,10 @@ impl ChannelMonitorImpl { }; htlcs.push(HTLCDescriptor { - // TODO(splicing): Consider alternative funding scopes. channel_derivation_parameters: ChannelDerivationParameters { - value_satoshis: self.funding.channel_parameters.channel_value_satoshis, + value_satoshis: funding.channel_parameters.channel_value_satoshis, keys_id: self.channel_keys_id, - transaction_parameters: self.funding.channel_parameters.clone(), + transaction_parameters: funding.channel_parameters.clone(), }, commitment_txid: tx.txid(), per_commitment_number: tx.commitment_number(), @@ -4549,7 +4699,7 @@ impl ChannelMonitorImpl { // script so we can detect whether a holder transaction has been seen on-chain. #[rustfmt::skip] fn get_broadcasted_holder_claims( - &self, holder_tx: &HolderCommitmentTransaction, conf_height: u32, + &self, funding: &FundingScope, holder_tx: &HolderCommitmentTransaction, conf_height: u32, ) -> (Vec, Option<(ScriptBuf, PublicKey, RevocationKey)>) { let tx = holder_tx.trust(); let keys = tx.keys(); @@ -4560,7 +4710,7 @@ impl ChannelMonitorImpl { redeem_script.to_p2wsh(), holder_tx.per_commitment_point(), keys.revocation_key.clone(), )); - let claim_requests = self.get_broadcasted_holder_htlc_descriptors(holder_tx).into_iter() + let claim_requests = self.get_broadcasted_holder_htlc_descriptors(funding, holder_tx).into_iter() .map(|htlc_descriptor| { let counterparty_spendable_height = if htlc_descriptor.htlc.offered { conf_height @@ -4603,64 +4753,65 @@ impl ChannelMonitorImpl { /// Should not be used if check_spend_revoked_transaction succeeds. /// Returns None unless the transaction is definitely one of our commitment transactions. fn check_spend_holder_transaction( - &mut self, tx: &Transaction, height: u32, block_hash: &BlockHash, logger: &L, + &mut self, commitment_txid: Txid, commitment_tx: &Transaction, height: u32, + block_hash: &BlockHash, logger: &L, ) -> Option<(Vec, TransactionOutputs)> where L::Target: Logger, { - let commitment_txid = tx.compute_txid(); - let mut claim_requests = Vec::new(); - let mut watch_outputs = Vec::new(); - - macro_rules! append_onchain_update { - ($updates: expr, $to_watch: expr) => { - claim_requests = $updates.0; - self.broadcasted_holder_revokable_script = $updates.1; - watch_outputs.append(&mut $to_watch); - }; - } + let funding_spent = get_confirmed_funding_scope!(self); // HTLCs set may differ between last and previous holder commitment txn, in case of one them hitting chain, ensure we cancel all HTLCs backward - let mut is_holder_tx = false; - - if self.funding.current_holder_commitment_tx.trust().txid() == commitment_txid { - is_holder_tx = true; - log_info!(logger, "Got broadcast of latest holder commitment tx {}, searching for available HTLCs to claim", commitment_txid); - let holder_commitment_tx = &self.funding.current_holder_commitment_tx; - let res = self.get_broadcasted_holder_claims(holder_commitment_tx, height); - let mut to_watch = self.get_broadcasted_holder_watch_outputs(holder_commitment_tx); - append_onchain_update!(res, to_watch); - fail_unbroadcast_htlcs!( - self, - "latest holder", - commitment_txid, - tx, - height, - block_hash, - holder_commitment_htlcs!(self, CURRENT_WITH_SOURCES), - logger - ); - } else if let Some(holder_commitment_tx) = &self.funding.prev_holder_commitment_tx { - if holder_commitment_tx.trust().txid() == commitment_txid { - is_holder_tx = true; - log_info!(logger, "Got broadcast of previous holder commitment tx {}, searching for available HTLCs to claim", commitment_txid); - let res = self.get_broadcasted_holder_claims(holder_commitment_tx, height); - let mut to_watch = self.get_broadcasted_holder_watch_outputs(holder_commitment_tx); - append_onchain_update!(res, to_watch); + let holder_commitment_tx = Some((&funding_spent.current_holder_commitment_tx, true)) + .filter(|(current_holder_commitment_tx, _)| { + current_holder_commitment_tx.trust().txid() == commitment_txid + }) + .or_else(|| { + funding_spent + .prev_holder_commitment_tx + .as_ref() + .map(|prev_holder_commitment_tx| (prev_holder_commitment_tx, false)) + .filter(|(prev_holder_commitment_tx, _)| { + prev_holder_commitment_tx.trust().txid() == commitment_txid + }) + }); + + if let Some((holder_commitment_tx, current)) = holder_commitment_tx { + let funding_txid_spent = commitment_tx.input[0].previous_output.txid; + assert_eq!(funding_spent.funding_txid(), funding_txid_spent); + + let current_msg = if current { "latest holder" } else { "previous holder" }; + log_info!(logger, "Got broadcast of {current_msg} commitment tx {commitment_txid}, searching for available HTLCs to claim"); + + let (claim_requests, broadcasted_holder_revokable_script) = + self.get_broadcasted_holder_claims(funding_spent, holder_commitment_tx, height); + self.broadcasted_holder_revokable_script = broadcasted_holder_revokable_script; + let watch_outputs = self.get_broadcasted_holder_watch_outputs(holder_commitment_tx); + + if current { fail_unbroadcast_htlcs!( self, - "previous holder", + current_msg, commitment_txid, - tx, + commitment_tx, + height, + block_hash, + holder_commitment_htlcs!(self, CURRENT_WITH_SOURCES), + logger + ); + } else { + fail_unbroadcast_htlcs!( + self, + current_msg, + commitment_txid, + commitment_tx, height, block_hash, holder_commitment_htlcs!(self, PREV_WITH_SOURCES).unwrap(), logger ); } - } - if is_holder_tx { Some((claim_requests, (commitment_txid, watch_outputs))) } else { None @@ -4680,45 +4831,63 @@ impl ChannelMonitorImpl { } // If we have generated claims for counterparty_commitment_txid earlier, we can rely on always // having claim related htlcs for counterparty_commitment_txid in counterparty_claimable_outpoints. - for (htlc, _) in self.funding.counterparty_claimable_outpoints.get(counterparty_commitment_txid).unwrap_or(&vec![]) { - log_trace!(logger, "Canceling claims for previously confirmed counterparty commitment {}", - counterparty_commitment_txid); - let mut outpoint = BitcoinOutPoint { txid: *counterparty_commitment_txid, vout: 0 }; - if let Some(vout) = htlc.transaction_output_index { - outpoint.vout = vout; - self.onchain_tx_handler.abandon_claim(&outpoint); + for funding in core::iter::once(&self.funding).chain(self.pending_funding.iter()) { + let mut found_claim = false; + for (htlc, _) in funding.counterparty_claimable_outpoints.get(counterparty_commitment_txid).unwrap_or(&vec![]) { + let mut outpoint = BitcoinOutPoint { txid: *counterparty_commitment_txid, vout: 0 }; + if let Some(vout) = htlc.transaction_output_index { + outpoint.vout = vout; + if self.onchain_tx_handler.abandon_claim(&outpoint) { + found_claim = true; + } + } + } + if found_claim { + log_trace!(logger, "Canceled claims for previously confirmed counterparty commitment with txid {counterparty_commitment_txid}"); } } } // Cancel any pending claims for any holder commitments in case they had previously // confirmed or been signed (in which case we will start attempting to claim without // waiting for confirmation). - if self.funding.current_holder_commitment_tx.trust().txid() != *confirmed_commitment_txid { - let txid = self.funding.current_holder_commitment_tx.trust().txid(); - log_trace!(logger, "Canceling claims for previously broadcast holder commitment {}", txid); - let mut outpoint = BitcoinOutPoint { txid, vout: 0 }; - for htlc in self.funding.current_holder_commitment_tx.nondust_htlcs() { - if let Some(vout) = htlc.transaction_output_index { - outpoint.vout = vout; - self.onchain_tx_handler.abandon_claim(&outpoint); - } else { - debug_assert!(false, "Expected transaction output index for non-dust HTLC"); - } - } - } - if let Some(prev_holder_commitment_tx) = &self.funding.prev_holder_commitment_tx { - let txid = prev_holder_commitment_tx.trust().txid(); - if txid != *confirmed_commitment_txid { - log_trace!(logger, "Canceling claims for previously broadcast holder commitment {}", txid); + for funding in core::iter::once(&self.funding).chain(self.pending_funding.iter()) { + if funding.current_holder_commitment_tx.trust().txid() != *confirmed_commitment_txid { + let mut found_claim = false; + let txid = funding.current_holder_commitment_tx.trust().txid(); let mut outpoint = BitcoinOutPoint { txid, vout: 0 }; - for htlc in prev_holder_commitment_tx.nondust_htlcs() { + for htlc in funding.current_holder_commitment_tx.nondust_htlcs() { if let Some(vout) = htlc.transaction_output_index { outpoint.vout = vout; - self.onchain_tx_handler.abandon_claim(&outpoint); + if self.onchain_tx_handler.abandon_claim(&outpoint) { + found_claim = true; + } } else { debug_assert!(false, "Expected transaction output index for non-dust HTLC"); } } + if found_claim { + log_trace!(logger, "Canceled claims for previously broadcast holder commitment with txid {txid}"); + } + } + if let Some(prev_holder_commitment_tx) = &funding.prev_holder_commitment_tx { + let txid = prev_holder_commitment_tx.trust().txid(); + if txid != *confirmed_commitment_txid { + let mut found_claim = false; + let mut outpoint = BitcoinOutPoint { txid, vout: 0 }; + for htlc in prev_holder_commitment_tx.nondust_htlcs() { + if let Some(vout) = htlc.transaction_output_index { + outpoint.vout = vout; + if self.onchain_tx_handler.abandon_claim(&outpoint) { + found_claim = true; + } + } else { + debug_assert!(false, "Expected transaction output index for non-dust HTLC"); + } + } + if found_claim { + log_trace!(logger, "Canceled claims for previously broadcast holder commitment with txid {txid}"); + } + } } } } @@ -4745,7 +4914,7 @@ impl ChannelMonitorImpl { return holder_transactions; } - self.get_broadcasted_holder_htlc_descriptors(&self.funding.current_holder_commitment_tx) + self.get_broadcasted_holder_htlc_descriptors(&self.funding, &self.funding.current_holder_commitment_tx) .into_iter() .for_each(|htlc_descriptor| { let txid = self.funding.current_holder_commitment_tx.trust().txid(); @@ -4806,7 +4975,7 @@ impl ChannelMonitorImpl { self.onchain_events_awaiting_threshold_conf.retain(|ref entry| entry.height <= height); let conf_target = self.closure_conf_target(); self.onchain_tx_handler.block_disconnected( - height + 1, broadcaster, conf_target, &self.destination_script, fee_estimator, logger, + height + 1, &broadcaster, conf_target, &self.destination_script, fee_estimator, logger, ); Vec::new() } else { Vec::new() } @@ -4839,6 +5008,13 @@ impl ChannelMonitorImpl { let block_hash = header.block_hash(); + // We may need to broadcast our holder commitment if we see a funding transaction reorg, + // with a different funding transaction confirming. It's possible we process a + // holder/counterparty commitment within this same block that would invalidate the one we're + // intending to broadcast, so we track whether we should broadcast and wait until all + // transactions in the block have been processed. + let mut should_broadcast_commitment = false; + let mut watch_outputs = Vec::new(); let mut claimable_outpoints = Vec::new(); 'tx_iter: for tx in &txn_matched { @@ -4873,21 +5049,107 @@ impl ChannelMonitorImpl { } } + // A splice/dual-funded RBF transaction has confirmed. We can't promote the + // `FundingScope` scope until we see the + // [`ChannelMonitorUpdateStep::RenegotiatedFundingLocked`] for it, but we track the txid + // so we know which holder commitment transaction we may need to broadcast. + if let Some(alternative_funding) = self + .pending_funding + .iter() + .find(|funding| funding.funding_txid() == txid) + { + assert!(self.alternative_funding_confirmed.is_none()); + assert!( + !self.onchain_events_awaiting_threshold_conf.iter() + .any(|e| matches!(e.event, OnchainEvent::AlternativeFundingConfirmation {})) + ); + assert!(self.funding_spend_confirmed.is_none()); + assert!( + !self.onchain_events_awaiting_threshold_conf.iter() + .any(|e| matches!(e.event, OnchainEvent::FundingSpendConfirmation { .. })) + ); + + let (desc, msg) = if alternative_funding.is_splice() { + debug_assert!(tx.input.iter().any(|input| { + let funding_outpoint = self.funding.funding_outpoint().into_bitcoin_outpoint(); + input.previous_output == funding_outpoint + })); + ("Splice", "splice_locked") + } else { + ("Dual-funded RBF", "channel_ready") + }; + let action = if self.holder_tx_signed || self.funding_spend_seen { + ", broadcasting holder commitment transaction".to_string() + } else if !self.no_further_updates_allowed() { + format!(", waiting for `{msg}` exchange") + } else { + "".to_string() + }; + log_info!(logger, "{desc} for channel {} confirmed with txid {txid}{action}", self.channel_id()); + + self.alternative_funding_confirmed = Some((txid, height)); + + if self.no_further_updates_allowed() { + // We can no longer rely on + // [`ChannelMonitorUpdateStep::RenegotiatedFundingLocked`] to promote the + // scope; do so when the funding is no longer under reorg risk. + self.onchain_events_awaiting_threshold_conf.push(OnchainEventEntry { + txid, + transaction: Some((*tx).clone()), + height, + block_hash: Some(block_hash), + event: OnchainEvent::AlternativeFundingConfirmation {}, + }); + } + + if self.holder_tx_signed || self.funding_spend_seen { + // Cancel any previous claims that are no longer valid as they stemmed from a + // different funding transaction. + let new_holder_commitment_txid = + alternative_funding.current_holder_commitment_tx.trust().txid(); + self.cancel_prev_commitment_claims(&logger, &new_holder_commitment_txid); + + // We either attempted to broadcast a holder commitment, or saw one confirm + // onchain, so broadcast the new holder commitment for the confirmed funding to + // claim our funds as the channel is no longer operational. + should_broadcast_commitment = true; + } + + continue 'tx_iter; + } + if tx.input.len() == 1 { // Assuming our keys were not leaked (in which case we're screwed no matter what), // commitment transactions and HTLC transactions will all only ever have one input // (except for HTLC transactions for channels with anchor outputs), which is an easy // way to filter out any potential non-matching txn for lazy filters. - let prevout = &tx.input[0].previous_output; - let funding_outpoint = self.get_funding_txo(); - if prevout.txid == funding_outpoint.txid && prevout.vout == funding_outpoint.index as u32 { - let mut balance_spendable_csv = None; - log_info!(logger, "Channel {} closed by funding output spend in txid {}.", - &self.channel_id(), txid); + if let Some(funding_txid_spent) = core::iter::once(&self.funding) + .chain(self.pending_funding.iter()) + .find(|funding| { + let funding_outpoint = funding.funding_outpoint().into_bitcoin_outpoint(); + funding_outpoint == tx.input[0].previous_output + }) + .map(|funding| funding.funding_txid()) + { + assert_eq!( + funding_txid_spent, + self.alternative_funding_confirmed + .map(|(txid, _)| txid) + .unwrap_or_else(|| self.funding.funding_txid()) + ); + log_info!(logger, "Channel {} closed by funding output spend in txid {txid}", + self.channel_id()); + if !self.funding_spend_seen { + self.pending_monitor_events.push(MonitorEvent::CommitmentTxConfirmed(())); + } self.funding_spend_seen = true; + + let mut balance_spendable_csv = None; let mut commitment_tx_to_counterparty_output = None; + + // Is it a commitment transaction? if (tx.input[0].sequence.0 >> 8*3) as u8 == 0x80 && (tx.lock_time.to_consensus_u32() >> 8*3) as u8 == 0x20 { - if let Some((mut new_outpoints, new_outputs)) = self.check_spend_holder_transaction(&tx, height, &block_hash, &logger) { + if let Some((mut new_outpoints, new_outputs)) = self.check_spend_holder_transaction(txid, &tx, height, &block_hash, &logger) { if !new_outputs.1.is_empty() { watch_outputs.push(new_outputs); } @@ -4902,12 +5164,20 @@ impl ChannelMonitorImpl { watch_outputs.push((txid, new_watch_outputs)); let (mut new_outpoints, counterparty_output_idx_sats) = - self.check_spend_counterparty_transaction(&tx, height, &block_hash, &logger); + self.check_spend_counterparty_transaction(txid, &tx, height, &block_hash, &logger); commitment_tx_to_counterparty_output = counterparty_output_idx_sats; claimable_outpoints.append(&mut new_outpoints); } + + // We've just seen a commitment confirm, which conflicts with the holder + // commitment we intend to broadcast + if should_broadcast_commitment { + log_info!(logger, "Canceling our queued holder commitment broadcast as we've found a conflict confirm instead"); + should_broadcast_commitment = false; + } } + self.onchain_events_awaiting_threshold_conf.push(OnchainEventEntry { txid, transaction: Some((*tx).clone()), @@ -4918,6 +5188,7 @@ impl ChannelMonitorImpl { commitment_tx_to_counterparty_output, }, }); + // Now that we've detected a confirmed commitment transaction, attempt to cancel // pending claims for any commitments that were previously confirmed such that // we don't continue claiming inputs that no longer exist. @@ -4955,6 +5226,13 @@ impl ChannelMonitorImpl { self.best_block = BestBlock::new(block_hash, height); } + if should_broadcast_commitment { + let (mut claimables, mut outputs) = + self.generate_claimable_outpoints_and_watch_outputs(None); + claimable_outpoints.append(&mut claimables); + watch_outputs.append(&mut outputs); + } + self.block_confirmed(height, block_hash, txn_matched, watch_outputs, claimable_outpoints, &broadcaster, &fee_estimator, logger) } @@ -4987,8 +5265,10 @@ impl ChannelMonitorImpl { debug_assert!(self.best_block.height >= conf_height); let should_broadcast = self.should_broadcast_holder_commitment_txn(logger); - if should_broadcast { - let (mut new_outpoints, mut new_outputs) = self.generate_claimable_outpoints_and_watch_outputs(ClosureReason::HTLCsTimedOut); + if let Some(payment_hash) = should_broadcast { + let reason = ClosureReason::HTLCsTimedOut { payment_hash: Some(payment_hash) }; + let (mut new_outpoints, mut new_outputs) = + self.generate_claimable_outpoints_and_watch_outputs(Some(reason)); claimable_outpoints.append(&mut new_outpoints); watch_outputs.append(&mut new_outputs); } @@ -5004,7 +5284,7 @@ impl ChannelMonitorImpl { let unmatured_htlcs: Vec<_> = self.onchain_events_awaiting_threshold_conf .iter() .filter_map(|entry| match &entry.event { - OnchainEvent::HTLCUpdate { source, .. } => Some(source), + OnchainEvent::HTLCUpdate { source, .. } => Some(source.clone()), _ => None, }) .collect(); @@ -5019,7 +5299,7 @@ impl ChannelMonitorImpl { #[cfg(debug_assertions)] { debug_assert!( - !unmatured_htlcs.contains(&&source), + !unmatured_htlcs.contains(&source), "An unmature HTLC transaction conflicts with a maturing one; failed to \ call either transaction_unconfirmed for the conflicting transaction \ or block_disconnected for a block containing it."); @@ -5066,6 +5346,16 @@ impl ChannelMonitorImpl { self.funding_spend_confirmed = Some(entry.txid); self.confirmed_commitment_tx_counterparty_output = commitment_tx_to_counterparty_output; }, + OnchainEvent::AlternativeFundingConfirmation {} => { + // An alternative funding transaction has irrevocably confirmed and we're no + // longer allowing monitor updates, so promote the `FundingScope` now. + debug_assert!(self.no_further_updates_allowed()); + debug_assert_ne!(self.funding.funding_txid(), entry.txid); + if let Err(_) = self.promote_funding(entry.txid) { + debug_assert!(false); + log_error!(logger, "Missing scope for alternative funding confirmation with txid {}", entry.txid); + } + }, } } @@ -5178,12 +5468,35 @@ impl ChannelMonitorImpl { //- maturing spendable output has transaction paying us has been disconnected self.onchain_events_awaiting_threshold_conf.retain(|ref entry| entry.height < height); + // TODO: Replace with `take_if` once our MSRV is >= 1.80. + let mut should_broadcast_commitment = false; + if let Some((_, conf_height)) = self.alternative_funding_confirmed.as_ref() { + if *conf_height == height { + self.alternative_funding_confirmed.take(); + if self.holder_tx_signed || self.funding_spend_seen { + // Cancel any previous claims that are no longer valid as they stemmed from a + // different funding transaction. + let new_holder_commitment_txid = + self.funding.current_holder_commitment_tx.trust().txid(); + self.cancel_prev_commitment_claims(&logger, &new_holder_commitment_txid); + + should_broadcast_commitment = true; + } + } + } + let bounded_fee_estimator = LowerBoundedFeeEstimator::new(fee_estimator); let conf_target = self.closure_conf_target(); self.onchain_tx_handler.block_disconnected( - height, broadcaster, conf_target, &self.destination_script, &bounded_fee_estimator, logger + height, &broadcaster, conf_target, &self.destination_script, &bounded_fee_estimator, logger ); + // Only attempt to broadcast the new commitment after the `block_disconnected` call above so that + // it doesn't get removed from the set of pending claims. + if should_broadcast_commitment { + self.queue_latest_holder_commitment_txn_for_broadcast(&broadcaster, &bounded_fee_estimator, logger); + } + self.best_block = BestBlock::new(header.prev_blockhash, height - 1); } @@ -5217,10 +5530,33 @@ impl ChannelMonitorImpl { debug_assert!(!self.onchain_events_awaiting_threshold_conf.iter().any(|ref entry| entry.txid == *txid)); + // TODO: Replace with `take_if` once our MSRV is >= 1.80. + let mut should_broadcast_commitment = false; + if let Some((alternative_funding_txid, _)) = self.alternative_funding_confirmed.as_ref() { + if alternative_funding_txid == txid { + self.alternative_funding_confirmed.take(); + if self.holder_tx_signed || self.funding_spend_seen { + // Cancel any previous claims that are no longer valid as they stemmed from a + // different funding transaction. + let new_holder_commitment_txid = + self.funding.current_holder_commitment_tx.trust().txid(); + self.cancel_prev_commitment_claims(&logger, &new_holder_commitment_txid); + + should_broadcast_commitment = true; + } + } + } + let conf_target = self.closure_conf_target(); self.onchain_tx_handler.transaction_unconfirmed( - txid, broadcaster, conf_target, &self.destination_script, fee_estimator, logger + txid, &broadcaster, conf_target, &self.destination_script, fee_estimator, logger ); + + // Only attempt to broadcast the new commitment after the `transaction_unconfirmed` call above so + // that it doesn't get removed from the set of pending claims. + if should_broadcast_commitment { + self.queue_latest_holder_commitment_txn_for_broadcast(&broadcaster, fee_estimator, logger); + } } /// Filters a block's `txdata` for transactions spending watched outputs or for any child @@ -5280,7 +5616,7 @@ impl ChannelMonitorImpl { #[rustfmt::skip] fn should_broadcast_holder_commitment_txn( &self, logger: &WithChannelMonitor - ) -> bool where L::Target: Logger { + ) -> Option where L::Target: Logger { // There's no need to broadcast our commitment transaction if we've seen one confirmed (even // with 1 confirmation) as it'll be rejected as duplicate/conflicting. if self.funding_spend_confirmed.is_some() || @@ -5289,7 +5625,7 @@ impl ChannelMonitorImpl { _ => false, }).is_some() { - return false; + return None; } // We need to consider all HTLCs which are: // * in any unrevoked counterparty commitment transaction, as they could broadcast said @@ -5319,8 +5655,8 @@ impl ChannelMonitorImpl { let htlc_outbound = $holder_tx == htlc.offered; if ( htlc_outbound && htlc.cltv_expiry + LATENCY_GRACE_PERIOD_BLOCKS <= height) || (!htlc_outbound && htlc.cltv_expiry <= height + CLTV_CLAIM_BUFFER && self.payment_preimages.contains_key(&htlc.payment_hash)) { - log_info!(logger, "Force-closing channel due to {} HTLC timeout, HTLC expiry is {}", if htlc_outbound { "outbound" } else { "inbound "}, htlc.cltv_expiry); - return true; + log_info!(logger, "Force-closing channel due to {} HTLC timeout - HTLC with payment hash {} expires at {}", if htlc_outbound { "outbound" } else { "inbound"}, htlc.payment_hash, htlc.cltv_expiry); + return Some(htlc.payment_hash); } } } @@ -5339,7 +5675,7 @@ impl ChannelMonitorImpl { } } - false + None } /// Check if any transaction broadcasted is resolving HTLC output by a success or timeout on a holder @@ -5348,6 +5684,8 @@ impl ChannelMonitorImpl { fn is_resolving_htlc_output( &mut self, tx: &Transaction, height: u32, block_hash: &BlockHash, logger: &WithChannelMonitor, ) where L::Target: Logger { + let funding_spent = get_confirmed_funding_scope!(self); + 'outer_loop: for input in &tx.input { let mut payment_data = None; let htlc_claim = HTLCClaim::from_witness(&input.witness); @@ -5409,7 +5747,7 @@ impl ChannelMonitorImpl { } macro_rules! scan_commitment { - ($htlcs: expr, $tx_info: expr, $holder_tx: expr) => { + ($funding_spent: expr, $htlcs: expr, $tx_info: expr, $holder_tx: expr) => { for (ref htlc_output, source_option) in $htlcs { if Some(input.previous_output.vout) == htlc_output.transaction_output_index { if let Some(ref source) = source_option { @@ -5421,12 +5759,12 @@ impl ChannelMonitorImpl { // resolve the source HTLC with the original sender. payment_data = Some(((*source).clone(), htlc_output.payment_hash, htlc_output.amount_msat)); } else if !$holder_tx { - if let Some(current_counterparty_commitment_txid) = &self.funding.current_counterparty_commitment_txid { - check_htlc_valid_counterparty!(htlc_output, self.funding.counterparty_claimable_outpoints.get(current_counterparty_commitment_txid).unwrap()); + if let Some(current_counterparty_commitment_txid) = &$funding_spent.current_counterparty_commitment_txid { + check_htlc_valid_counterparty!(htlc_output, $funding_spent.counterparty_claimable_outpoints.get(current_counterparty_commitment_txid).unwrap()); } if payment_data.is_none() { - if let Some(prev_counterparty_commitment_txid) = &self.funding.prev_counterparty_commitment_txid { - check_htlc_valid_counterparty!(htlc_output, self.funding.counterparty_claimable_outpoints.get(prev_counterparty_commitment_txid).unwrap()); + if let Some(prev_counterparty_commitment_txid) = &$funding_spent.prev_counterparty_commitment_txid { + check_htlc_valid_counterparty!(htlc_output, $funding_spent.counterparty_claimable_outpoints.get(prev_counterparty_commitment_txid).unwrap()); } } } @@ -5454,23 +5792,24 @@ impl ChannelMonitorImpl { } } - if input.previous_output.txid == self.funding.current_holder_commitment_tx.trust().txid() { + if input.previous_output.txid == funding_spent.current_holder_commitment_tx.trust().txid() { scan_commitment!( - holder_commitment_htlcs!(self, CURRENT_WITH_SOURCES), + funding_spent, holder_commitment_htlcs!(self, CURRENT_WITH_SOURCES), "our latest holder commitment tx", true ); } - if let Some(prev_holder_commitment_tx) = self.funding.prev_holder_commitment_tx.as_ref() { + if let Some(prev_holder_commitment_tx) = funding_spent.prev_holder_commitment_tx.as_ref() { if input.previous_output.txid == prev_holder_commitment_tx.trust().txid() { scan_commitment!( - holder_commitment_htlcs!(self, PREV_WITH_SOURCES).unwrap(), + funding_spent, holder_commitment_htlcs!(self, PREV_WITH_SOURCES).unwrap(), "our previous holder commitment tx", true ); } } - if let Some(ref htlc_outputs) = self.funding.counterparty_claimable_outpoints.get(&input.previous_output.txid) { - scan_commitment!(htlc_outputs.iter().map(|&(ref a, ref b)| (a, b.as_ref().map(|boxed| &**boxed))), - "counterparty commitment tx", false); + if let Some(ref htlc_outputs) = funding_spent.counterparty_claimable_outpoints.get(&input.previous_output.txid) { + let htlcs = htlc_outputs.iter() + .map(|&(ref a, ref b)| (a, b.as_ref().map(|boxed| &**boxed))); + scan_commitment!(funding_spent, htlcs, "counterparty commitment tx", false); } // Check that scan_commitment, above, decided there is some source worth relaying an @@ -5550,7 +5889,7 @@ impl ChannelMonitorImpl { } #[rustfmt::skip] - fn get_spendable_outputs(&self, tx: &Transaction) -> Vec { + fn get_spendable_outputs(&self, funding_spent: &FundingScope, tx: &Transaction) -> Vec { let mut spendable_outputs = Vec::new(); for (i, outp) in tx.output.iter().enumerate() { if outp.script_pubkey == self.destination_script { @@ -5569,8 +5908,8 @@ impl ChannelMonitorImpl { output: outp.clone(), revocation_pubkey: broadcasted_holder_revokable_script.2, channel_keys_id: self.channel_keys_id, - channel_value_satoshis: self.funding.channel_parameters.channel_value_satoshis, - channel_transaction_parameters: Some(self.funding.channel_parameters.clone()), + channel_value_satoshis: funding_spent.channel_parameters.channel_value_satoshis, + channel_transaction_parameters: Some(funding_spent.channel_parameters.clone()), })); } } @@ -5579,8 +5918,8 @@ impl ChannelMonitorImpl { outpoint: OutPoint { txid: tx.compute_txid(), index: i as u16 }, output: outp.clone(), channel_keys_id: self.channel_keys_id, - channel_value_satoshis: self.funding.channel_parameters.channel_value_satoshis, - channel_transaction_parameters: Some(self.funding.channel_parameters.clone()), + channel_value_satoshis: funding_spent.channel_parameters.channel_value_satoshis, + channel_transaction_parameters: Some(funding_spent.channel_parameters.clone()), })); } if self.shutdown_script.as_ref() == Some(&outp.script_pubkey) { @@ -5600,7 +5939,8 @@ impl ChannelMonitorImpl { fn check_tx_and_push_spendable_outputs( &mut self, tx: &Transaction, height: u32, block_hash: &BlockHash, logger: &WithChannelMonitor, ) where L::Target: Logger { - for spendable_output in self.get_spendable_outputs(tx) { + let funding_spent = get_confirmed_funding_scope!(self); + for spendable_output in self.get_spendable_outputs(funding_spent, tx) { let entry = OnchainEventEntry { txid: tx.compute_txid(), transaction: Some(tx.clone()), @@ -5870,6 +6210,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP let mut first_negotiated_funding_txo = RequiredWrapper(None); let mut channel_parameters = None; let mut pending_funding = None; + let mut alternative_funding_confirmed = None; read_tlv_fields!(reader, { (1, funding_spend_confirmed, option), (3, htlcs_resolved_on_chain, optional_vec), @@ -5888,6 +6229,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP (29, initial_counterparty_commitment_tx, option), (31, channel_parameters, (option: ReadableArgs, None)), (32, pending_funding, optional_vec), + (34, alternative_funding_confirmed, option), }); if let Some(payment_preimages_with_info) = payment_preimages_with_info { if payment_preimages_with_info.len() != payment_preimages.len() { @@ -6057,6 +6399,8 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP current_holder_htlc_data, prev_holder_htlc_data, + + alternative_funding_confirmed, }))) } } @@ -6081,6 +6425,7 @@ mod tests { use bitcoin::{Sequence, Witness}; use crate::chain::chaininterface::LowerBoundedFeeEstimator; + use crate::events::ClosureReason; use super::ChannelMonitorUpdateStep; use crate::chain::channelmonitor::{ChannelMonitor, WithChannelMonitor}; @@ -6178,22 +6523,26 @@ mod tests { // Build a new ChannelMonitorUpdate which contains both the failing commitment tx update // and provides the claim preimages for the two pending HTLCs. The first update generates // an error, but the point of this test is to ensure the later updates are still applied. - let monitor_updates = nodes[1].chain_monitor.monitor_updates.lock().unwrap(); - let mut replay_update = monitor_updates.get(&channel.2).unwrap().iter().next_back().unwrap().clone(); - assert_eq!(replay_update.updates.len(), 1); - if let ChannelMonitorUpdateStep::LatestCounterpartyCommitmentTXInfo { .. } = replay_update.updates[0] { - } else { panic!(); } - replay_update.updates.push(ChannelMonitorUpdateStep::PaymentPreimage { - payment_preimage: payment_preimage_1, payment_info: None, - }); - replay_update.updates.push(ChannelMonitorUpdateStep::PaymentPreimage { - payment_preimage: payment_preimage_2, payment_info: None, - }); + let replay_update = { + let monitor_updates = nodes[1].chain_monitor.monitor_updates.lock().unwrap(); + let mut replay_update = monitor_updates.get(&channel.2).unwrap().iter().next_back().unwrap().clone(); + assert_eq!(replay_update.updates.len(), 1); + if let ChannelMonitorUpdateStep::LatestCounterpartyCommitmentTXInfo { .. } = replay_update.updates[0] { + } else { panic!(); } + replay_update.updates.push(ChannelMonitorUpdateStep::PaymentPreimage { + payment_preimage: payment_preimage_1, payment_info: None, + }); + replay_update.updates.push(ChannelMonitorUpdateStep::PaymentPreimage { + payment_preimage: payment_preimage_2, payment_info: None, + }); + replay_update + }; let broadcaster = TestBroadcaster::with_blocks(Arc::clone(&nodes[1].blocks)); assert!( pre_update_monitor.update_monitor(&replay_update, &&broadcaster, &&chanmon_cfgs[1].fee_estimator, &nodes[1].logger) .is_err()); + // Even though we error'd on the first update, we should still have generated an HTLC claim // transaction let txn_broadcasted = broadcaster.txn_broadcasted.lock().unwrap().split_off(0); @@ -6205,7 +6554,12 @@ mod tests { assert_eq!(htlc_txn.len(), 2); check_spends!(htlc_txn[0], broadcast_tx); check_spends!(htlc_txn[1], broadcast_tx); + + check_closed_broadcast(&nodes[1], 1, true); + check_closed_event(&nodes[1], 1, ClosureReason::CommitmentTxConfirmed, false, &[nodes[0].node.get_our_node_id()], 100000); + check_added_monitors(&nodes[1], 1); } + #[test] fn test_funding_spend_refuses_updates() { do_test_funding_spend_refuses_updates(true); diff --git a/lightning/src/chain/onchaintx.rs b/lightning/src/chain/onchaintx.rs index f7c2bac3a39..deb1282f1f3 100644 --- a/lightning/src/chain/onchaintx.rs +++ b/lightning/src/chain/onchaintx.rs @@ -23,7 +23,7 @@ use bitcoin::secp256k1::{ecdsa::Signature, Secp256k1}; use bitcoin::transaction::OutPoint as BitcoinOutPoint; use bitcoin::transaction::Transaction; -use crate::chain::chaininterface::{compute_feerate_sat_per_1000_weight, ConfirmationTarget}; +use crate::chain::chaininterface::ConfirmationTarget; use crate::chain::chaininterface::{BroadcasterInterface, FeeEstimator, LowerBoundedFeeEstimator}; use crate::chain::channelmonitor::ANTI_REORG_DELAY; use crate::chain::package::{PackageSolvingData, PackageTemplate}; @@ -670,19 +670,8 @@ impl OnchainTxHandler { let fee_sat = input_amount_sats - tx.output.iter() .map(|output| output.value.to_sat()).sum::(); - let commitment_tx_feerate_sat_per_1000_weight = - compute_feerate_sat_per_1000_weight(fee_sat, tx.weight().to_wu()); let package_target_feerate_sat_per_1000_weight = cached_request .compute_package_feerate(fee_estimator, conf_target, feerate_strategy); - if commitment_tx_feerate_sat_per_1000_weight >= package_target_feerate_sat_per_1000_weight { - log_debug!(logger, "Pre-signed commitment {} already has feerate {} sat/kW above required {} sat/kW", - tx.compute_txid(), commitment_tx_feerate_sat_per_1000_weight, - package_target_feerate_sat_per_1000_weight); - // The commitment transaction already meets the required feerate and doesn't - // need a CPFP. We still want to return something other than the event to - // register the claim. - return Some((new_timer, 0, OnchainClaim::Tx(MaybeSignedTransaction(tx)))); - } // We'll locate an anchor output we can spend within the commitment transaction. let channel_parameters = output.channel_parameters.as_ref() @@ -723,7 +712,8 @@ impl OnchainTxHandler { } #[rustfmt::skip] - pub fn abandon_claim(&mut self, outpoint: &BitcoinOutPoint) { + pub fn abandon_claim(&mut self, outpoint: &BitcoinOutPoint) -> bool { + let mut found_claim = false; let claim_id = self.claimable_outpoints.get(outpoint).map(|(claim_id, _)| *claim_id) .or_else(|| { self.pending_claim_requests.iter() @@ -733,13 +723,23 @@ impl OnchainTxHandler { if let Some(claim_id) = claim_id { if let Some(claim) = self.pending_claim_requests.remove(&claim_id) { for outpoint in claim.outpoints() { - self.claimable_outpoints.remove(outpoint); + if self.claimable_outpoints.remove(outpoint).is_some() { + found_claim = true; + } } } } else { - self.locktimed_packages.values_mut().for_each(|claims| - claims.retain(|claim| !claim.outpoints().contains(&outpoint))); + self.locktimed_packages.values_mut().for_each(|claims| { + claims.retain(|claim| { + let includes_outpoint = claim.outpoints().contains(&outpoint); + if includes_outpoint { + found_claim = true; + } + !includes_outpoint + }) + }); } + found_claim } /// Upon channelmonitor.block_connected(..) or upon provision of a preimage on the forward link @@ -1109,7 +1109,7 @@ impl OnchainTxHandler { pub(super) fn transaction_unconfirmed( &mut self, txid: &Txid, - broadcaster: B, + broadcaster: &B, conf_target: ConfirmationTarget, destination_script: &Script, fee_estimator: &LowerBoundedFeeEstimator, @@ -1135,7 +1135,7 @@ impl OnchainTxHandler { #[rustfmt::skip] pub(super) fn block_disconnected( - &mut self, height: u32, broadcaster: B, conf_target: ConfirmationTarget, + &mut self, height: u32, broadcaster: &B, conf_target: ConfirmationTarget, destination_script: &Script, fee_estimator: &LowerBoundedFeeEstimator, logger: &L, ) where B::Target: BroadcasterInterface, diff --git a/lightning/src/events/bump_transaction/mod.rs b/lightning/src/events/bump_transaction/mod.rs index b89d2a7fc25..6f127691f21 100644 --- a/lightning/src/events/bump_transaction/mod.rs +++ b/lightning/src/events/bump_transaction/mod.rs @@ -16,7 +16,9 @@ pub mod sync; use alloc::collections::BTreeMap; use core::ops::Deref; -use crate::chain::chaininterface::{fee_for_weight, BroadcasterInterface}; +use crate::chain::chaininterface::{ + compute_feerate_sat_per_1000_weight, fee_for_weight, BroadcasterInterface, +}; use crate::chain::ClaimId; use crate::io_extras::sink; use crate::ln::chan_utils; @@ -123,7 +125,9 @@ pub enum BumpTransactionEvent { /// and child anchor transactions), possibly resulting in a loss of funds. Once the transaction /// is constructed, it must be fully signed for and broadcast by the consumer of the event /// along with the `commitment_tx` enclosed. Note that the `commitment_tx` must always be - /// broadcast first, as the child anchor transaction depends on it. + /// broadcast first, as the child anchor transaction depends on it. It is also possible that the + /// feerate of the commitment transaction is already sufficient, in which case the child anchor + /// transaction is not needed and only the commitment transaction should be broadcast. /// /// The consumer should be able to sign for any of the additional inputs included within the /// child anchor transaction. To sign its anchor input, an [`EcdsaChannelSigner`] should be @@ -658,6 +662,19 @@ where commitment_tx: &Transaction, commitment_tx_fee_sat: u64, anchor_descriptor: &AnchorDescriptor, ) -> Result<(), ()> { + // First, check if the commitment transaction has sufficient fees on its own. + let commitment_tx_feerate_sat_per_1000_weight = compute_feerate_sat_per_1000_weight( + commitment_tx_fee_sat, + commitment_tx.weight().to_wu(), + ); + if commitment_tx_feerate_sat_per_1000_weight >= package_target_feerate_sat_per_1000_weight { + log_debug!(self.logger, "Pre-signed commitment {} already has feerate {} sat/kW above required {} sat/kW, broadcasting.", + commitment_tx.compute_txid(), commitment_tx_feerate_sat_per_1000_weight, + package_target_feerate_sat_per_1000_weight); + self.broadcaster.broadcast_transactions(&[&commitment_tx]); + return Ok(()); + } + // Our commitment transaction already has fees allocated to it, so we should take them into // account. We do so by pretending the commitment transaction's fee and weight are part of // the anchor input. diff --git a/lightning/src/events/mod.rs b/lightning/src/events/mod.rs index 2b712073568..30c928297a8 100644 --- a/lightning/src/events/mod.rs +++ b/lightning/src/events/mod.rs @@ -233,6 +233,11 @@ impl_writeable_tlv_based_enum_legacy!(PaymentPurpose, /// Information about an HTLC that is part of a payment that can be claimed. #[derive(Clone, Debug, PartialEq, Eq)] pub struct ClaimedHTLC { + /// The counterparty of the channel. + /// + /// This value will always be `None` for objects serialized with LDK versions prior to 0.2 and + /// `Some` otherwise. + pub counterparty_node_id: Option, /// The `channel_id` of the channel over which the HTLC was received. pub channel_id: ChannelId, /// The `user_channel_id` of the channel over which the HTLC was received. This is the value @@ -263,6 +268,7 @@ impl_writeable_tlv_based!(ClaimedHTLC, { (0, channel_id, required), (1, counterparty_skimmed_fee_msat, (default_value, 0u64)), (2, user_channel_id, required), + (3, counterparty_node_id, option), (4, cltv_expiry, required), (6, value_msat, required), }); @@ -400,7 +406,12 @@ pub enum ClosureReason { /// was ready to be broadcast. FundingBatchClosure, /// One of our HTLCs timed out in a channel, causing us to force close the channel. - HTLCsTimedOut, + HTLCsTimedOut { + /// The payment hash of an HTLC that timed out. + /// + /// Will be `None` for any event serialized by LDK prior to 0.2. + payment_hash: Option, + }, /// Our peer provided a feerate which violated our required minimum (fetched from our /// [`FeeEstimator`] either as [`ConfirmationTarget::MinAllowedAnchorChannelRemoteFee`] or /// [`ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee`]). @@ -474,7 +485,12 @@ impl core::fmt::Display for ClosureReason { ClosureReason::FundingBatchClosure => { f.write_str("another channel in the same funding batch closed") }, - ClosureReason::HTLCsTimedOut => f.write_str("htlcs on the channel timed out"), + ClosureReason::HTLCsTimedOut { payment_hash: Some(hash) } => f.write_fmt(format_args!( + "HTLC(s) on the channel timed out (including the HTLC with payment hash {hash})", + )), + ClosureReason::HTLCsTimedOut { payment_hash: None } => { + f.write_fmt(format_args!("HTLC(s) on the channel timed out")) + }, ClosureReason::PeerFeerateTooLow { peer_feerate_sat_per_kw, required_feerate_sat_per_kw, @@ -502,7 +518,9 @@ impl_writeable_tlv_based_enum_upgradable!(ClosureReason, (15, FundingBatchClosure) => {}, (17, CounterpartyInitiatedCooperativeClosure) => {}, (19, LocallyInitiatedCooperativeClosure) => {}, - (21, HTLCsTimedOut) => {}, + (21, HTLCsTimedOut) => { + (1, payment_hash, option), + }, (23, PeerFeerateTooLow) => { (0, peer_feerate_sat_per_kw, required), (2, required_feerate_sat_per_kw, required), @@ -1628,7 +1646,6 @@ pub enum Event { /// /// [`ChannelManager::blinded_paths_for_async_recipient`]: crate::ln::channelmanager::ChannelManager::blinded_paths_for_async_recipient /// [`ChannelManager::set_paths_to_static_invoice_server`]: crate::ln::channelmanager::ChannelManager::set_paths_to_static_invoice_server - #[cfg(async_payments)] PersistStaticInvoice { /// The invoice that should be persisted and later provided to payers when handling a future /// [`Event::StaticInvoiceRequested`]. @@ -1644,16 +1661,11 @@ pub enum Event { /// [`ChannelManager::blinded_paths_for_async_recipient`]. /// /// When an [`Event::StaticInvoiceRequested`] comes in for the invoice, this id will be surfaced - /// and can be used alongside the `invoice_id` to retrieve the invoice from the database. + /// and can be used alongside the `invoice_slot` to retrieve the invoice from the database. + /// + ///[`ChannelManager::blinded_paths_for_async_recipient`]: crate::ln::channelmanager::ChannelManager::blinded_paths_for_async_recipient recipient_id: Vec, - /// A random identifier for the invoice. When an [`Event::StaticInvoiceRequested`] comes in for - /// the invoice, this id will be surfaced and can be used alongside the `recipient_id` to - /// retrieve the invoice from the database. - /// - /// Note that this id will remain the same for all invoice updates corresponding to a particular - /// offer that the recipient has cached. - invoice_id: u128, - /// Once the [`StaticInvoice`], `invoice_slot` and `invoice_id` are persisted, + /// Once the [`StaticInvoice`] and `invoice_slot` are persisted, /// [`ChannelManager::static_invoice_persisted`] should be called with this responder to confirm /// to the recipient that their [`Offer`] is ready to be used for async payments. /// @@ -1669,29 +1681,74 @@ pub enum Event { /// them via [`ChannelManager::set_paths_to_static_invoice_server`]. /// /// If we previously persisted a [`StaticInvoice`] from an [`Event::PersistStaticInvoice`] that - /// matches the below `recipient_id` and `invoice_id`, that invoice should be retrieved now + /// matches the below `recipient_id` and `invoice_slot`, that invoice should be retrieved now /// and forwarded to the payer via [`ChannelManager::send_static_invoice`]. /// /// [`ChannelManager::blinded_paths_for_async_recipient`]: crate::ln::channelmanager::ChannelManager::blinded_paths_for_async_recipient /// [`ChannelManager::set_paths_to_static_invoice_server`]: crate::ln::channelmanager::ChannelManager::set_paths_to_static_invoice_server /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest /// [`ChannelManager::send_static_invoice`]: crate::ln::channelmanager::ChannelManager::send_static_invoice - #[cfg(async_payments)] StaticInvoiceRequested { /// An identifier for the recipient previously surfaced in - /// [`Event::PersistStaticInvoice::recipient_id`]. Useful when paired with the `invoice_id` to + /// [`Event::PersistStaticInvoice::recipient_id`]. Useful when paired with the `invoice_slot` to /// retrieve the [`StaticInvoice`] requested by the payer. recipient_id: Vec, - /// A random identifier for the invoice being requested, previously surfaced in - /// [`Event::PersistStaticInvoice::invoice_id`]. Useful when paired with the `recipient_id` to + /// The slot number for the invoice being requested, previously surfaced in + /// [`Event::PersistStaticInvoice::invoice_slot`]. Useful when paired with the `recipient_id` to /// retrieve the [`StaticInvoice`] requested by the payer. - invoice_id: u128, + invoice_slot: u16, /// The path over which the [`StaticInvoice`] will be sent to the payer, which should be /// provided to [`ChannelManager::send_static_invoice`] along with the invoice. /// /// [`ChannelManager::send_static_invoice`]: crate::ln::channelmanager::ChannelManager::send_static_invoice reply_path: Responder, }, + /// Indicates that a channel funding transaction constructed interactively is ready to be + /// signed. This event will only be triggered if at least one input was contributed. + /// + /// The transaction contains all inputs and outputs provided by both parties including the + /// channel's funding output and a change output if applicable. + /// + /// No part of the transaction should be changed before signing as the content of the transaction + /// has already been negotiated with the counterparty. + /// + /// Each signature MUST use the `SIGHASH_ALL` flag to avoid invalidation of the initial commitment and + /// hence possible loss of funds. + /// + /// After signing, call [`ChannelManager::funding_transaction_signed`] with the (partially) signed + /// funding transaction. + /// + /// Generated in [`ChannelManager`] message handling. + /// + /// # Failure Behavior and Persistence + /// This event will eventually be replayed after failures-to-handle (i.e., the event handler + /// returning `Err(ReplayEvent ())`), but will only be regenerated as needed after restarts. + /// + /// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager + /// [`ChannelManager::funding_transaction_signed`]: crate::ln::channelmanager::ChannelManager::funding_transaction_signed + FundingTransactionReadyForSigning { + /// The `channel_id` of the channel which you'll need to pass back into + /// [`ChannelManager::funding_transaction_signed`]. + /// + /// [`ChannelManager::funding_transaction_signed`]: crate::ln::channelmanager::ChannelManager::funding_transaction_signed + channel_id: ChannelId, + /// The counterparty's `node_id`, which you'll need to pass back into + /// [`ChannelManager::funding_transaction_signed`]. + /// + /// [`ChannelManager::funding_transaction_signed`]: crate::ln::channelmanager::ChannelManager::funding_transaction_signed + counterparty_node_id: PublicKey, + /// The `user_channel_id` value passed in for outbound channels, or for inbound channels if + /// [`UserConfig::manually_accept_inbound_channels`] config flag is set to true. Otherwise + /// `user_channel_id` will be randomized for inbound channels. + /// + /// [`UserConfig::manually_accept_inbound_channels`]: crate::util::config::UserConfig::manually_accept_inbound_channels + user_channel_id: u128, + /// The unsigned transaction to be signed and passed back to + /// [`ChannelManager::funding_transaction_signed`]. + /// + /// [`ChannelManager::funding_transaction_signed`]: crate::ln::channelmanager::ChannelManager::funding_transaction_signed + unsigned_transaction: Transaction, + }, } impl Writeable for Event { @@ -2123,17 +2180,20 @@ impl Writeable for Event { (8, former_temporary_channel_id, required), }); }, - #[cfg(async_payments)] &Event::PersistStaticInvoice { .. } => { 45u8.write(writer)?; // No need to write these events because we can just restart the static invoice negotiation // on startup. }, - #[cfg(async_payments)] &Event::StaticInvoiceRequested { .. } => { 47u8.write(writer)?; // Never write StaticInvoiceRequested events as buffered onion messages aren't serialized. }, + &Event::FundingTransactionReadyForSigning { .. } => { + 49u8.write(writer)?; + // We never write out FundingTransactionReadyForSigning events as they will be regenerated when + // necessary. + }, // Note that, going forward, all new events must only write data inside of // `write_tlv_fields`. Versions 0.0.101+ will ignore odd-numbered events that write // data via `write_tlv_fields`. @@ -2711,11 +2771,11 @@ impl MaybeReadable for Event { })) }, // Note that we do not write a length-prefixed TLV for PersistStaticInvoice events. - #[cfg(async_payments)] 45u8 => Ok(None), // Note that we do not write a length-prefixed TLV for StaticInvoiceRequested events. - #[cfg(async_payments)] 47u8 => Ok(None), + // Note that we do not write a length-prefixed TLV for FundingTransactionReadyForSigning events. + 49u8 => Ok(None), // Versions prior to 0.0.100 did not ignore odd types, instead returning InvalidValue. // Version 0.0.100 failed to properly ignore odd types, possibly resulting in corrupt // reads. diff --git a/lightning/src/ln/async_payments_tests.rs b/lightning/src/ln/async_payments_tests.rs index 7ee48225f78..2fa5dee8a84 100644 --- a/lightning/src/ln/async_payments_tests.rs +++ b/lightning/src/ln/async_payments_tests.rs @@ -17,6 +17,7 @@ use crate::events::{ use crate::ln::blinded_payment_tests::{fail_blinded_htlc_backwards, get_blinded_route_parameters}; use crate::ln::channelmanager::{PaymentId, RecipientOnionFields}; use crate::ln::functional_test_utils::*; +use crate::ln::inbound_payment; use crate::ln::msgs; use crate::ln::msgs::{ BaseMessageHandler, ChannelMessageHandler, MessageSendEvent, OnionMessageHandler, @@ -27,7 +28,7 @@ use crate::ln::outbound_payment::{ PendingOutboundPayment, Retry, TEST_ASYNC_PAYMENT_TIMEOUT_RELATIVE_EXPIRY, }; use crate::offers::async_receive_offer_cache::{ - TEST_MAX_CACHED_OFFERS_TARGET, TEST_MAX_UPDATE_ATTEMPTS, + TEST_INVOICE_REFRESH_THRESHOLD, TEST_MAX_CACHED_OFFERS_TARGET, TEST_MAX_UPDATE_ATTEMPTS, TEST_MIN_OFFER_PATHS_RELATIVE_EXPIRY_SECS, TEST_OFFER_REFRESH_THRESHOLD, }; use crate::offers::flow::{ @@ -36,8 +37,11 @@ use crate::offers::flow::{ }; use crate::offers::invoice_request::InvoiceRequest; use crate::offers::nonce::Nonce; -use crate::offers::offer::Offer; -use crate::offers::static_invoice::StaticInvoice; +use crate::offers::offer::{Amount, Offer}; +use crate::offers::static_invoice::{ + StaticInvoice, StaticInvoiceBuilder, + DEFAULT_RELATIVE_EXPIRY as STATIC_INVOICE_DEFAULT_RELATIVE_EXPIRY, +}; use crate::onion_message::async_payments::{AsyncPaymentsMessage, AsyncPaymentsMessageHandler}; use crate::onion_message::messenger::{ Destination, MessageRouter, MessageSendInstructions, PeeledOnion, @@ -62,7 +66,6 @@ use core::time::Duration; struct StaticInvoiceServerFlowResult { invoice: StaticInvoice, invoice_slot: u16, - invoice_id: u128, // Returning messages that were sent along the way allows us to test handling duplicate messages. offer_paths_request: msgs::OnionMessage, @@ -144,16 +147,15 @@ fn pass_static_invoice_server_messages( // that the static invoice should be persisted. let mut events = server.node.get_and_clear_pending_events(); assert_eq!(events.len(), 1); - let (invoice, invoice_slot, invoice_id, ack_path) = match events.pop().unwrap() { + let (invoice, invoice_slot, ack_path) = match events.pop().unwrap() { Event::PersistStaticInvoice { invoice, invoice_persisted_path, recipient_id: ev_id, invoice_slot, - invoice_id, } => { assert_eq!(recipient_id, ev_id); - (invoice, invoice_slot, invoice_id, invoice_persisted_path) + (invoice, invoice_slot, invoice_persisted_path) }, _ => panic!(), }; @@ -179,7 +181,6 @@ fn pass_static_invoice_server_messages( static_invoice_persisted_message: invoice_persisted_om, invoice, invoice_slot, - invoice_id, } } @@ -205,7 +206,7 @@ fn pass_async_payments_oms( let mut events = always_online_recipient_counterparty.node.get_and_clear_pending_events(); assert_eq!(events.len(), 1); let reply_path = match events.pop().unwrap() { - Event::StaticInvoiceRequested { recipient_id: ev_id, invoice_id: _, reply_path } => { + Event::StaticInvoiceRequested { recipient_id: ev_id, invoice_slot: _, reply_path } => { assert_eq!(recipient_id, ev_id); reply_path }, @@ -240,10 +241,50 @@ fn pass_async_payments_oms( (held_htlc_available_om_1_2, release_held_htlc) } +fn create_static_invoice_builder<'a>( + recipient: &Node, offer: &'a Offer, offer_nonce: Nonce, relative_expiry: Option, +) -> StaticInvoiceBuilder<'a> { + let entropy = recipient.keys_manager; + let amount_msat = offer.amount().and_then(|amount| match amount { + Amount::Bitcoin { amount_msats } => Some(amount_msats), + Amount::Currency { .. } => None, + }); + + let relative_expiry = relative_expiry.unwrap_or(STATIC_INVOICE_DEFAULT_RELATIVE_EXPIRY); + let relative_expiry_secs: u32 = relative_expiry.as_secs().try_into().unwrap_or(u32::MAX); + + let created_at = recipient.node.duration_since_epoch(); + let payment_secret = inbound_payment::create_for_spontaneous_payment( + &recipient.keys_manager.get_inbound_payment_key(), + amount_msat, + relative_expiry_secs, + created_at.as_secs(), + None, + ) + .unwrap(); + + recipient + .node + .flow + .create_static_invoice_builder( + &recipient.router, + entropy, + offer, + offer_nonce, + payment_secret, + relative_expiry_secs, + recipient.node.list_usable_channels(), + recipient.node.test_get_peers_for_blinded_path(), + ) + .unwrap() +} + fn create_static_invoice( always_online_counterparty: &Node, recipient: &Node, relative_expiry: Option, secp_ctx: &Secp256k1, ) -> (Offer, StaticInvoice) { + let entropy_source = recipient.keys_manager; + let blinded_paths_to_always_online_node = always_online_counterparty .message_router .create_blinded_paths( @@ -256,15 +297,14 @@ fn create_static_invoice( .unwrap(); let (offer_builder, offer_nonce) = recipient .node - .create_async_receive_offer_builder(blinded_paths_to_always_online_node) + .flow + .create_async_receive_offer_builder(entropy_source, blinded_paths_to_always_online_node) .unwrap(); let offer = offer_builder.build().unwrap(); - let static_invoice = recipient - .node - .create_static_invoice_builder(&offer, offer_nonce, relative_expiry) - .unwrap() - .build_and_sign(&secp_ctx) - .unwrap(); + let static_invoice = + create_static_invoice_builder(recipient, &offer, offer_nonce, relative_expiry) + .build_and_sign(&secp_ctx) + .unwrap(); (offer, static_invoice) } @@ -287,6 +327,38 @@ fn extract_payment_preimage(event: &Event) -> PaymentPreimage { } } +fn expect_offer_paths_requests(recipient: &Node, next_hop_nodes: &[&Node]) { + // We want to check that the async recipient has enqueued at least one `OfferPathsRequest` and no + // other message types. Check this by iterating through all their outbound onion messages, peeling + // multiple times if the messages are forwarded through other nodes. + let per_msg_recipient_msgs = recipient.onion_messenger.release_pending_msgs(); + let mut pk_to_msg = Vec::new(); + for (pk, msgs) in per_msg_recipient_msgs { + for msg in msgs { + pk_to_msg.push((pk, msg)); + } + } + let mut num_offer_paths_reqs: u8 = 0; + while let Some((pk, msg)) = pk_to_msg.pop() { + let node = next_hop_nodes.iter().find(|node| node.node.get_our_node_id() == pk).unwrap(); + let peeled_msg = node.onion_messenger.peel_onion_message(&msg).unwrap(); + match peeled_msg { + PeeledOnion::AsyncPayments(AsyncPaymentsMessage::OfferPathsRequest(_), _, _) => { + num_offer_paths_reqs += 1; + }, + PeeledOnion::Forward(next_hop, msg) => { + let next_pk = match next_hop { + crate::blinded_path::message::NextMessageHop::NodeId(pk) => pk, + _ => panic!(), + }; + pk_to_msg.push((next_pk, msg)); + }, + _ => panic!("Unexpected message"), + } + } + assert!(num_offer_paths_reqs > 0); +} + fn advance_time_by(duration: Duration, node: &Node) { let target_time = (node.node.duration_since_epoch() + duration).as_secs() as u32; let block = create_dummy_block(node.best_block_hash(), target_time, Vec::new()); @@ -377,6 +449,7 @@ fn static_invoice_unknown_required_features() { let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]); let nodes = create_network(3, &node_cfgs, &node_chanmgrs); + let entropy_source = nodes[2].keys_manager; create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); create_unannounced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0); @@ -393,16 +466,15 @@ fn static_invoice_unknown_required_features() { .unwrap(); let (offer_builder, nonce) = nodes[2] .node - .create_async_receive_offer_builder(blinded_paths_to_always_online_node) + .flow + .create_async_receive_offer_builder(entropy_source, blinded_paths_to_always_online_node) .unwrap(); let offer = offer_builder.build().unwrap(); - let static_invoice_unknown_req_features = nodes[2] - .node - .create_static_invoice_builder(&offer, nonce, None) - .unwrap() - .features_unchecked(Bolt12InvoiceFeatures::unknown()) - .build_and_sign(&secp_ctx) - .unwrap(); + let static_invoice_unknown_req_features = + create_static_invoice_builder(&nodes[2], &offer, nonce, None) + .features_unchecked(Bolt12InvoiceFeatures::unknown()) + .build_and_sign(&secp_ctx) + .unwrap(); // Initiate payment to the offer corresponding to the manually-constructed invoice that has // unknown required features. @@ -469,6 +541,7 @@ fn ignore_unexpected_static_invoice() { let inv_server_paths = nodes[1].node.blinded_paths_for_async_recipient(recipient_id.clone(), None).unwrap(); nodes[2].node.set_paths_to_static_invoice_server(inv_server_paths).unwrap(); + expect_offer_paths_requests(&nodes[2], &[&nodes[0], &nodes[1]]); // Initiate payment to the sender's intended offer. let valid_static_invoice = @@ -497,7 +570,7 @@ fn ignore_unexpected_static_invoice() { let mut events = nodes[1].node.get_and_clear_pending_events(); assert_eq!(events.len(), 1); let reply_path = match events.pop().unwrap() { - Event::StaticInvoiceRequested { recipient_id: ev_id, invoice_id: _, reply_path } => { + Event::StaticInvoiceRequested { recipient_id: ev_id, invoice_slot: _, reply_path } => { assert_eq!(recipient_id, ev_id); reply_path }, @@ -566,6 +639,7 @@ fn async_receive_flow_success() { let inv_server_paths = nodes[1].node.blinded_paths_for_async_recipient(recipient_id.clone(), None).unwrap(); nodes[2].node.set_paths_to_static_invoice_server(inv_server_paths).unwrap(); + expect_offer_paths_requests(&nodes[2], &[&nodes[0], &nodes[1]]); let invoice_flow_res = pass_static_invoice_server_messages(&nodes[1], &nodes[2], recipient_id.clone()); @@ -628,6 +702,7 @@ fn expired_static_invoice_fail() { let inv_server_paths = nodes[1].node.blinded_paths_for_async_recipient(recipient_id.clone(), None).unwrap(); nodes[2].node.set_paths_to_static_invoice_server(inv_server_paths).unwrap(); + expect_offer_paths_requests(&nodes[2], &[&nodes[0], &nodes[1]]); let static_invoice = pass_static_invoice_server_messages(&nodes[1], &nodes[2], recipient_id.clone()).invoice; @@ -703,6 +778,7 @@ fn timeout_unreleased_payment() { let inv_server_paths = server.node.blinded_paths_for_async_recipient(recipient_id.clone(), None).unwrap(); recipient.node.set_paths_to_static_invoice_server(inv_server_paths).unwrap(); + expect_offer_paths_requests(&nodes[2], &[&nodes[0], &nodes[1]]); let static_invoice = pass_static_invoice_server_messages(server, recipient, recipient_id.clone()).invoice; @@ -788,6 +864,7 @@ fn async_receive_mpp() { let inv_server_paths = nodes[1].node.blinded_paths_for_async_recipient(recipient_id.clone(), None).unwrap(); nodes[3].node.set_paths_to_static_invoice_server(inv_server_paths).unwrap(); + expect_offer_paths_requests(&nodes[3], &[&nodes[0], &nodes[1], &nodes[2]]); let static_invoice = pass_static_invoice_server_messages(&nodes[1], &nodes[3], recipient_id.clone()).invoice; @@ -881,6 +958,7 @@ fn amount_doesnt_match_invreq() { let inv_server_paths = nodes[1].node.blinded_paths_for_async_recipient(recipient_id.clone(), None).unwrap(); nodes[3].node.set_paths_to_static_invoice_server(inv_server_paths).unwrap(); + expect_offer_paths_requests(&nodes[3], &[&nodes[0], &nodes[1], &nodes[2]]); let static_invoice = pass_static_invoice_server_messages(&nodes[1], &nodes[3], recipient_id.clone()).invoice; @@ -1073,6 +1151,7 @@ fn invalid_async_receive_with_retry( create_node_chanmgrs(3, &node_cfgs, &[None, Some(allow_priv_chan_fwds_cfg), None]); let nodes = create_network(3, &node_cfgs, &node_chanmgrs); + let entropy_source = nodes[2].keys_manager; create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); create_unannounced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0); @@ -1080,6 +1159,7 @@ fn invalid_async_receive_with_retry( let inv_server_paths = nodes[1].node.blinded_paths_for_async_recipient(recipient_id.clone(), None).unwrap(); nodes[2].node.set_paths_to_static_invoice_server(inv_server_paths).unwrap(); + expect_offer_paths_requests(&nodes[2], &[&nodes[0], &nodes[1]]); // Set the random bytes so we can predict the offer nonce. let hardcoded_random_bytes = [42; 32]; @@ -1102,7 +1182,8 @@ fn invalid_async_receive_with_retry( .unwrap(); let (offer_builder, offer_nonce) = nodes[2] .node - .create_async_receive_offer_builder(blinded_paths_to_always_online_node) + .flow + .create_async_receive_offer_builder(entropy_source, blinded_paths_to_always_online_node) .unwrap(); let offer = offer_builder.build().unwrap(); let amt_msat = 5000; @@ -1112,12 +1193,10 @@ fn invalid_async_receive_with_retry( // use the same nodes to avoid complicating the test with a bunch of extra nodes. let mut static_invoice_paths = Vec::new(); for _ in 0..3 { - let static_inv_for_path = nodes[2] - .node - .create_static_invoice_builder(&offer, offer_nonce, None) - .unwrap() - .build_and_sign(&secp_ctx) - .unwrap(); + let static_inv_for_path = + create_static_invoice_builder(&nodes[2], &offer, offer_nonce, None) + .build_and_sign(&secp_ctx) + .unwrap(); static_invoice_paths.push(static_inv_for_path.payment_paths()[0].clone()); } nodes[2].router.expect_blinded_payment_paths(static_invoice_paths); @@ -1208,6 +1287,7 @@ fn expired_static_invoice_message_path() { let inv_server_paths = nodes[1].node.blinded_paths_for_async_recipient(recipient_id.clone(), None).unwrap(); nodes[2].node.set_paths_to_static_invoice_server(inv_server_paths).unwrap(); + expect_offer_paths_requests(&nodes[2], &[&nodes[0], &nodes[1]]); let static_invoice = pass_static_invoice_server_messages(&nodes[1], &nodes[2], recipient_id.clone()).invoice; @@ -1272,6 +1352,7 @@ fn expired_static_invoice_payment_path() { let inv_server_paths = nodes[1].node.blinded_paths_for_async_recipient(recipient_id.clone(), None).unwrap(); nodes[2].node.set_paths_to_static_invoice_server(inv_server_paths).unwrap(); + expect_offer_paths_requests(&nodes[2], &[&nodes[0], &nodes[1]]); // Make sure all nodes are at the same block height in preparation for CLTV timeout things. let node_max_height = @@ -1470,54 +1551,6 @@ fn ignore_expired_offer_paths_message() { .is_none()); } -#[cfg_attr(feature = "std", ignore)] -#[test] -fn ignore_expired_invoice_persisted_message() { - // If the recipient receives a static_invoice_persisted message over an expired reply path, it - // should be ignored. - let chanmon_cfgs = create_chanmon_cfgs(2); - let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); - let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); - let nodes = create_network(2, &node_cfgs, &node_chanmgrs); - create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); - let server = &nodes[0]; - let recipient = &nodes[1]; - - let recipient_id = vec![42; 32]; - let inv_server_paths = - server.node.blinded_paths_for_async_recipient(recipient_id, None).unwrap(); - recipient.node.set_paths_to_static_invoice_server(inv_server_paths).unwrap(); - - // Exchange messages until we can extract the final static_invoice_persisted OM. - recipient.node.timer_tick_occurred(); - let serve_static_invoice = invoice_flow_up_to_send_serve_static_invoice(server, recipient).1; - server - .onion_messenger - .handle_onion_message(recipient.node.get_our_node_id(), &serve_static_invoice); - let mut events = server.node.get_and_clear_pending_events(); - assert_eq!(events.len(), 1); - let ack_path = match events.pop().unwrap() { - Event::PersistStaticInvoice { invoice_persisted_path, .. } => invoice_persisted_path, - _ => panic!(), - }; - - server.node.static_invoice_persisted(ack_path); - let invoice_persisted = server - .onion_messenger - .next_onion_message_for_peer(recipient.node.get_our_node_id()) - .unwrap(); - assert!(matches!( - recipient.onion_messenger.peel_onion_message(&invoice_persisted).unwrap(), - PeeledOnion::AsyncPayments(AsyncPaymentsMessage::StaticInvoicePersisted(_), _, _) - )); - - advance_time_by(TEST_TEMP_REPLY_PATH_RELATIVE_EXPIRY + Duration::from_secs(1), recipient); - recipient - .onion_messenger - .handle_onion_message(server.node.get_our_node_id(), &invoice_persisted); - assert!(recipient.node.get_async_receive_offer().is_err()); -} - #[test] fn limit_offer_paths_requests() { // Limit the number of offer_paths_requests sent to the server if they aren't responding. @@ -1533,10 +1566,12 @@ fn limit_offer_paths_requests() { let inv_server_paths = server.node.blinded_paths_for_async_recipient(recipient_id, None).unwrap(); recipient.node.set_paths_to_static_invoice_server(inv_server_paths).unwrap(); + expect_offer_paths_requests(&nodes[1], &[&nodes[0]]); // Up to TEST_MAX_UPDATE_ATTEMPTS offer_paths_requests are allowed to be sent out before the async // recipient should give up. - for _ in 0..TEST_MAX_UPDATE_ATTEMPTS { + // Subtract 1 because we sent the first request when invoice server paths were set above. + for _ in 0..TEST_MAX_UPDATE_ATTEMPTS - 1 { recipient.node.test_check_refresh_async_receive_offers(); let offer_paths_req = recipient .onion_messenger @@ -1588,13 +1623,13 @@ fn limit_serve_static_invoice_requests() { // Build the target number of offers interactively with the static invoice server. let mut offer_paths_req = None; - let mut invoice_ids = new_hash_set(); + let mut invoice_slots = new_hash_set(); for expected_inv_slot in 0..TEST_MAX_CACHED_OFFERS_TARGET { let flow_res = pass_static_invoice_server_messages(server, recipient, recipient_id.clone()); assert_eq!(flow_res.invoice_slot, expected_inv_slot as u16); offer_paths_req = Some(flow_res.offer_paths_request); - invoice_ids.insert(flow_res.invoice_id); + invoice_slots.insert(flow_res.invoice_slot); // Trigger a cache refresh recipient.node.timer_tick_occurred(); @@ -1603,8 +1638,8 @@ fn limit_serve_static_invoice_requests() { recipient.node.flow.test_get_async_receive_offers().len(), TEST_MAX_CACHED_OFFERS_TARGET ); - // Check that all invoice ids are unique. - assert_eq!(invoice_ids.len(), TEST_MAX_CACHED_OFFERS_TARGET); + // Check that all invoice slot numbers are unique. + assert_eq!(invoice_slots.len(), TEST_MAX_CACHED_OFFERS_TARGET); // Force allowing more offer paths request attempts so we can check that the recipient will not // attempt to build any further offers. @@ -1677,11 +1712,53 @@ fn offer_cache_round_trip_ser() { assert_eq!(cached_offers_pre_ser, cached_offers_post_ser); } +#[test] +fn refresh_static_invoices_for_pending_offers() { + // Check that an invoice for an offer that is pending persistence with the server will be updated + // every timer tick. + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); + let server = &nodes[0]; + let recipient = &nodes[1]; + + let recipient_id = vec![42; 32]; + let inv_server_paths = + server.node.blinded_paths_for_async_recipient(recipient_id.clone(), None).unwrap(); + recipient.node.set_paths_to_static_invoice_server(inv_server_paths).unwrap(); + expect_offer_paths_requests(&nodes[1], &[&nodes[0]]); + + // Set up the recipient to have one offer pending with the static invoice server. + invoice_flow_up_to_send_serve_static_invoice(server, recipient); + + // Every timer tick, we'll send a fresh invoice to the server. + for _ in 0..10 { + recipient.node.timer_tick_occurred(); + let pending_oms = recipient.onion_messenger.release_pending_msgs(); + pending_oms + .get(&server.node.get_our_node_id()) + .unwrap() + .iter() + .find(|msg| match server.onion_messenger.peel_onion_message(&msg).unwrap() { + PeeledOnion::AsyncPayments(AsyncPaymentsMessage::ServeStaticInvoice(_), _, _) => { + true + }, + PeeledOnion::AsyncPayments(AsyncPaymentsMessage::OfferPathsRequest(_), _, _) => { + false + }, + _ => panic!("Unexpected message"), + }) + .unwrap(); + } +} + #[cfg_attr(feature = "std", ignore)] #[test] -fn refresh_static_invoices() { - // Check that an invoice for a particular offer stored with the server will be updated once per - // timer tick. +fn refresh_static_invoices_for_used_offers() { + // Check that an invoice for a used offer stored with the server will be updated every + // INVOICE_REFRESH_THRESHOLD. let chanmon_cfgs = create_chanmon_cfgs(3); let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); @@ -1701,29 +1778,31 @@ fn refresh_static_invoices() { let inv_server_paths = server.node.blinded_paths_for_async_recipient(recipient_id.clone(), None).unwrap(); recipient.node.set_paths_to_static_invoice_server(inv_server_paths).unwrap(); + expect_offer_paths_requests(&nodes[2], &[&nodes[0], &nodes[1]]); // Set up the recipient to have one offer and an invoice with the static invoice server. let flow_res = pass_static_invoice_server_messages(server, recipient, recipient_id.clone()); let original_invoice = flow_res.invoice; - // Mark the offer as used so we'll update the invoice on timer tick. + // Mark the offer as used so we'll update the invoice after INVOICE_REFRESH_THRESHOLD. let _offer = recipient.node.get_async_receive_offer().unwrap(); // Force the server and recipient to send OMs directly to each other for testing simplicity. server.message_router.peers_override.lock().unwrap().push(recipient.node.get_our_node_id()); recipient.message_router.peers_override.lock().unwrap().push(server.node.get_our_node_id()); - assert!(recipient - .onion_messenger - .next_onion_message_for_peer(server.node.get_our_node_id()) - .is_none()); + // Prior to INVOICE_REFRESH_THRESHOLD, we won't refresh the invoice. + advance_time_by(TEST_INVOICE_REFRESH_THRESHOLD, recipient); + recipient.node.timer_tick_occurred(); + expect_offer_paths_requests(&nodes[2], &[&nodes[0], &nodes[1]]); - // Check that we'll refresh the invoice on the next timer tick. + // After INVOICE_REFRESH_THRESHOLD, we will refresh the invoice. + advance_time_by(Duration::from_secs(1), recipient); recipient.node.timer_tick_occurred(); let pending_oms = recipient.onion_messenger.release_pending_msgs(); let serve_static_invoice_om = pending_oms .get(&server.node.get_our_node_id()) .unwrap() - .into_iter() + .iter() .find(|msg| match server.onion_messenger.peel_onion_message(&msg).unwrap() { PeeledOnion::AsyncPayments(AsyncPaymentsMessage::ServeStaticInvoice(_), _, _) => true, PeeledOnion::AsyncPayments(AsyncPaymentsMessage::OfferPathsRequest(_), _, _) => false, @@ -1740,16 +1819,15 @@ fn refresh_static_invoices() { Event::PersistStaticInvoice { invoice, invoice_slot, - invoice_id, invoice_persisted_path, recipient_id: ev_id, } => { assert_ne!(original_invoice, invoice); assert_eq!(recipient_id, ev_id); assert_eq!(invoice_slot, flow_res.invoice_slot); - // When we update the invoice corresponding to a specific offer, the invoice_id stays the + // When we update the invoice corresponding to a specific offer, the invoice_slot stays the // same. - assert_eq!(invoice_id, flow_res.invoice_id); + assert_eq!(invoice_slot, flow_res.invoice_slot); (invoice, invoice_persisted_path) }, _ => panic!(), @@ -2093,6 +2171,7 @@ fn invoice_server_is_not_channel_peer() { let inv_server_paths = invoice_server.node.blinded_paths_for_async_recipient(recipient_id.clone(), None).unwrap(); recipient.node.set_paths_to_static_invoice_server(inv_server_paths).unwrap(); + expect_offer_paths_requests(&nodes[2], &[&nodes[0], &nodes[1], &nodes[3]]); let invoice = pass_static_invoice_server_messages(invoice_server, recipient, recipient_id.clone()) .invoice; diff --git a/lightning/src/ln/async_signer_tests.rs b/lightning/src/ln/async_signer_tests.rs index 225f58898c2..00696515724 100644 --- a/lightning/src/ln/async_signer_tests.rs +++ b/lightning/src/ln/async_signer_tests.rs @@ -1010,7 +1010,7 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) { // Route an HTLC and set the signer as unavailable. let (_, _, chan_id, funding_tx) = create_announced_chan_between_nodes(&nodes, 0, 1); - route_payment(&nodes[0], &[&nodes[1]], 1_000_000); + let (_, payment_hash, _, _) = route_payment(&nodes[0], &[&nodes[1]], 1_000_000); if remote_commitment { let message = "Channel force-closed".to_owned(); @@ -1051,6 +1051,9 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) { &nodes[0].logger, ); } + if anchors { + handle_bump_close_event(closing_node); + } let commitment_tx = { let mut txn = closing_node.tx_broadcaster.txn_broadcast(); @@ -1083,16 +1086,14 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) { nodes[0].disable_channel_signer_op(&node_b_id, &chan_id, sign_htlc_op); mine_transaction(&nodes[0], &commitment_tx); - check_added_monitors(&nodes[0], 1); check_closed_broadcast(&nodes[0], 1, true); - check_closed_event( - &nodes[0], - 1, - ClosureReason::CommitmentTxConfirmed, - false, - &[node_b_id], - 100_000, - ); + check_added_monitors(&nodes[0], 1); + let closure_reason = if remote_commitment { + ClosureReason::CommitmentTxConfirmed + } else { + ClosureReason::HTLCsTimedOut { payment_hash: Some(payment_hash) } + }; + check_closed_event(&nodes[0], 1, closure_reason, false, &[node_b_id], 100_000); // If the counterparty broadcast its latest commitment, we need to mine enough blocks for the // HTLC timeout. @@ -1395,6 +1396,7 @@ fn test_no_disconnect_while_async_commitment_signed_expecting_remote_revoke_and_ let (preimage, payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1]], payment_amount); nodes[1].node.claim_funds(preimage); check_added_monitors(&nodes[1], 1); + expect_payment_claimed!(nodes[1], payment_hash, payment_amount); // We'll disable signing counterparty commitments on the payment sender. nodes[0].disable_channel_signer_op(&node_b_id, &chan_id, SignerOp::SignCounterpartyCommitment); @@ -1403,6 +1405,7 @@ fn test_no_disconnect_while_async_commitment_signed_expecting_remote_revoke_and_ // the `commitment_signed` is no longer pending. let mut update = get_htlc_update_msgs!(&nodes[1], node_a_id); nodes[0].node.handle_update_fulfill_htlc(node_b_id, update.update_fulfill_htlcs.remove(0)); + expect_payment_sent(&nodes[0], preimage, None, false, false); nodes[0].node.handle_commitment_signed_batch_test(node_b_id, &update.commitment_signed); check_added_monitors(&nodes[0], 1); @@ -1426,7 +1429,4 @@ fn test_no_disconnect_while_async_commitment_signed_expecting_remote_revoke_and_ }; assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty()); assert!(nodes[1].node.get_and_clear_pending_msg_events().into_iter().any(has_disconnect_event)); - - expect_payment_sent(&nodes[0], preimage, None, false, false); - expect_payment_claimed!(nodes[1], payment_hash, payment_amount); } diff --git a/lightning/src/ln/blinded_payment_tests.rs b/lightning/src/ln/blinded_payment_tests.rs index d72c816cba9..f8ea84707f9 100644 --- a/lightning/src/ln/blinded_payment_tests.rs +++ b/lightning/src/ln/blinded_payment_tests.rs @@ -10,8 +10,6 @@ // licenses. use bitcoin::hashes::hex::FromHex; -use bitcoin::hashes::sha256::Hash as Sha256; -use bitcoin::hashes::Hash; use bitcoin::hex::DisplayHex; use bitcoin::secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey, schnorr}; use bitcoin::secp256k1::ecdh::SharedSecret; @@ -453,7 +451,7 @@ fn do_forward_checks_failure(check: ForwardCheckFail, intro_fails: bool) { HTLCHandlingFailureType::Forward { node_id: Some(nodes[2].node.get_our_node_id()), channel_id: chan_1_2.2 }, }; expect_htlc_handling_failed_destinations!( - nodes[1].node.get_and_clear_pending_events(), &[failed_destination.clone()] + nodes[1].node.get_and_clear_pending_events(), core::slice::from_ref(&failed_destination) ); match check { ForwardCheckFail::ForwardPayloadEncodedAsReceive => { @@ -484,7 +482,7 @@ fn do_forward_checks_failure(check: ForwardCheckFail, intro_fails: bool) { HTLCHandlingFailureType::Forward { node_id: Some(nodes[3].node.get_our_node_id()), channel_id: chan_2_3.2 }, }; expect_htlc_handling_failed_destinations!( - nodes[2].node.get_and_clear_pending_events(), &[failed_destination.clone()] + nodes[2].node.get_and_clear_pending_events(), core::slice::from_ref(&failed_destination) ); check_added_monitors!(nodes[2], 1); @@ -1830,7 +1828,7 @@ fn test_combined_trampoline_onion_creation_vectors() { let amt_msat = 150_000_000; let cur_height = 800_000; let recipient_onion_fields = RecipientOnionFields::secret_only(payment_secret); - let (bob_onion, htlc_msat, htlc_cltv) = onion_utils::create_payment_onion_internal(&secp_ctx, &path, &session_priv, amt_msat, &recipient_onion_fields, cur_height, &associated_data, &None, None, [0; 32], Some(outer_session_key), Some(outer_onion_prng_seed)).unwrap(); + let (bob_onion, htlc_msat, htlc_cltv) = onion_utils::create_payment_onion_internal(&secp_ctx, &path, &outer_session_key, amt_msat, &recipient_onion_fields, cur_height, &associated_data, &None, None, outer_onion_prng_seed, Some(session_priv), Some([0; 32])).unwrap(); let outer_onion_packet_hex = bob_onion.encode().to_lower_hex_string(); assert_eq!(outer_onion_packet_hex, "00025fd60556c134ae97e4baedba220a644037754ee67c54fd05e93bf40c17cbb73362fb9dee96001ff229945595b6edb59437a6bc143406d3f90f749892a84d8d430c6890437d26d5bfc599d565316ef51347521075bbab87c59c57bcf20af7e63d7192b46cf171e4f73cb11f9f603915389105d91ad630224bea95d735e3988add1e24b5bf28f1d7128db64284d90a839ba340d088c74b1fb1bd21136b1809428ec5399c8649e9bdf92d2dcfc694deae5046fa5b2bdf646847aaad73f5e95275763091c90e71031cae1f9a770fdea559642c9c02f424a2a28163dd0957e3874bd28a97bec67d18c0321b0e68bc804aa8345b17cb626e2348ca06c8312a167c989521056b0f25c55559d446507d6c491d50605cb79fa87929ce64b0a9860926eeaec2c431d926a1cadb9a1186e4061cb01671a122fc1f57602cbef06d6c194ec4b715c2e3dd4120baca3172cd81900b49fef857fb6d6afd24c983b608108b0a5ac0c1c6c52011f23b8778059ffadd1bb7cd06e2525417365f485a7fd1d4a9ba3818ede7cdc9e71afee8532252d08e2531ca52538655b7e8d912f7ec6d37bbcce8d7ec690709dbf9321e92c565b78e7fe2c22edf23e0902153d1ca15a112ad32fb19695ec65ce11ddf670da7915f05ad4b86c154fb908cb567315d1124f303f75fa075ebde8ef7bb12e27737ad9e4924439097338ea6d7a6fc3721b88c9b830a34e8d55f4c582b74a3895cc848fe57f4fe29f115dabeb6b3175be15d94408ed6771109cfaf57067ae658201082eae7605d26b1449af4425ae8e8f58cdda5c6265f1fd7a386fc6cea3074e4f25b909b96175883676f7610a00fdf34df9eb6c7b9a4ae89b839c69fd1f285e38cdceb634d782cc6d81179759bc9fd47d7fd060470d0b048287764c6837963274e708314f017ac7dc26d0554d59bfcfd3136225798f65f0b0fea337c6b256ebbb63a90b994c0ab93fd8b1d6bd4c74aebe535d6110014cd3d525394027dfe8faa98b4e9b2bee7949eb1961f1b026791092f84deea63afab66603dbe9b6365a102a1fef2f6b9744bc1bb091a8da9130d34d4d39f25dbad191649cfb67e10246364b7ce0c6ec072f9690cabb459d9fda0c849e17535de4357e9907270c75953fca3c845bb613926ecf73205219c7057a4b6bb244c184362bb4e2f24279dc4e60b94a5b1ec11c34081a628428ba5646c995b9558821053ba9c84a05afbf00dabd60223723096516d2f5668f3ec7e11612b01eb7a3a0506189a2272b88e89807943adb34291a17f6cb5516ffd6f945a1c42a524b21f096d66f350b1dad4db455741ae3d0e023309fbda5ef55fb0dc74f3297041448b2be76c525141963934c6afc53d263fb7836626df502d7c2ee9e79cbbd87afd84bbb8dfbf45248af3cd61ad5fac827e7683ca4f91dfad507a8eb9c17b2c9ac5ec051fe645a4a6cb37136f6f19b611e0ea8da7960af2d779507e55f57305bc74b7568928c5dd5132990fe54c22117df91c257d8c7b61935a018a28c1c3b17bab8e4294fa699161ec21123c9fc4e71079df31f300c2822e1246561e04765d3aab333eafd026c7431ac7616debb0e022746f4538e1c6348b600c988eeb2d051fc60c468dca260a84c79ab3ab8342dc345a764672848ea234e17332bc124799daf7c5fcb2e2358514a7461357e1c19c802c5ee32deccf1776885dd825bedd5f781d459984370a6b7ae885d4483a76ddb19b30f47ed47cd56aa5a079a89793dbcad461c59f2e002067ac98dd5a534e525c9c46c2af730741bf1f8629357ec0bfc0bc9ecb31af96777e507648ff4260dc3673716e098d9111dfd245f1d7c55a6de340deb8bd7a053e5d62d760f184dc70ca8fa255b9023b9b9aedfb6e419a5b5951ba0f83b603793830ee68d442d7b88ee1bbf6bbd1bcd6f68cc1af"); @@ -1984,7 +1982,10 @@ fn test_trampoline_inbound_payment_decoding() { }; } -fn do_test_trampoline_single_hop_receive(success: bool) { +#[test] +fn test_trampoline_forward_payload_encoded_as_receive() { + // Test that we'll fail backwards as expected when receiving a well-formed blinded forward + // trampoline onion payload with no next hop present. const TOTAL_NODE_COUNT: usize = 3; let secp_ctx = Secp256k1::new(); @@ -2008,30 +2009,18 @@ fn do_test_trampoline_single_hop_receive(success: bool) { let bob_carol_scid = nodes[1].node().list_channels().iter().find(|c| c.channel_id == chan_id_bob_carol).unwrap().short_channel_id.unwrap(); let amt_msat = 1000; - let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), None); + let (payment_preimage, payment_hash, _) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), None); - let carol_alice_trampoline_session_priv = secret_from_hex("a0f4b8d7b6c2d0ffdfaf718f76e9decaef4d9fb38a8c4addb95c4007cc3eee03"); - let carol_blinding_point = PublicKey::from_secret_key(&secp_ctx, &carol_alice_trampoline_session_priv); - let carol_blinded_hops = if success { - let payee_tlvs = UnauthenticatedReceiveTlvs { - payment_secret, - payment_constraints: PaymentConstraints { - max_cltv_expiry: u32::max_value(), - htlc_minimum_msat: amt_msat, - }, - payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}), - }; + // We need the session priv to construct an invalid onion packet later. + let override_random_bytes = [3; 32]; + *nodes[0].keys_manager.override_random_bytes.lock().unwrap() = Some(override_random_bytes); - let nonce = Nonce([42u8; 16]); - let expanded_key = nodes[2].keys_manager.get_inbound_payment_key(); - let payee_tlvs = payee_tlvs.authenticate(nonce, &expanded_key); - let carol_unblinded_tlvs = payee_tlvs.encode(); + let outer_session_priv = SecretKey::from_slice(&override_random_bytes).unwrap(); + let trampoline_session_priv = onion_utils::compute_trampoline_session_priv(&outer_session_priv); - let path = [((carol_node_id, None), WithoutLength(&carol_unblinded_tlvs))]; - blinded_path::utils::construct_blinded_hops( - &secp_ctx, path.into_iter(), &carol_alice_trampoline_session_priv, - ).unwrap() - } else { + // Create a blinded hop for the recipient that is encoded as a trampoline forward. + let carol_blinding_point = PublicKey::from_secret_key(&secp_ctx, &trampoline_session_priv); + let carol_blinded_hops = { let payee_tlvs = blinded_path::payment::TrampolineForwardTlvs { next_trampoline: alice_node_id, payment_constraints: PaymentConstraints { @@ -2050,7 +2039,7 @@ fn do_test_trampoline_single_hop_receive(success: bool) { let carol_unblinded_tlvs = payee_tlvs.encode(); let path = [((carol_node_id, None), WithoutLength(&carol_unblinded_tlvs))]; blinded_path::utils::construct_blinded_hops( - &secp_ctx, path.into_iter(), &carol_alice_trampoline_session_priv, + &secp_ctx, path.into_iter(), &trampoline_session_priv, ).unwrap() }; @@ -2086,7 +2075,7 @@ fn do_test_trampoline_single_hop_receive(success: bool) { pubkey: carol_node_id, node_features: Features::empty(), fee_msat: amt_msat, - cltv_expiry_delta: 24, + cltv_expiry_delta: 104, }, ], hops: carol_blinded_hops, @@ -2098,83 +2087,350 @@ fn do_test_trampoline_single_hop_receive(success: bool) { route_params: None, }; - // We need the session priv to construct an invalid onion packet later. - let override_random_bytes = [3; 32]; - *nodes[0].keys_manager.override_random_bytes.lock().unwrap() = Some(override_random_bytes); - nodes[0].node.send_payment_with_route(route.clone(), payment_hash, RecipientOnionFields::spontaneous_empty(), PaymentId(payment_hash.0)).unwrap(); + check_added_monitors!(&nodes[0], 1); + + let replacement_onion = { + // create a substitute onion where the last Trampoline hop is a forward + let recipient_onion_fields = RecipientOnionFields::spontaneous_empty(); + + let mut blinded_tail = route.paths[0].blinded_tail.clone().unwrap(); + + // append some dummy blinded hop so the intro hop looks like a forward + blinded_tail.hops.push(BlindedHop { + blinded_node_id: alice_node_id, + encrypted_payload: vec![], + }); + + let (mut trampoline_payloads, outer_total_msat, outer_starting_htlc_offset) = onion_utils::build_trampoline_onion_payloads(&blinded_tail, amt_msat, &recipient_onion_fields, 32, &None).unwrap(); + + // pop the last dummy hop + trampoline_payloads.pop(); + + let trampoline_onion_keys = onion_utils::construct_trampoline_onion_keys(&secp_ctx, &route.paths[0].blinded_tail.as_ref().unwrap(), &trampoline_session_priv); + let trampoline_packet = onion_utils::construct_trampoline_onion_packet( + trampoline_payloads, + trampoline_onion_keys, + override_random_bytes, + &payment_hash, + None, + ).unwrap(); + + let (outer_payloads, _, _) = onion_utils::build_onion_payloads(&route.paths[0], outer_total_msat, &recipient_onion_fields, outer_starting_htlc_offset, &None, None, Some(trampoline_packet)).unwrap(); + let outer_onion_keys = onion_utils::construct_onion_keys(&secp_ctx, &route.clone().paths[0], &outer_session_priv); + let outer_packet = onion_utils::construct_onion_packet( + outer_payloads, + outer_onion_keys, + override_random_bytes, + &payment_hash, + ).unwrap(); + + outer_packet + }; + + let mut events = nodes[0].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + let mut first_message_event = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events); + let mut update_message = match first_message_event { + MessageSendEvent::UpdateHTLCs { ref mut updates, .. } => { + assert_eq!(updates.update_add_htlcs.len(), 1); + updates.update_add_htlcs.get_mut(0) + }, + _ => panic!() + }; + update_message.map(|msg| { + msg.onion_routing_packet = replacement_onion.clone(); + }); + + let route: &[&Node] = &[&nodes[1], &nodes[2]]; + let args = PassAlongPathArgs::new(&nodes[0], route, amt_msat, payment_hash, first_message_event) + .with_payment_preimage(payment_preimage) + .without_claimable_event() + .expect_failure(HTLCHandlingFailureType::InvalidOnion); + do_pass_along_path(args); + + { + let unblinded_node_updates = get_htlc_update_msgs!(nodes[2], nodes[1].node.get_our_node_id()); + nodes[1].node.handle_update_fail_htlc( + nodes[2].node.get_our_node_id(), &unblinded_node_updates.update_fail_htlcs[0] + ); + do_commitment_signed_dance(&nodes[1], &nodes[2], &unblinded_node_updates.commitment_signed, true, false); + } + { + let unblinded_node_updates = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()); + nodes[0].node.handle_update_fail_htlc( + nodes[1].node.get_our_node_id(), &unblinded_node_updates.update_fail_htlcs[0] + ); + do_commitment_signed_dance(&nodes[0], &nodes[1], &unblinded_node_updates.commitment_signed, false, false); + } + { + let payment_failed_conditions = PaymentFailedConditions::new() + .expected_htlc_error_data(LocalHTLCFailureReason::InvalidOnionPayload, &[0; 0]); + expect_payment_failed_conditions(&nodes[0], payment_hash, true, payment_failed_conditions); + } +} + +fn do_test_trampoline_single_hop_receive(success: bool) { + const TOTAL_NODE_COUNT: usize = 3; + let secp_ctx = Secp256k1::new(); + + let chanmon_cfgs = create_chanmon_cfgs(TOTAL_NODE_COUNT); + let node_cfgs = create_node_cfgs(TOTAL_NODE_COUNT, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(TOTAL_NODE_COUNT, &node_cfgs, &vec![None; TOTAL_NODE_COUNT]); + let mut nodes = create_network(TOTAL_NODE_COUNT, &node_cfgs, &node_chanmgrs); + + let (_, _, chan_id_alice_bob, _) = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); + let (_, _, chan_id_bob_carol, _) = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0); + + for i in 0..TOTAL_NODE_COUNT { // connect all nodes' blocks + connect_blocks(&nodes[i], (TOTAL_NODE_COUNT as u32) * CHAN_CONFIRM_DEPTH + 1 - nodes[i].best_block_info().1); + } + + let bob_node_id = nodes[1].node().get_our_node_id(); + let carol_node_id = nodes[2].node().get_our_node_id(); + + let alice_bob_scid = nodes[0].node().list_channels().iter().find(|c| c.channel_id == chan_id_alice_bob).unwrap().short_channel_id.unwrap(); + let bob_carol_scid = nodes[1].node().list_channels().iter().find(|c| c.channel_id == chan_id_bob_carol).unwrap().short_channel_id.unwrap(); + + let amt_msat = 1000; + let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), None); + + // Create a 1-hop blinded path for Carol. + let payee_tlvs = UnauthenticatedReceiveTlvs { + payment_secret, + payment_constraints: PaymentConstraints { + max_cltv_expiry: u32::max_value(), + htlc_minimum_msat: amt_msat, + }, + payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}), + }; + let nonce = Nonce([42u8; 16]); + let expanded_key = nodes[2].keys_manager.get_inbound_payment_key(); + let payee_tlvs = payee_tlvs.authenticate(nonce, &expanded_key); + let blinded_path = BlindedPaymentPath::new(&[], carol_node_id, payee_tlvs, u64::MAX, 0, nodes[2].keys_manager, &secp_ctx).unwrap(); + + let route = Route { + paths: vec![Path { + hops: vec![ + // Bob + RouteHop { + pubkey: bob_node_id, + node_features: NodeFeatures::empty(), + short_channel_id: alice_bob_scid, + channel_features: ChannelFeatures::empty(), + fee_msat: 1000, + cltv_expiry_delta: 48, + maybe_announced_channel: false, + }, + + // Carol + RouteHop { + pubkey: carol_node_id, + node_features: NodeFeatures::empty(), + short_channel_id: bob_carol_scid, + channel_features: ChannelFeatures::empty(), + fee_msat: 0, + cltv_expiry_delta: 48, + maybe_announced_channel: false, + } + ], + blinded_tail: Some(BlindedTail { + trampoline_hops: vec![ + // Carol + TrampolineHop { + pubkey: carol_node_id, + node_features: Features::empty(), + fee_msat: amt_msat, + cltv_expiry_delta: 104, + }, + ], + hops: blinded_path.blinded_hops().to_vec(), + blinding_point: blinded_path.blinding_point(), + excess_final_cltv_expiry_delta: 39, + final_value_msat: amt_msat, + }) + }], + route_params: None, + }; + nodes[0].node.send_payment_with_route(route.clone(), payment_hash, RecipientOnionFields::spontaneous_empty(), PaymentId(payment_hash.0)).unwrap(); check_added_monitors!(&nodes[0], 1); + pass_along_route(&nodes[0], &[&[&nodes[1], &nodes[2]]], amt_msat, payment_hash, payment_secret); if success { - pass_along_route(&nodes[0], &[&[&nodes[1], &nodes[2]]], amt_msat, payment_hash, payment_secret); claim_payment(&nodes[0], &[&nodes[1], &nodes[2]], payment_preimage); } else { - let replacement_onion = { - // create a substitute onion where the last Trampoline hop is a forward - let trampoline_secret_key = SecretKey::from_slice(&override_random_bytes).unwrap(); - let recipient_onion_fields = RecipientOnionFields::spontaneous_empty(); + fail_payment(&nodes[0], &[&nodes[1], &nodes[2]], payment_hash); + } +} - let mut blinded_tail = route.paths[0].blinded_tail.clone().unwrap(); +#[test] +fn test_trampoline_single_hop_receive() { + // Simulate a payment of A (0) -> B (1) -> C(Trampoline (blinded intro)) (2) + do_test_trampoline_single_hop_receive(true); - // append some dummy blinded hop so the intro hop looks like a forward - blinded_tail.hops.push(BlindedHop { - blinded_node_id: alice_node_id, - encrypted_payload: vec![], - }); + // Simulate a payment failure of A (0) -> B (1) -> C(Trampoline (blinded forward)) (2) + do_test_trampoline_single_hop_receive(false); +} - let (mut trampoline_payloads, outer_total_msat, outer_starting_htlc_offset) = onion_utils::build_trampoline_onion_payloads(&blinded_tail, amt_msat, &recipient_onion_fields, 32, &None).unwrap(); +fn do_test_trampoline_unblinded_receive(underpay: bool) { + // Test trampoline payment receipt with unblinded final hop. + // Creates custom onion packet where the final trampoline hop uses unblinded receive format + // (not natively supported) to validate payment amount verification. + // - When underpay=false: Payment succeeds with correct amount + // - When underpay=true: Payment fails due to amount mismatch (sends 1/2 expected amount) + // Topology: A (0) -> B (1) C -> (Trampoline receiver) (2) - // pop the last dummy hop - trampoline_payloads.pop(); + const TOTAL_NODE_COUNT: usize = 3; + let secp_ctx = Secp256k1::new(); - let trampoline_onion_keys = onion_utils::construct_trampoline_onion_keys(&secp_ctx, &route.paths[0].blinded_tail.as_ref().unwrap(), &trampoline_secret_key); - let trampoline_packet = onion_utils::construct_trampoline_onion_packet( - trampoline_payloads, - trampoline_onion_keys, - override_random_bytes, - &payment_hash, - None, - ).unwrap(); + let chanmon_cfgs = create_chanmon_cfgs(TOTAL_NODE_COUNT); + let node_cfgs = create_node_cfgs(TOTAL_NODE_COUNT, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(TOTAL_NODE_COUNT, &node_cfgs, &vec![None; TOTAL_NODE_COUNT]); + let mut nodes = create_network(TOTAL_NODE_COUNT, &node_cfgs, &node_chanmgrs); - let outer_session_priv = { - let session_priv_hash = Sha256::hash(&override_random_bytes).to_byte_array(); - SecretKey::from_slice(&session_priv_hash[..]).expect("You broke SHA-256!") - }; - - let (outer_payloads, _, _) = onion_utils::build_onion_payloads(&route.paths[0], outer_total_msat, &recipient_onion_fields, outer_starting_htlc_offset, &None, None, Some(trampoline_packet)).unwrap(); - let outer_onion_keys = onion_utils::construct_onion_keys(&secp_ctx, &route.clone().paths[0], &outer_session_priv); - let outer_packet = onion_utils::construct_onion_packet( - outer_payloads, - outer_onion_keys, - override_random_bytes, - &payment_hash, - ).unwrap(); + let (_, _, chan_id_alice_bob, _) = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); + let (_, _, chan_id_bob_carol, _) = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0); - outer_packet - }; + for i in 0..TOTAL_NODE_COUNT { // connect all nodes' blocks + connect_blocks(&nodes[i], (TOTAL_NODE_COUNT as u32) * CHAN_CONFIRM_DEPTH + 1 - nodes[i].best_block_info().1); + } - let mut events = nodes[0].node.get_and_clear_pending_msg_events(); - assert_eq!(events.len(), 1); - let mut first_message_event = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events); - let mut update_message = match first_message_event { - MessageSendEvent::UpdateHTLCs { ref mut updates, .. } => { - assert_eq!(updates.update_add_htlcs.len(), 1); - updates.update_add_htlcs.get_mut(0) - }, - _ => panic!() + let bob_node_id = nodes[1].node().get_our_node_id(); + let carol_node_id = nodes[2].node().get_our_node_id(); + + let alice_bob_scid = nodes[0].node().list_channels().iter().find(|c| c.channel_id == chan_id_alice_bob).unwrap().short_channel_id.unwrap(); + let bob_carol_scid = nodes[1].node().list_channels().iter().find(|c| c.channel_id == chan_id_bob_carol).unwrap().short_channel_id.unwrap(); + + let amt_msat = 1000; + let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), None); + + let route = Route { + paths: vec![Path { + hops: vec![ + // Bob + RouteHop { + pubkey: bob_node_id, + node_features: NodeFeatures::empty(), + short_channel_id: alice_bob_scid, + channel_features: ChannelFeatures::empty(), + fee_msat: 1000, + cltv_expiry_delta: 48, + maybe_announced_channel: false, + }, + + // Carol + RouteHop { + pubkey: carol_node_id, + node_features: NodeFeatures::empty(), + short_channel_id: bob_carol_scid, + channel_features: ChannelFeatures::empty(), + fee_msat: 0, // no routing fees because it's the final hop + cltv_expiry_delta: 48, + maybe_announced_channel: false, + } + ], + blinded_tail: Some(BlindedTail { + trampoline_hops: vec![ + // Carol + TrampolineHop { + pubkey: carol_node_id, + node_features: Features::empty(), + fee_msat: 0, // no trampoline fee because we are receiving. + cltv_expiry_delta: 72, // blinded hop cltv to be used building the outer onion. + }, + ], + // The blinded path data is unused because we replace the onion of the last hop + hops: vec![BlindedHop { + blinded_node_id: PublicKey::from_slice(&[2; 33]).unwrap(), + encrypted_payload: vec![42; 32] + }], + blinding_point: PublicKey::from_slice(&[2; 33]).unwrap(), + excess_final_cltv_expiry_delta: 39, + final_value_msat: amt_msat, + }) + }], + route_params: None, + }; + + // We need the session priv to construct an invalid onion packet later. + let override_random_bytes = [42; 32]; + *nodes[0].keys_manager.override_random_bytes.lock().unwrap() = Some(override_random_bytes); + nodes[0].node.send_payment_with_route(route.clone(), payment_hash, RecipientOnionFields::spontaneous_empty(), PaymentId(payment_hash.0)).unwrap(); + + let replacement_onion = { + // create a substitute onion where the last Trampoline hop is an unblinded receive, which we + // (deliberately) do not support out of the box, therefore necessitating this workaround + let outer_session_priv = SecretKey::from_slice(&override_random_bytes[..]).unwrap(); + let trampoline_session_priv = onion_utils::compute_trampoline_session_priv(&outer_session_priv); + let recipient_onion_fields = RecipientOnionFields::spontaneous_empty(); + + let blinded_tail = route.paths[0].blinded_tail.clone().unwrap(); + let (_, outer_total_msat, outer_starting_htlc_offset) = onion_utils::build_trampoline_onion_payloads(&blinded_tail, amt_msat, &recipient_onion_fields, 32, &None).unwrap(); + let replacement_payload_amount = if underpay { amt_msat * 2 } else { amt_msat }; + let trampoline_payloads = vec![msgs::OutboundTrampolinePayload::Receive { + payment_data: Some(msgs::FinalOnionHopData { + payment_secret, + total_msat: replacement_payload_amount, + }), + sender_intended_htlc_amt_msat: replacement_payload_amount, + // We will use the same cltv to the outer onion: 72 (blinded tail) + 32 (offset). + cltv_expiry_height: 104, + }]; + + let trampoline_onion_keys = onion_utils::construct_trampoline_onion_keys(&secp_ctx, &route.paths[0].blinded_tail.as_ref().unwrap(), &trampoline_session_priv); + let trampoline_packet = onion_utils::construct_trampoline_onion_packet( + trampoline_payloads, + trampoline_onion_keys, + override_random_bytes, + &payment_hash, + None, + ).unwrap(); + + // Use a different session key to construct the replacement onion packet. Note that the sender isn't aware of + // this and won't be able to decode the fulfill hold times. + let (outer_payloads, _, _) = onion_utils::build_onion_payloads(&route.paths[0], outer_total_msat, &recipient_onion_fields, outer_starting_htlc_offset, &None, None, Some(trampoline_packet)).unwrap(); + let outer_onion_keys = onion_utils::construct_onion_keys(&secp_ctx, &route.clone().paths[0], &outer_session_priv); + let outer_packet = onion_utils::construct_onion_packet( + outer_payloads, + outer_onion_keys, + override_random_bytes, + &payment_hash, + ).unwrap(); + + outer_packet + }; + + check_added_monitors!(&nodes[0], 1); + + let mut events = nodes[0].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + let mut first_message_event = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events); + let mut update_message = match first_message_event { + MessageSendEvent::UpdateHTLCs { ref mut updates, .. } => { + assert_eq!(updates.update_add_htlcs.len(), 1); + updates.update_add_htlcs.get_mut(0) + }, + _ => panic!() + }; + update_message.map(|msg| { + msg.onion_routing_packet = replacement_onion.clone(); + }); + + let route: &[&Node] = &[&nodes[1], &nodes[2]]; + let args = PassAlongPathArgs::new(&nodes[0], route, amt_msat, payment_hash, first_message_event); + + let args = if underpay { + args.with_payment_preimage(payment_preimage) + .without_claimable_event() + .expect_failure(HTLCHandlingFailureType::Receive { payment_hash }) + } else { + args.with_payment_secret(payment_secret) }; - update_message.map(|msg| { - msg.onion_routing_packet = replacement_onion.clone(); - }); - let route: &[&Node] = &[&nodes[1], &nodes[2]]; - let args = PassAlongPathArgs::new(&nodes[0], route, amt_msat, payment_hash, first_message_event) - .with_payment_preimage(payment_preimage) - .without_claimable_event() - .expect_failure(HTLCHandlingFailureType::InvalidOnion); - do_pass_along_path(args); + do_pass_along_path(args); + if underpay { { let unblinded_node_updates = get_htlc_update_msgs!(nodes[2], nodes[1].node.get_our_node_id()); nodes[1].node.handle_update_fail_htlc( @@ -2190,25 +2446,43 @@ fn do_test_trampoline_single_hop_receive(success: bool) { do_commitment_signed_dance(&nodes[0], &nodes[1], &unblinded_node_updates.commitment_signed, false, false); } { + let expected_error_data = amt_msat.to_be_bytes(); let payment_failed_conditions = PaymentFailedConditions::new() - .expected_htlc_error_data(LocalHTLCFailureReason::InvalidOnionPayload, &[0; 0]); - expect_payment_failed_conditions(&nodes[0], payment_hash, true, payment_failed_conditions); + .expected_htlc_error_data(LocalHTLCFailureReason::FinalIncorrectHTLCAmount, &expected_error_data); + expect_payment_failed_conditions(&nodes[0], payment_hash, false, payment_failed_conditions); } + } else { + claim_payment(&nodes[0], &[&nodes[1], &nodes[2]], payment_preimage); } } #[test] -fn test_trampoline_single_hop_receive() { - // Simulate a payment of A (0) -> B (1) -> C(Trampoline (blinded intro)) (2) - do_test_trampoline_single_hop_receive(true); - - // Simulate a payment failure of A (0) -> B (1) -> C(Trampoline (blinded forward)) (2) - do_test_trampoline_single_hop_receive(false); +fn test_trampoline_unblinded_receive_underpay() { + do_test_trampoline_unblinded_receive(true); } #[test] -fn test_trampoline_unblinded_receive() { - // Simulate a payment of A (0) -> B (1) -> C(Trampoline) (2) +fn test_trampoline_unblinded_receive_normal() { + do_test_trampoline_unblinded_receive(false); +} + +#[derive(PartialEq)] +enum TrampolineConstraintFailureScenarios { + TrampolineCLTVGreaterThanOnion, + #[allow(dead_code)] + // TODO: To test amount greater than onion we need the ability + // to forward Trampoline payments. + TrampolineAmountGreaterThanOnion, +} + +fn do_test_trampoline_unblinded_receive_constraint_failure(failure_scenario: TrampolineConstraintFailureScenarios) { + // Test trampoline payment constraint validation failures with unblinded receive format. + // Creates deliberately invalid trampoline payments to verify constraint enforcement: + // - TrampolineCLTVGreaterThanOnion: Trampoline CLTV exceeds outer onion requirements + // - TrampolineAmountGreaterThanOnion: Trampoline amount exceeds outer onion value + // Uses custom onion construction to simulate constraint violations that should trigger + // specific HTLC failure codes (FinalIncorrectCLTVExpiry or FinalIncorrectHTLCAmount). + // Topology: A (0) -> B (1) -> C (Trampoline receiver) (2) const TOTAL_NODE_COUNT: usize = 3; let secp_ctx = Secp256k1::new(); @@ -2257,6 +2531,15 @@ fn test_trampoline_unblinded_receive() { &secp_ctx, path.into_iter(), &carol_alice_trampoline_session_priv, ).unwrap(); + // We decide an arbitrary ctlv delta for the blinded hop that will be the only cltv delta + // in the blinded tail. + let blinded_hop_cltv = if failure_scenario == TrampolineConstraintFailureScenarios::TrampolineCLTVGreaterThanOnion { 52 } else { 72 }; + // Then when building the trampoline hop we use an arbitrary cltv delta offset to be used + // when re-building the outer trampoline onion. + let starting_cltv_offset_trampoline = 32; + // Finally we decide a forced cltv delta expiry for the trampoline hop itself. + // This one will be compared against the outer onion ctlv delta. + let forced_trampoline_cltv_delta = 104; let route = Route { paths: vec![Path { hops: vec![ @@ -2277,7 +2560,7 @@ fn test_trampoline_unblinded_receive() { node_features: NodeFeatures::empty(), short_channel_id: bob_carol_scid, channel_features: ChannelFeatures::empty(), - fee_msat: 0, + fee_msat: 0, // no routing fees because it's the final hop cltv_expiry_delta: 48, maybe_announced_channel: false, } @@ -2289,11 +2572,12 @@ fn test_trampoline_unblinded_receive() { pubkey: carol_node_id, node_features: Features::empty(), fee_msat: amt_msat, - cltv_expiry_delta: 24, + cltv_expiry_delta: blinded_hop_cltv, // blinded tail ctlv delta. }, ], hops: carol_blinded_hops, blinding_point: carol_blinding_point, + // This will be ignored because we force the cltv_expiry of the trampoline hop. excess_final_cltv_expiry_delta: 39, final_value_msat: amt_msat, }) @@ -2301,49 +2585,45 @@ fn test_trampoline_unblinded_receive() { route_params: None, }; + let override_random_bytes = [42; 32]; + *nodes[0].keys_manager.override_random_bytes.lock().unwrap() = Some(override_random_bytes); + nodes[0].node.send_payment_with_route(route.clone(), payment_hash, RecipientOnionFields::spontaneous_empty(), PaymentId(payment_hash.0)).unwrap(); let replacement_onion = { // create a substitute onion where the last Trampoline hop is an unblinded receive, which we // (deliberately) do not support out of the box, therefore necessitating this workaround - let trampoline_secret_key = secret_from_hex("0134928f7b7ca6769080d70f16be84c812c741f545b49a34db47ce338a205799"); - let prng_seed = secret_from_hex("fe02b4b9054302a3ddf4e1e9f7c411d644aebbd295218ab009dca94435f775a9"); + let outer_session_priv = SecretKey::from_slice(&override_random_bytes[..]).unwrap(); + let trampoline_session_priv = onion_utils::compute_trampoline_session_priv(&outer_session_priv); let recipient_onion_fields = RecipientOnionFields::spontaneous_empty(); let blinded_tail = route.paths[0].blinded_tail.clone().unwrap(); - let (mut trampoline_payloads, outer_total_msat, outer_starting_htlc_offset) = onion_utils::build_trampoline_onion_payloads(&blinded_tail, amt_msat, &recipient_onion_fields, 32, &None).unwrap(); - // pop the last dummy hop - trampoline_payloads.pop(); - - trampoline_payloads.push(msgs::OutboundTrampolinePayload::Receive { + let (_ , outer_total_msat, outer_starting_htlc_offset) = onion_utils::build_trampoline_onion_payloads(&blinded_tail, amt_msat, &recipient_onion_fields, starting_cltv_offset_trampoline, &None).unwrap(); + let trampoline_payloads = vec![msgs::OutboundTrampolinePayload::Receive { payment_data: Some(msgs::FinalOnionHopData { payment_secret, total_msat: amt_msat, }), sender_intended_htlc_amt_msat: amt_msat, - cltv_expiry_height: 104, - }); + cltv_expiry_height: forced_trampoline_cltv_delta, + }]; - let trampoline_onion_keys = onion_utils::construct_trampoline_onion_keys(&secp_ctx, &route.paths[0].blinded_tail.as_ref().unwrap(), &trampoline_secret_key); + let trampoline_onion_keys = onion_utils::construct_trampoline_onion_keys(&secp_ctx, &route.paths[0].blinded_tail.as_ref().unwrap(), &trampoline_session_priv); let trampoline_packet = onion_utils::construct_trampoline_onion_packet( trampoline_payloads, trampoline_onion_keys, - prng_seed.secret_bytes(), + override_random_bytes, &payment_hash, None, ).unwrap(); - // Use a different session key to construct the replacement onion packet. Note that the sender isn't aware of - // this and won't be able to decode the fulfill hold times. - let outer_session_priv = secret_from_hex("e52c20461ed7acd46c4e7b591a37610519179482887bd73bf3b94617f8f03677"); - let (outer_payloads, _, _) = onion_utils::build_onion_payloads(&route.paths[0], outer_total_msat, &recipient_onion_fields, outer_starting_htlc_offset, &None, None, Some(trampoline_packet)).unwrap(); let outer_onion_keys = onion_utils::construct_onion_keys(&secp_ctx, &route.clone().paths[0], &outer_session_priv); let outer_packet = onion_utils::construct_onion_packet( outer_payloads, outer_onion_keys, - prng_seed.secret_bytes(), + override_random_bytes, &payment_hash, ).unwrap(); @@ -2368,10 +2648,199 @@ fn test_trampoline_unblinded_receive() { let route: &[&Node] = &[&nodes[1], &nodes[2]]; let args = PassAlongPathArgs::new(&nodes[0], route, amt_msat, payment_hash, first_message_event) - .with_payment_secret(payment_secret); + .with_payment_preimage(payment_preimage) + .without_claimable_event() + .expect_failure(HTLCHandlingFailureType::Receive { payment_hash }); + do_pass_along_path(args); + { + let unblinded_node_updates = get_htlc_update_msgs!(nodes[2], nodes[1].node.get_our_node_id()); + nodes[1].node.handle_update_fail_htlc( + nodes[2].node.get_our_node_id(), &unblinded_node_updates.update_fail_htlcs[0] + ); + do_commitment_signed_dance(&nodes[1], &nodes[2], &unblinded_node_updates.commitment_signed, true, false); + } + { + let unblinded_node_updates = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()); + nodes[0].node.handle_update_fail_htlc( + nodes[1].node.get_our_node_id(), &unblinded_node_updates.update_fail_htlcs[0] + ); + do_commitment_signed_dance(&nodes[0], &nodes[1], &unblinded_node_updates.commitment_signed, false, false); + } - claim_payment(&nodes[0], &[&nodes[1], &nodes[2]], payment_preimage); + match failure_scenario { + TrampolineConstraintFailureScenarios::TrampolineAmountGreaterThanOnion => { + let expected_error_data = amt_msat.to_be_bytes(); + let payment_failed_conditions = PaymentFailedConditions::new() + .expected_htlc_error_data(LocalHTLCFailureReason::FinalIncorrectHTLCAmount, &expected_error_data); + expect_payment_failed_conditions(&nodes[0], payment_hash, false, payment_failed_conditions); + }, + TrampolineConstraintFailureScenarios::TrampolineCLTVGreaterThanOnion => { + // The amount of the outer onion cltv delta plus the trampoline offset. + let expected_error_data = (blinded_hop_cltv + starting_cltv_offset_trampoline).to_be_bytes(); + let payment_failed_conditions = PaymentFailedConditions::new() + .expected_htlc_error_data(LocalHTLCFailureReason::FinalIncorrectCLTVExpiry, &expected_error_data); + expect_payment_failed_conditions(&nodes[0], payment_hash, false, payment_failed_conditions); + } + } +} + +fn do_test_trampoline_blinded_receive_constraint_failure(failure_scenario: TrampolineConstraintFailureScenarios) { + // Test trampoline payment constraint validation failures with blinded receive format. + // Creates deliberately invalid trampoline payments to verify constraint enforcement: + // - TrampolineCLTVGreaterThanOnion: Trampoline CLTV exceeds outer onion requirements + // - TrampolineAmountGreaterThanOnion: Trampoline amount exceeds outer onion value + // Topology: A (0) -> B (1) -> C (Trampoline receiver inside blinded path) (2) + + const TOTAL_NODE_COUNT: usize = 3; + let secp_ctx = Secp256k1::new(); + + let chanmon_cfgs = create_chanmon_cfgs(TOTAL_NODE_COUNT); + let node_cfgs = create_node_cfgs(TOTAL_NODE_COUNT, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(TOTAL_NODE_COUNT, &node_cfgs, &vec![None; TOTAL_NODE_COUNT]); + let mut nodes = create_network(TOTAL_NODE_COUNT, &node_cfgs, &node_chanmgrs); + + let (_, _, chan_id_alice_bob, _) = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); + let (_, _, chan_id_bob_carol, _) = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0); + + for i in 0..TOTAL_NODE_COUNT { // connect all nodes' blocks + connect_blocks(&nodes[i], (TOTAL_NODE_COUNT as u32) * CHAN_CONFIRM_DEPTH + 1 - nodes[i].best_block_info().1); + } + + let bob_node_id = nodes[1].node().get_our_node_id(); + let carol_node_id = nodes[2].node().get_our_node_id(); + + let alice_bob_scid = nodes[0].node().list_channels().iter().find(|c| c.channel_id == chan_id_alice_bob).unwrap().short_channel_id.unwrap(); + let bob_carol_scid = nodes[1].node().list_channels().iter().find(|c| c.channel_id == chan_id_bob_carol).unwrap().short_channel_id.unwrap(); + let amt_msat = 1000; + let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), None); + + let alice_carol_trampoline_shared_secret = secret_from_hex("a0f4b8d7b6c2d0ffdfaf718f76e9decaef4d9fb38a8c4addb95c4007cc3eee03"); + let carol_blinding_point = PublicKey::from_secret_key(&secp_ctx, &alice_carol_trampoline_shared_secret); + let payee_tlvs = UnauthenticatedReceiveTlvs { + payment_secret, + payment_constraints: PaymentConstraints { + max_cltv_expiry: u32::max_value(), + htlc_minimum_msat: amt_msat, + }, + payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}), + }; + + let nonce = Nonce([42u8; 16]); + let expanded_key = nodes[2].keys_manager.get_inbound_payment_key(); + let payee_tlvs = payee_tlvs.authenticate(nonce, &expanded_key); + let carol_unblinded_tlvs = payee_tlvs.encode(); + + // Blinded path is Carol as recipient. + let path = [((carol_node_id, None), WithoutLength(&carol_unblinded_tlvs))]; + let blinded_hops = blinded_path::utils::construct_blinded_hops( + &secp_ctx, path.into_iter(), &alice_carol_trampoline_shared_secret, + ).unwrap(); + + // We decide an arbitrary ctlv delta for the blinded hop that will be the only cltv delta + // in the blinded tail. + let blinded_hop_cltv = if failure_scenario == TrampolineConstraintFailureScenarios::TrampolineCLTVGreaterThanOnion { 2 } else { 144 }; + + let route = Route { + paths: vec![Path { + hops: vec![ + // Bob + RouteHop { + pubkey: bob_node_id, + node_features: NodeFeatures::empty(), + short_channel_id: alice_bob_scid, + channel_features: ChannelFeatures::empty(), + fee_msat: 1000, // forwarding fee to Carol + cltv_expiry_delta: 48, + maybe_announced_channel: false, + }, + + // Carol + RouteHop { + pubkey: carol_node_id, + node_features: NodeFeatures::empty(), + short_channel_id: bob_carol_scid, + channel_features: ChannelFeatures::empty(), + // fee for the usage of the entire blinded path, including Trampoline. + // In this case is zero as we are the recipient of the payment. + fee_msat: 0, + cltv_expiry_delta: 48, + maybe_announced_channel: false, + } + ], + blinded_tail: Some(BlindedTail { + trampoline_hops: vec![ + // Carol + TrampolineHop { + pubkey: carol_node_id, + node_features: Features::empty(), + fee_msat: amt_msat, + cltv_expiry_delta: blinded_hop_cltv, + }, + + ], + hops: blinded_hops, + blinding_point: carol_blinding_point, + excess_final_cltv_expiry_delta: 39, + final_value_msat: amt_msat, + }) + }], + route_params: None, + }; + + nodes[0].node.send_payment_with_route(route.clone(), payment_hash, RecipientOnionFields::spontaneous_empty(), PaymentId(payment_hash.0)).unwrap(); + check_added_monitors!(&nodes[0], 1); + + let mut events = nodes[0].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + let first_message_event = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events); + + let route: &[&Node] = &[&nodes[1], &nodes[2]]; + let args = PassAlongPathArgs::new(&nodes[0], route, amt_msat, payment_hash, first_message_event) + .with_payment_preimage(payment_preimage) + .without_claimable_event() + .expect_failure(HTLCHandlingFailureType::Receive { payment_hash }); + + do_pass_along_path(args); + { + let unblinded_node_updates = get_htlc_update_msgs!(nodes[2], nodes[1].node.get_our_node_id()); + nodes[1].node.handle_update_fail_htlc( + nodes[2].node.get_our_node_id(), &unblinded_node_updates.update_fail_htlcs[0] + ); + do_commitment_signed_dance(&nodes[1], &nodes[2], &unblinded_node_updates.commitment_signed, true, false); + } + { + let unblinded_node_updates = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()); + nodes[0].node.handle_update_fail_htlc( + nodes[1].node.get_our_node_id(), &unblinded_node_updates.update_fail_htlcs[0] + ); + do_commitment_signed_dance(&nodes[0], &nodes[1], &unblinded_node_updates.commitment_signed, false, false); + } + + // We don't share the error data when receiving inside a blinded path. + let expected_error_data = [0; 32]; + match failure_scenario { + TrampolineConstraintFailureScenarios::TrampolineAmountGreaterThanOnion => { + let payment_failed_conditions = PaymentFailedConditions::new() + .expected_htlc_error_data(LocalHTLCFailureReason::InvalidOnionBlinding, &expected_error_data); + expect_payment_failed_conditions(&nodes[0], payment_hash, true, payment_failed_conditions); + }, + TrampolineConstraintFailureScenarios::TrampolineCLTVGreaterThanOnion => { + let payment_failed_conditions = PaymentFailedConditions::new() + .expected_htlc_error_data(LocalHTLCFailureReason::InvalidOnionBlinding, &expected_error_data); + expect_payment_failed_conditions(&nodes[0], payment_hash, true, payment_failed_conditions); + } + } +} + +#[test] +fn test_trampoline_enforced_constraint_cltv() { + do_test_trampoline_unblinded_receive_constraint_failure(TrampolineConstraintFailureScenarios::TrampolineCLTVGreaterThanOnion); +} + +#[test] +fn test_trampoline_blinded_receive_enforced_constraint_cltv() { + do_test_trampoline_blinded_receive_constraint_failure(TrampolineConstraintFailureScenarios::TrampolineCLTVGreaterThanOnion); } #[test] diff --git a/lightning/src/ln/chan_utils.rs b/lightning/src/ln/chan_utils.rs index 557a988cc92..545d5296f61 100644 --- a/lightning/src/ln/chan_utils.rs +++ b/lightning/src/ln/chan_utils.rs @@ -236,7 +236,7 @@ pub(crate) fn commit_tx_fee_sat(feerate_per_kw: u32, num_htlcs: usize, channel_t } /// Returns the fees for success and timeout second stage HTLC transactions. -pub(super) fn second_stage_tx_fees_sat( +pub(crate) fn second_stage_tx_fees_sat( channel_type: &ChannelTypeFeatures, feerate_sat_per_1000_weight: u32, ) -> (u64, u64) { if channel_type.supports_anchors_zero_fee_htlc_tx() @@ -1035,7 +1035,7 @@ impl ChannelTransactionParameters { /// /// self.is_populated() must be true before calling this function. #[rustfmt::skip] - pub fn as_holder_broadcastable(&self) -> DirectedChannelTransactionParameters { + pub fn as_holder_broadcastable(&self) -> DirectedChannelTransactionParameters<'_> { assert!(self.is_populated(), "self.late_parameters must be set before using as_holder_broadcastable"); DirectedChannelTransactionParameters { inner: self, @@ -1048,7 +1048,7 @@ impl ChannelTransactionParameters { /// /// self.is_populated() must be true before calling this function. #[rustfmt::skip] - pub fn as_counterparty_broadcastable(&self) -> DirectedChannelTransactionParameters { + pub fn as_counterparty_broadcastable(&self) -> DirectedChannelTransactionParameters<'_> { assert!(self.is_populated(), "self.late_parameters must be set before using as_counterparty_broadcastable"); DirectedChannelTransactionParameters { inner: self, @@ -1435,7 +1435,7 @@ impl ClosingTransaction { /// /// This should only be used if you fully trust the builder of this object. It should not /// be used by an external signer - instead use the verify function. - pub fn trust(&self) -> TrustedClosingTransaction { + pub fn trust(&self) -> TrustedClosingTransaction<'_> { TrustedClosingTransaction { inner: self } } @@ -1446,7 +1446,7 @@ impl ClosingTransaction { /// An external validating signer must call this method before signing /// or using the built transaction. #[rustfmt::skip] - pub fn verify(&self, funding_outpoint: OutPoint) -> Result { + pub fn verify(&self, funding_outpoint: OutPoint) -> Result, ()> { let built = build_closing_transaction( self.to_holder_value_sat, self.to_counterparty_value_sat, self.to_holder_script.clone(), self.to_counterparty_script.clone(), @@ -1971,7 +1971,7 @@ impl CommitmentTransaction { /// /// This should only be used if you fully trust the builder of this object. It should not /// be used by an external signer - instead use the verify function. - pub fn trust(&self) -> TrustedCommitmentTransaction { + pub fn trust(&self) -> TrustedCommitmentTransaction<'_> { TrustedCommitmentTransaction { inner: self } } @@ -1982,7 +1982,7 @@ impl CommitmentTransaction { /// An external validating signer must call this method before signing /// or using the built transaction. #[rustfmt::skip] - pub fn verify(&self, channel_parameters: &DirectedChannelTransactionParameters, secp_ctx: &Secp256k1) -> Result { + pub fn verify(&self, channel_parameters: &DirectedChannelTransactionParameters, secp_ctx: &Secp256k1) -> Result, ()> { // This is the only field of the key cache that we trust let per_commitment_point = &self.keys.per_commitment_point; let keys = TxCreationKeys::from_channel_static_keys(per_commitment_point, channel_parameters.broadcaster_pubkeys(), channel_parameters.countersignatory_pubkeys(), secp_ctx); diff --git a/lightning/src/ln/chanmon_update_fail_tests.rs b/lightning/src/ln/chanmon_update_fail_tests.rs index 878608e14f6..4aa587ab111 100644 --- a/lightning/src/ln/chanmon_update_fail_tests.rs +++ b/lightning/src/ln/chanmon_update_fail_tests.rs @@ -3895,6 +3895,11 @@ fn do_test_durable_preimages_on_closed_channel( persister.set_update_ret(ChannelMonitorUpdateStatus::InProgress); } } + if !close_chans_before_reload { + check_closed_broadcast(&nodes[1], 1, true); + let reason = ClosureReason::CommitmentTxConfirmed; + check_closed_event(&nodes[1], 1, reason, false, &[node_a_id], 100000); + } nodes[1].node.timer_tick_occurred(); check_added_monitors(&nodes[1], mons_added); @@ -3907,12 +3912,6 @@ fn do_test_durable_preimages_on_closed_channel( .unwrap(); check_spends!(bs_preimage_tx, as_closing_tx[0]); - if !close_chans_before_reload { - check_closed_broadcast(&nodes[1], 1, true); - let reason = ClosureReason::CommitmentTxConfirmed; - check_closed_event(&nodes[1], 1, reason, false, &[node_a_id], 100000); - } - mine_transactions(&nodes[0], &[&as_closing_tx[0], bs_preimage_tx]); check_closed_broadcast(&nodes[0], 1, true); expect_payment_sent(&nodes[0], payment_preimage, None, true, true); @@ -4048,7 +4047,7 @@ fn do_test_reload_mon_update_completion_actions(close_during_reload: bool) { let mut events = nodes[1].node.get_and_clear_pending_events(); assert_eq!(events.len(), if close_during_reload { 2 } else { 1 }); expect_payment_forwarded( - events.pop().unwrap(), + events.remove(0), &nodes[1], &nodes[0], &nodes[2], @@ -4218,13 +4217,17 @@ fn test_glacial_peer_cant_hang() { do_test_glacial_peer_cant_hang(true); } -#[test] -fn test_partial_claim_mon_update_compl_actions() { +fn do_test_partial_claim_mon_update_compl_actions(reload_a: bool, reload_b: bool) { // Test that if we have an MPP claim that we ensure the preimage for the claim is retained in // all the `ChannelMonitor`s until the preimage reaches every `ChannelMonitor` for a channel // which was a part of the MPP. let chanmon_cfgs = create_chanmon_cfgs(4); let node_cfgs = create_node_cfgs(4, &chanmon_cfgs); + + let (persister, persister_2, persister_3); + let (new_chain_mon, new_chain_mon_2, new_chain_mon_3); + let (nodes_3_reload, nodes_3_reload_2, nodes_3_reload_3); + let node_chanmgrs = create_node_chanmgrs(4, &node_cfgs, &[None, None, None, None]); let mut nodes = create_network(4, &node_cfgs, &node_chanmgrs); @@ -4253,6 +4256,8 @@ fn test_partial_claim_mon_update_compl_actions() { let paths = &[&[&nodes[1], &nodes[3]][..], &[&nodes[2], &nodes[3]][..]]; send_along_route_with_secret(&nodes[0], route, paths, 200_000, payment_hash, payment_secret); + // Store the monitor for channel 4 without the preimage to use on reload + let chan_4_monitor_serialized = get_monitor!(nodes[3], chan_4_id).encode(); // Claim along both paths, but only complete one of the two monitor updates. chanmon_cfgs[3].persister.set_update_ret(ChannelMonitorUpdateStatus::InProgress); chanmon_cfgs[3].persister.set_update_ret(ChannelMonitorUpdateStatus::InProgress); @@ -4264,7 +4269,13 @@ fn test_partial_claim_mon_update_compl_actions() { // Complete the 1<->3 monitor update and play the commitment_signed dance forward until it // blocks. nodes[3].chain_monitor.complete_sole_pending_chan_update(&chan_3_id); - expect_payment_claimed!(&nodes[3], payment_hash, 200_000); + let payment_claimed = nodes[3].node.get_and_clear_pending_events(); + assert_eq!(payment_claimed.len(), 1, "{payment_claimed:?}"); + if let Event::PaymentClaimed { payment_hash: ev_hash, .. } = &payment_claimed[0] { + assert_eq!(*ev_hash, payment_hash); + } else { + panic!("{payment_claimed:?}"); + } let mut updates = get_htlc_update_msgs(&nodes[3], &node_b_id); nodes[1].node.handle_update_fulfill_htlc(node_d_id, updates.update_fulfill_htlcs.remove(0)); @@ -4283,15 +4294,41 @@ fn test_partial_claim_mon_update_compl_actions() { check_added_monitors(&nodes[3], 0); assert!(nodes[3].node.get_and_clear_pending_msg_events().is_empty()); + if reload_a { + // After a reload (with the monitor not yet fully updated), the RAA should still be blocked + // waiting until the monitor update completes. + let node_ser = nodes[3].node.encode(); + let chan_3_monitor_serialized = get_monitor!(nodes[3], chan_3_id).encode(); + let mons = &[&chan_3_monitor_serialized[..], &chan_4_monitor_serialized[..]]; + reload_node!(nodes[3], &node_ser, mons, persister, new_chain_mon, nodes_3_reload); + // The final update to channel 4 should be replayed. + persister.set_update_ret(ChannelMonitorUpdateStatus::InProgress); + assert!(nodes[3].node.get_and_clear_pending_msg_events().is_empty()); + check_added_monitors(&nodes[3], 1); + + // Because the HTLCs aren't yet cleared, the PaymentClaimed event will be replayed on + // restart. + let second_payment_claimed = nodes[3].node.get_and_clear_pending_events(); + assert_eq!(payment_claimed, second_payment_claimed); + + nodes[1].node.peer_disconnected(node_d_id); + nodes[2].node.peer_disconnected(node_d_id); + reconnect_nodes(ReconnectArgs::new(&nodes[1], &nodes[3])); + reconnect_nodes(ReconnectArgs::new(&nodes[2], &nodes[3])); + + assert!(nodes[3].node.get_and_clear_pending_msg_events().is_empty()); + } + // Now double-check that the preimage is still in the 1<->3 channel and complete the pending // monitor update, allowing node 3 to claim the payment on the 2<->3 channel. This also // unblocks the 1<->3 channel, allowing node 3 to release the two blocked monitor updates and // respond to the final commitment_signed. assert!(get_monitor!(nodes[3], chan_3_id).get_stored_preimages().contains_key(&payment_hash)); + assert!(nodes[3].node.get_and_clear_pending_events().is_empty()); nodes[3].chain_monitor.complete_sole_pending_chan_update(&chan_4_id); let mut ds_msgs = nodes[3].node.get_and_clear_pending_msg_events(); - assert_eq!(ds_msgs.len(), 2); + assert_eq!(ds_msgs.len(), 2, "{ds_msgs:?}"); check_added_monitors(&nodes[3], 2); match remove_first_msg_event_to_node(&node_b_id, &mut ds_msgs) { @@ -4335,13 +4372,86 @@ fn test_partial_claim_mon_update_compl_actions() { assert!(get_monitor!(nodes[3], chan_3_id).get_stored_preimages().contains_key(&payment_hash)); assert!(get_monitor!(nodes[3], chan_4_id).get_stored_preimages().contains_key(&payment_hash)); + if reload_b { + // Ensure that the channel pause logic doesn't accidentally get restarted after a second + // reload once the HTLCs for the first payment have been removed and the monitors + // completed. + let node_ser = nodes[3].node.encode(); + let chan_3_monitor_serialized = get_monitor!(nodes[3], chan_3_id).encode(); + let chan_4_monitor_serialized = get_monitor!(nodes[3], chan_4_id).encode(); + let mons = &[&chan_3_monitor_serialized[..], &chan_4_monitor_serialized[..]]; + reload_node!(nodes[3], &node_ser, mons, persister_2, new_chain_mon_2, nodes_3_reload_2); + check_added_monitors(&nodes[3], 0); + + nodes[1].node.peer_disconnected(node_d_id); + nodes[2].node.peer_disconnected(node_d_id); + reconnect_nodes(ReconnectArgs::new(&nodes[1], &nodes[3])); + reconnect_nodes(ReconnectArgs::new(&nodes[2], &nodes[3])); + + assert!(nodes[3].node.get_and_clear_pending_msg_events().is_empty()); + + // Because the HTLCs aren't yet cleared, the PaymentClaimed event will be replayed on + // restart. + let third_payment_claimed = nodes[3].node.get_and_clear_pending_events(); + assert_eq!(payment_claimed, third_payment_claimed); + } + send_payment(&nodes[1], &[&nodes[3]], 100_000); assert!(!get_monitor!(nodes[3], chan_3_id).get_stored_preimages().contains_key(&payment_hash)); + if reload_b { + // Ensure that the channel pause logic doesn't accidentally get restarted after a second + // reload once the HTLCs for the first payment have been removed and the monitors + // completed, even if only one of the two monitors still knows about the first payment. + let node_ser = nodes[3].node.encode(); + let chan_3_monitor_serialized = get_monitor!(nodes[3], chan_3_id).encode(); + let chan_4_monitor_serialized = get_monitor!(nodes[3], chan_4_id).encode(); + let mons = &[&chan_3_monitor_serialized[..], &chan_4_monitor_serialized[..]]; + reload_node!(nodes[3], &node_ser, mons, persister_3, new_chain_mon_3, nodes_3_reload_3); + check_added_monitors(&nodes[3], 0); + + nodes[1].node.peer_disconnected(node_d_id); + nodes[2].node.peer_disconnected(node_d_id); + reconnect_nodes(ReconnectArgs::new(&nodes[1], &nodes[3])); + reconnect_nodes(ReconnectArgs::new(&nodes[2], &nodes[3])); + + assert!(nodes[3].node.get_and_clear_pending_msg_events().is_empty()); + + // Because the HTLCs aren't yet cleared, the PaymentClaimed events for both payments will + // be replayed on restart. + // Use this as an opportunity to check the payment_ids are unique. + let mut events = nodes[3].node.get_and_clear_pending_events(); + assert_eq!(events.len(), 2); + events.retain(|ev| *ev != payment_claimed[0]); + assert_eq!(events.len(), 1); + if let Event::PaymentClaimed { payment_id: original_payment_id, .. } = &payment_claimed[0] { + assert!(original_payment_id.is_some()); + if let Event::PaymentClaimed { amount_msat, payment_id, .. } = &events[0] { + assert!(payment_id.is_some()); + assert_ne!(original_payment_id, payment_id); + assert_eq!(*amount_msat, 100_000); + } else { + panic!("{events:?}"); + } + } else { + panic!("{events:?}"); + } + + send_payment(&nodes[1], &[&nodes[3]], 100_000); + } + send_payment(&nodes[2], &[&nodes[3]], 100_000); assert!(!get_monitor!(nodes[3], chan_4_id).get_stored_preimages().contains_key(&payment_hash)); } +#[test] +fn test_partial_claim_mon_update_compl_actions() { + do_test_partial_claim_mon_update_compl_actions(true, true); + do_test_partial_claim_mon_update_compl_actions(true, false); + do_test_partial_claim_mon_update_compl_actions(false, true); + do_test_partial_claim_mon_update_compl_actions(false, false); +} + #[test] fn test_claim_to_closed_channel_blocks_forwarded_preimage_removal() { // One of the last features for async persistence we implemented was the correct blocking of @@ -4380,9 +4490,9 @@ fn test_claim_to_closed_channel_blocks_forwarded_preimage_removal() { assert_eq!(as_commit_tx.len(), 1); mine_transaction(&nodes[1], &as_commit_tx[0]); + check_closed_broadcast!(nodes[1], true); check_added_monitors!(nodes[1], 1); check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [node_a_id], 1000000); - check_closed_broadcast!(nodes[1], true); // Now that B has a pending forwarded payment across it with the inbound edge on-chain, claim // the payment on C and give B the preimage for it. @@ -4456,9 +4566,9 @@ fn test_claim_to_closed_channel_blocks_claimed_event() { assert_eq!(as_commit_tx.len(), 1); mine_transaction(&nodes[1], &as_commit_tx[0]); + check_closed_broadcast!(nodes[1], true); check_added_monitors!(nodes[1], 1); check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [node_a_id], 1000000); - check_closed_broadcast!(nodes[1], true); // Now that B has a pending payment with the inbound HTLC on a closed channel, claim the // payment on disk, but don't let the `ChannelMonitorUpdate` complete. This should prevent the @@ -4583,6 +4693,23 @@ fn test_single_channel_multiple_mpp() { // `update_fulfill_htlc`/`commitment_signed` pair to pass to our counterparty. do_a_write.send(()).unwrap(); + let event_node: &'static TestChannelManager<'static, 'static> = + unsafe { std::mem::transmute(nodes[8].node as &TestChannelManager) }; + let thrd_event = std::thread::spawn(move || { + let mut have_event = false; + while !have_event { + let mut events = event_node.get_and_clear_pending_events(); + assert!(events.len() == 1 || events.len() == 0); + if events.len() == 1 { + if let Event::PaymentClaimed { .. } = events[0] { + } else { + panic!("Unexpected event {events:?}"); + } + have_event = true; + } + } + }); + // Then fetch the `update_fulfill_htlc`/`commitment_signed`. Note that the // `get_and_clear_pending_msg_events` will immediately hang trying to take a peer lock which // `claim_funds` is holding. Thus, we release a second write after a small sleep in the @@ -4602,7 +4729,11 @@ fn test_single_channel_multiple_mpp() { }); block_thrd2.store(false, Ordering::Release); let mut first_updates = get_htlc_update_msgs(&nodes[8], &node_h_id); + + // Thread 2 could unblock first, or it could get blocked waiting on us to process a + // `PaymentClaimed` event. Either way, wait until both have finished. thrd2.join().unwrap(); + thrd_event.join().unwrap(); // Disconnect node 6 from all its peers so it doesn't bother to fail the HTLCs back nodes[7].node.peer_disconnected(node_b_id); @@ -4649,8 +4780,6 @@ fn test_single_channel_multiple_mpp() { thrd4.join().unwrap(); thrd.join().unwrap(); - expect_payment_claimed!(nodes[8], payment_hash, 50_000_000); - // At the end, we should have 7 ChannelMonitorUpdates - 6 for HTLC claims, and one for the // above `revoke_and_ack`. check_added_monitors(&nodes[8], 7); diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 0796369d4c6..559a25442b2 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -8,13 +8,13 @@ // licenses. use bitcoin::absolute::LockTime; -use bitcoin::amount::Amount; +use bitcoin::amount::{Amount, SignedAmount}; use bitcoin::consensus::encode; use bitcoin::constants::ChainHash; use bitcoin::script::{Builder, Script, ScriptBuf, WScriptHash}; use bitcoin::sighash::EcdsaSighashType; -use bitcoin::transaction::{Transaction, TxIn, TxOut}; -use bitcoin::Weight; +use bitcoin::transaction::{Transaction, TxOut}; +use bitcoin::Witness; use bitcoin::hash_types::{BlockHash, Txid}; use bitcoin::hashes::sha256::Hash as Sha256; @@ -24,9 +24,9 @@ use bitcoin::hashes::Hash; use bitcoin::secp256k1::constants::PUBLIC_KEY_SIZE; use bitcoin::secp256k1::{ecdsa::Signature, Secp256k1}; use bitcoin::secp256k1::{PublicKey, SecretKey}; -use bitcoin::{secp256k1, sighash}; +use bitcoin::{secp256k1, sighash, TxIn}; #[cfg(splicing)] -use bitcoin::{Sequence, Witness}; +use bitcoin::{FeeRate, Sequence}; use crate::chain::chaininterface::{ fee_for_weight, ConfirmationTarget, FeeEstimator, LowerBoundedFeeEstimator, @@ -37,16 +37,15 @@ use crate::chain::channelmonitor::{ }; use crate::chain::transaction::{OutPoint, TransactionData}; use crate::chain::BestBlock; -use crate::events::bump_transaction::BASE_INPUT_WEIGHT; -use crate::events::{ClosureReason, Event}; +use crate::events::bump_transaction::{BASE_INPUT_WEIGHT, EMPTY_SCRIPT_SIG_WEIGHT}; +use crate::events::ClosureReason; use crate::ln::chan_utils; -#[cfg(splicing)] -use crate::ln::chan_utils::FUNDING_TRANSACTION_WITNESS_WEIGHT; use crate::ln::chan_utils::{ get_commitment_transaction_number_obscure_factor, max_htlcs, second_stage_tx_fees_sat, selected_commitment_sat_per_1000_weight, ChannelPublicKeys, ChannelTransactionParameters, ClosingTransaction, CommitmentTransaction, CounterpartyChannelTransactionParameters, CounterpartyCommitmentSecrets, HTLCOutputInCommitment, HolderCommitmentTransaction, + FUNDING_TRANSACTION_WITNESS_WEIGHT, }; use crate::ln::channel_state::{ ChannelShutdownState, CounterpartyForwardingInfo, InboundHTLCDetails, InboundHTLCStateDetails, @@ -57,6 +56,9 @@ use crate::ln::channelmanager::{ PaymentClaimDetails, PendingHTLCInfo, PendingHTLCStatus, RAACommitmentOrder, SentHTLCId, BREAKDOWN_TIMEOUT, MAX_LOCAL_BREAKDOWN_TIMEOUT, MIN_CLTV_EXPIRY_DELTA, }; +use crate::ln::funding::FundingTxInput; +#[cfg(splicing)] +use crate::ln::funding::SpliceContribution; #[cfg(splicing)] use crate::ln::interactivetxs::{ calculate_change_output_value, AbortReason, InteractiveTxMessageSend, @@ -72,9 +74,11 @@ use crate::ln::onion_utils::{ }; use crate::ln::script::{self, ShutdownScript}; use crate::ln::types::ChannelId; +#[cfg(splicing)] +use crate::ln::LN_MAX_MSG_LEN; use crate::routing::gossip::NodeId; use crate::sign::ecdsa::EcdsaChannelSigner; -use crate::sign::tx_builder::{SpecTxBuilder, TxBuilder}; +use crate::sign::tx_builder::{HTLCAmountDirection, NextCommitmentStats, SpecTxBuilder, TxBuilder}; use crate::sign::{ChannelSigner, EntropySource, NodeSigner, Recipient, SignerProvider}; use crate::types::features::{ChannelTypeFeatures, InitFeatures}; use crate::types::payment::{PaymentHash, PaymentPreimage}; @@ -85,9 +89,7 @@ use crate::util::config::{ use crate::util::errors::APIError; use crate::util::logger::{Logger, Record, WithContext}; use crate::util::scid_utils::{block_from_scid, scid_from_parts}; -use crate::util::ser::{ - Readable, ReadableArgs, RequiredWrapper, TransactionU16LenLimited, Writeable, Writer, -}; +use crate::util::ser::{Readable, ReadableArgs, RequiredWrapper, Writeable, Writer}; use alloc::collections::{btree_map, BTreeMap}; @@ -1009,16 +1011,6 @@ impl ChannelError { pub(super) fn close(err: String) -> Self { ChannelError::Close((err.clone(), ClosureReason::ProcessingError { err })) } - - pub(super) fn message(&self) -> &str { - match self { - &ChannelError::Ignore(ref e) => &e, - &ChannelError::Warn(ref e) => &e, - &ChannelError::WarnAndDisconnect(ref e) => &e, - &ChannelError::Close((ref e, _)) => &e, - &ChannelError::SendError(ref e) => &e, - } - } } pub(super) struct WithChannelContext<'a, L: Deref> @@ -1111,12 +1103,12 @@ pub enum AnnouncementSigsState { /// An enum indicating whether the local or remote side offered a given HTLC. enum HTLCInitiator { LocalOffered, + #[allow(dead_code)] RemoteOffered, } /// Current counts of various HTLCs, useful for calculating current balances available exactly. struct HTLCStats { - pending_inbound_htlcs: usize, pending_outbound_htlcs: usize, pending_inbound_htlcs_value_msat: u64, pending_outbound_htlcs_value_msat: u64, @@ -1126,8 +1118,6 @@ struct HTLCStats { // htlc on the counterparty's commitment transaction. extra_nondust_htlc_on_counterparty_tx_dust_exposure_msat: Option, on_holder_tx_dust_exposure_msat: u64, - outbound_holding_cell_msat: u64, - on_holder_tx_outbound_holding_cell_htlcs_count: u32, // dust HTLCs *non*-included } /// A struct gathering data on a commitment, either local or remote. @@ -1259,14 +1249,11 @@ pub(crate) struct ShutdownResult { /// This consolidates the logic to advance our commitment number and request new /// commitment points from our signer. #[derive(Debug, Copy, Clone)] -enum HolderCommitmentPoint { - /// We've advanced our commitment number and are waiting on the next commitment point. - /// - /// We should retry advancing to `Available` via `try_resolve_pending` once our - /// signer is ready to provide the next commitment point. - PendingNext { transaction_number: u64, current: PublicKey }, - /// Our current commitment point is ready and we've cached our next point. - Available { transaction_number: u64, current: PublicKey, next: PublicKey }, +struct HolderCommitmentPoint { + next_transaction_number: u64, + current_point: Option, + next_point: PublicKey, + pending_next_point: Option, } impl HolderCommitmentPoint { @@ -1274,87 +1261,73 @@ impl HolderCommitmentPoint { pub fn new(signer: &ChannelSignerType, secp_ctx: &Secp256k1) -> Option where SP::Target: SignerProvider { - let current = signer.as_ref().get_per_commitment_point(INITIAL_COMMITMENT_NUMBER, secp_ctx).ok()?; - let next = signer.as_ref().get_per_commitment_point(INITIAL_COMMITMENT_NUMBER - 1, secp_ctx).ok(); - let point = if let Some(next) = next { - HolderCommitmentPoint::Available { transaction_number: INITIAL_COMMITMENT_NUMBER, current, next } - } else { - HolderCommitmentPoint::PendingNext { transaction_number: INITIAL_COMMITMENT_NUMBER, current } - }; - Some(point) + Some(HolderCommitmentPoint { + next_transaction_number: INITIAL_COMMITMENT_NUMBER, + current_point: None, + next_point: signer.as_ref().get_per_commitment_point(INITIAL_COMMITMENT_NUMBER, secp_ctx).ok()?, + pending_next_point: signer.as_ref().get_per_commitment_point(INITIAL_COMMITMENT_NUMBER - 1, secp_ctx).ok(), + }) } - #[rustfmt::skip] - pub fn is_available(&self) -> bool { - if let HolderCommitmentPoint::Available { .. } = self { true } else { false } + pub fn can_advance(&self) -> bool { + self.pending_next_point.is_some() } - pub fn transaction_number(&self) -> u64 { - match self { - HolderCommitmentPoint::PendingNext { transaction_number, .. } => *transaction_number, - HolderCommitmentPoint::Available { transaction_number, .. } => *transaction_number, - } + pub fn current_transaction_number(&self) -> u64 { + self.next_transaction_number + 1 } - pub fn current_point(&self) -> PublicKey { - match self { - HolderCommitmentPoint::PendingNext { current, .. } => *current, - HolderCommitmentPoint::Available { current, .. } => *current, - } + pub fn current_point(&self) -> Option { + self.current_point } - pub fn next_point(&self) -> Option { - match self { - HolderCommitmentPoint::PendingNext { .. } => None, - HolderCommitmentPoint::Available { next, .. } => Some(*next), - } + pub fn next_transaction_number(&self) -> u64 { + self.next_transaction_number } - /// If we are pending the next commitment point, this method tries asking the signer again, - /// and transitions to the next state if successful. - /// - /// This method is used for the following transitions: - /// - `PendingNext` -> `Available` + pub fn next_point(&self) -> PublicKey { + self.next_point + } + + /// If we are pending advancing the next commitment point, this method tries asking the signer + /// again. pub fn try_resolve_pending( &mut self, signer: &ChannelSignerType, secp_ctx: &Secp256k1, logger: &L, ) where SP::Target: SignerProvider, L::Target: Logger, { - if let HolderCommitmentPoint::PendingNext { transaction_number, current } = self { - let next = signer.as_ref().get_per_commitment_point(*transaction_number - 1, secp_ctx); - if let Ok(next) = next { + if !self.can_advance() { + let pending_next_point = signer + .as_ref() + .get_per_commitment_point(self.next_transaction_number - 1, secp_ctx); + if let Ok(point) = pending_next_point { log_trace!( logger, - "Retrieved next per-commitment point {}", - *transaction_number - 1 + "Retrieved per-commitment point {} for next advancement", + self.next_transaction_number - 1 ); - *self = HolderCommitmentPoint::Available { - transaction_number: *transaction_number, - current: *current, - next, - }; + self.pending_next_point = Some(point); } else { - log_trace!(logger, "Next per-commitment point {} is pending", transaction_number); + log_trace!( + logger, + "Pending per-commitment point {} for next advancement", + self.next_transaction_number - 1 + ); } } } /// If we are not pending the next commitment point, this method advances the commitment number - /// and requests the next commitment point from the signer. Returns `Ok` if we were at - /// `Available` and were able to advance our commitment number (even if we are still pending - /// the next commitment point). - /// - /// If our signer is not ready to provide the next commitment point, we will - /// only advance to `PendingNext`, and should be tried again later in `signer_unblocked` - /// via `try_resolve_pending`. + /// and requests the next commitment point from the signer. Returns `Ok` if we were able to + /// advance our commitment number (even if we are still pending the next commitment point). /// - /// If our signer is ready to provide the next commitment point, we will advance all the - /// way to `Available`. + /// If our signer is not ready to provide the next commitment point, we will advance but won't + /// be able to advance again immediately. Instead, this hould be tried again later in + /// `signer_unblocked` via `try_resolve_pending`. /// - /// This method is used for the following transitions: - /// - `Available` -> `PendingNext` - /// - `Available` -> `PendingNext` -> `Available` (in one fell swoop) + /// If our signer is ready to provide the next commitment point, the next call to `advance` will + /// succeed. pub fn advance( &mut self, signer: &ChannelSignerType, secp_ctx: &Secp256k1, logger: &L, ) -> Result<(), ()> @@ -1362,11 +1335,14 @@ impl HolderCommitmentPoint { SP::Target: SignerProvider, L::Target: Logger, { - if let HolderCommitmentPoint::Available { transaction_number, next, .. } = self { - *self = HolderCommitmentPoint::PendingNext { - transaction_number: *transaction_number - 1, - current: *next, + if let Some(next_point) = self.pending_next_point { + *self = Self { + next_transaction_number: self.next_transaction_number - 1, + current_point: Some(self.next_point), + next_point, + pending_next_point: None, }; + self.try_resolve_pending(signer, secp_ctx, logger); return Ok(()); } @@ -1575,6 +1551,22 @@ where ) } + /// Returns true if this channel is waiting on a (batch) funding transaction to be provided. + /// + /// If this method returns true, [`Self::into_unfunded_outbound_v1`] will also succeed. + pub fn ready_to_fund(&self) -> bool { + if !self.funding().is_outbound() { + return false; + } + match self.context().channel_state { + ChannelState::NegotiatingFunding(flags) => { + debug_assert!(matches!(self.phase, ChannelPhase::UnfundedOutboundV1(_))); + flags.is_our_init_sent() && flags.is_their_init_sent() + }, + _ => false, + } + } + pub fn into_unfunded_outbound_v1(self) -> Result, Self> { if let ChannelPhase::UnfundedOutboundV1(channel) = self.phase { Ok(channel) @@ -1784,7 +1776,7 @@ where pub fn funding_tx_constructed( &mut self, logger: &L, - ) -> Result<(msgs::CommitmentSigned, Option), msgs::TxAbort> + ) -> Result where L::Target: Logger, { @@ -1796,7 +1788,7 @@ where .take() .expect("PendingV2Channel::interactive_tx_constructor should be set") .into_signing_session(); - let (commitment_signed, event) = chan.context.funding_tx_constructed( + let commitment_signed = chan.context.funding_tx_constructed( &mut chan.funding, &mut signing_session, false, @@ -1806,7 +1798,7 @@ where chan.interactive_tx_signing_session = Some(signing_session); - return Ok((commitment_signed, event)); + return Ok(commitment_signed); }, #[cfg(splicing)] ChannelPhase::Funded(chan) => { @@ -1819,11 +1811,11 @@ where { let mut signing_session = interactive_tx_constructor.into_signing_session(); - let (commitment_signed, event) = chan.context.funding_tx_constructed( + let commitment_signed = chan.context.funding_tx_constructed( &mut funding, &mut signing_session, true, - chan.holder_commitment_point.transaction_number(), + chan.holder_commitment_point.next_transaction_number(), &&logger, )?; @@ -1831,7 +1823,7 @@ where pending_splice.funding_negotiation = Some(FundingNegotiation::AwaitingSignatures(funding)); - return Ok((commitment_signed, event)); + return Ok(commitment_signed); } else { // Replace the taken state pending_splice.funding_negotiation = Some(funding_negotiation); @@ -1887,8 +1879,9 @@ where holder_commitment_point, #[cfg(splicing)] pending_splice: None, + quiescent_action: None, }; - let res = funded_channel.commitment_signed_initial_v2(msg, best_block, signer_provider, logger) + let res = funded_channel.initial_commitment_signed_v2(msg, best_block, signer_provider, logger) .map(|monitor| (Some(monitor), None)) // TODO: Change to `inspect_err` when MSRV is high enough. .map_err(|err| { @@ -2037,7 +2030,7 @@ impl UnfundedChannelContext { fn transaction_number(&self) -> u64 { self.holder_commitment_point .as_ref() - .map(|point| point.transaction_number()) + .map(|point| point.next_transaction_number()) .unwrap_or(INITIAL_COMMITMENT_NUMBER) } } @@ -2063,14 +2056,13 @@ pub(super) struct FundingScope { /// Max to_local and to_remote outputs in a remote-generated commitment transaction counterparty_max_commitment_tx_output: Mutex<(u64, u64)>, - // We save these values so we can make sure `next_local_commit_tx_fee_msat` and - // `next_remote_commit_tx_fee_msat` properly predict what the next commitment transaction fee will - // be, by comparing the cached values to the fee of the transaction generated by - // `build_commitment_transaction`. + // We save these values so we can make sure validation of channel updates properly predicts + // what the next commitment transaction fee will be, by comparing the cached values to the + // fee of the transaction generated by `build_commitment_transaction`. #[cfg(any(test, fuzzing))] - next_local_commitment_tx_fee_info_cached: Mutex>, + next_local_fee: Mutex, #[cfg(any(test, fuzzing))] - next_remote_commitment_tx_fee_info_cached: Mutex>, + next_remote_fee: Mutex, pub(super) channel_transaction_parameters: ChannelTransactionParameters, @@ -2144,9 +2136,9 @@ impl Readable for FundingScope { short_channel_id, minimum_depth_override, #[cfg(any(test, fuzzing))] - next_local_commitment_tx_fee_info_cached: Mutex::new(None), + next_local_fee: Mutex::new(PredictedNextFee::default()), #[cfg(any(test, fuzzing))] - next_remote_commitment_tx_fee_info_cached: Mutex::new(None), + next_remote_fee: Mutex::new(PredictedNextFee::default()), }) } } @@ -2254,20 +2246,23 @@ impl FundingScope { /// Constructs a `FundingScope` for splicing a channel. #[cfg(splicing)] fn for_splice( - prev_funding: &Self, context: &ChannelContext, our_funding_contribution_sats: i64, - their_funding_contribution_sats: i64, counterparty_funding_pubkey: PublicKey, + prev_funding: &Self, context: &ChannelContext, our_funding_contribution: SignedAmount, + their_funding_contribution: SignedAmount, counterparty_funding_pubkey: PublicKey, ) -> Result where SP::Target: SignerProvider, { + debug_assert!(our_funding_contribution.abs() <= SignedAmount::MAX_MONEY); + debug_assert!(their_funding_contribution.abs() <= SignedAmount::MAX_MONEY); + let post_channel_value = prev_funding.compute_post_splice_value( - our_funding_contribution_sats, - their_funding_contribution_sats, + our_funding_contribution.to_sat(), + their_funding_contribution.to_sat(), ); let post_value_to_self_msat = AddSigned::checked_add_signed( prev_funding.value_to_self_msat, - our_funding_contribution_sats * 1000, + our_funding_contribution.to_sat() * 1000, ); debug_assert!(post_value_to_self_msat.is_some()); let post_value_to_self_msat = post_value_to_self_msat.unwrap(); @@ -2320,9 +2315,9 @@ impl FundingScope { (post_channel_value * 1000).saturating_sub(post_value_to_self_msat), )), #[cfg(any(test, fuzzing))] - next_local_commitment_tx_fee_info_cached: Mutex::new(None), + next_local_fee: Mutex::new(PredictedNextFee::default()), #[cfg(any(test, fuzzing))] - next_remote_commitment_tx_fee_info_cached: Mutex::new(None), + next_remote_fee: Mutex::new(PredictedNextFee::default()), funding_tx_confirmation_height: 0, funding_tx_confirmed_in: None, minimum_depth_override: None, @@ -2453,6 +2448,15 @@ impl PendingSplice { } } +pub(crate) enum QuiescentAction { + // TODO: Make this test-only once we have another variant (as some code requires *a* variant). + DoNothing, +} + +impl_writeable_tlv_based_enum_upgradable!(QuiescentAction, + (99, DoNothing) => {}, +); + /// Wrapper around a [`Transaction`] useful for caching the result of [`Transaction::compute_txid`]. struct ConfirmedTransaction<'a> { tx: &'a Transaction, @@ -2523,7 +2527,7 @@ where // Our commitment numbers start at 2^48-1 and count down, whereas the ones used in transaction // generation start at 0 and count up...this simplifies some parts of implementation at the // cost of others, but should really just be changed. - cur_counterparty_commitment_transaction_number: u64, + counterparty_next_commitment_transaction_number: u64, pending_inbound_htlcs: Vec, pending_outbound_htlcs: Vec, holding_cell_htlc_updates: Vec, @@ -2549,7 +2553,6 @@ where monitor_pending_failures: Vec<(HTLCSource, PaymentHash, HTLCFailReason)>, monitor_pending_finalized_fulfills: Vec<(HTLCSource, Option)>, monitor_pending_update_adds: Vec, - monitor_pending_tx_signatures: Option, /// If we went to send a revoke_and_ack but our signer was unable to give us a signature, /// we should retry at some point in the future when the signer indicates it may have a @@ -2668,8 +2671,8 @@ where is_manual_broadcast: bool, is_batch_funding: Option<()>, - counterparty_cur_commitment_point: Option, - counterparty_prev_commitment_point: Option, + counterparty_next_commitment_point: Option, + counterparty_current_commitment_point: Option, counterparty_node_id: PublicKey, counterparty_shutdown_scriptpubkey: Option, @@ -2753,10 +2756,6 @@ where /// If we can't release a [`ChannelMonitorUpdate`] until some external action completes, we /// store it here and only release it to the `ChannelManager` once it asks for it. blocked_monitor_updates: Vec, - - /// Only set when a counterparty `stfu` has been processed to track which node is allowed to - /// propose "something fundamental" upon becoming quiescent. - is_holder_quiescence_initiator: Option, } /// A channel struct implementing this trait can receive an initial counterparty commitment @@ -2782,7 +2781,7 @@ where let funding_script = self.funding().get_funding_redeemscript(); let commitment_data = self.context().build_commitment_transaction(self.funding(), - holder_commitment_point.transaction_number(), &holder_commitment_point.current_point(), + holder_commitment_point.next_transaction_number(), &holder_commitment_point.next_point(), true, false, logger); let initial_commitment_tx = commitment_data.tx; let trusted_tx = initial_commitment_tx.trust(); @@ -2823,8 +2822,8 @@ where }; let context = self.context(); let commitment_data = context.build_commitment_transaction(self.funding(), - context.cur_counterparty_commitment_transaction_number, - &context.counterparty_cur_commitment_point.unwrap(), false, false, logger); + context.counterparty_next_commitment_transaction_number, + &context.counterparty_next_commitment_point.unwrap(), false, false, logger); let counterparty_initial_commitment_tx = commitment_data.tx; let counterparty_trusted_tx = counterparty_initial_commitment_tx.trust(); let counterparty_initial_bitcoin_tx = counterparty_trusted_tx.built_transaction(); @@ -2884,7 +2883,7 @@ where counterparty_initial_commitment_tx.clone(), ); - self.context_mut().cur_counterparty_commitment_transaction_number -= 1; + self.context_mut().counterparty_next_commitment_transaction_number -= 1; Ok((channel_monitor, counterparty_initial_commitment_tx)) } @@ -3199,9 +3198,9 @@ where counterparty_max_commitment_tx_output: Mutex::new((value_to_self_msat, (channel_value_satoshis * 1000 - msg_push_msat).saturating_sub(value_to_self_msat))), #[cfg(any(test, fuzzing))] - next_local_commitment_tx_fee_info_cached: Mutex::new(None), + next_local_fee: Mutex::new(PredictedNextFee::default()), #[cfg(any(test, fuzzing))] - next_remote_commitment_tx_fee_info_cached: Mutex::new(None), + next_remote_fee: Mutex::new(PredictedNextFee::default()), channel_transaction_parameters: ChannelTransactionParameters { holder_pubkeys: pubkeys, @@ -3249,7 +3248,7 @@ where shutdown_scriptpubkey, destination_script, - cur_counterparty_commitment_transaction_number: INITIAL_COMMITMENT_NUMBER, + counterparty_next_commitment_transaction_number: INITIAL_COMMITMENT_NUMBER, pending_inbound_htlcs: Vec::new(), pending_outbound_htlcs: Vec::new(), @@ -3269,7 +3268,6 @@ where monitor_pending_failures: Vec::new(), monitor_pending_finalized_fulfills: Vec::new(), monitor_pending_update_adds: Vec::new(), - monitor_pending_tx_signatures: None, signer_pending_revoke_and_ack: false, signer_pending_commitment_update: false, @@ -3301,8 +3299,8 @@ where is_batch_funding: None, - counterparty_cur_commitment_point: Some(open_channel_fields.first_per_commitment_point), - counterparty_prev_commitment_point: None, + counterparty_next_commitment_point: Some(open_channel_fields.first_per_commitment_point), + counterparty_current_commitment_point: None, counterparty_node_id, counterparty_shutdown_scriptpubkey, @@ -3332,8 +3330,6 @@ where blocked_monitor_updates: Vec::new(), is_manual_broadcast: false, - - is_holder_quiescence_initiator: None, }; Ok((funding, channel_context)) @@ -3442,9 +3438,9 @@ where counterparty_max_commitment_tx_output: Mutex::new((channel_value_satoshis * 1000 - push_msat, push_msat)), #[cfg(any(test, fuzzing))] - next_local_commitment_tx_fee_info_cached: Mutex::new(None), + next_local_fee: Mutex::new(PredictedNextFee::default()), #[cfg(any(test, fuzzing))] - next_remote_commitment_tx_fee_info_cached: Mutex::new(None), + next_remote_fee: Mutex::new(PredictedNextFee::default()), channel_transaction_parameters: ChannelTransactionParameters { holder_pubkeys: pubkeys, @@ -3488,7 +3484,7 @@ where shutdown_scriptpubkey, destination_script, - cur_counterparty_commitment_transaction_number: INITIAL_COMMITMENT_NUMBER, + counterparty_next_commitment_transaction_number: INITIAL_COMMITMENT_NUMBER, pending_inbound_htlcs: Vec::new(), pending_outbound_htlcs: Vec::new(), @@ -3508,7 +3504,6 @@ where monitor_pending_failures: Vec::new(), monitor_pending_finalized_fulfills: Vec::new(), monitor_pending_update_adds: Vec::new(), - monitor_pending_tx_signatures: None, signer_pending_revoke_and_ack: false, signer_pending_commitment_update: false, @@ -3542,8 +3537,8 @@ where is_batch_funding: None, - counterparty_cur_commitment_point: None, - counterparty_prev_commitment_point: None, + counterparty_next_commitment_point: None, + counterparty_current_commitment_point: None, counterparty_node_id, counterparty_shutdown_scriptpubkey: None, @@ -3571,8 +3566,6 @@ where blocked_monitor_updates: Vec::new(), local_initiated_shutdown: None, is_manual_broadcast: false, - - is_holder_quiescence_initiator: None, }; Ok((funding, channel_context)) @@ -3924,7 +3917,7 @@ where pubkeys: counterparty_pubkeys, }); - self.counterparty_cur_commitment_point = Some(common_fields.first_per_commitment_point); + self.counterparty_next_commitment_point = Some(common_fields.first_per_commitment_point); self.counterparty_shutdown_scriptpubkey = counterparty_shutdown_scriptpubkey; self.channel_state = ChannelState::NegotiatingFunding( @@ -4109,6 +4102,227 @@ where ); } + /// Returns a best-effort guess of the set of HTLCs that will be present + /// on the next local or remote commitment. We cannot be certain as the + /// actual set of HTLCs present on the next commitment depends on the + /// ordering of commitment_signed and revoke_and_ack messages. + /// + /// We take the conservative approach and only assume that a HTLC will + /// not be in the next commitment when it is guaranteed that it won't be. + #[rustfmt::skip] + fn get_next_commitment_htlcs( + &self, local: bool, htlc_candidate: Option, include_counterparty_unknown_htlcs: bool, + ) -> Vec { + let mut commitment_htlcs = Vec::with_capacity( + 1 + self.pending_inbound_htlcs.len() + + self.pending_outbound_htlcs.len() + + self.holding_cell_htlc_updates.len(), + ); + // `LocalRemoved` HTLCs will certainly not be present on any future remote + // commitments, but they could be in a future local commitment as the remote has + // not yet acknowledged the removal. + let pending_inbound_htlcs = self + .pending_inbound_htlcs + .iter() + .filter(|InboundHTLCOutput { state, .. }| match (state, local) { + (InboundHTLCState::RemoteAnnounced(..), _) => true, + (InboundHTLCState::AwaitingRemoteRevokeToAnnounce(..), _) => true, + (InboundHTLCState::AwaitingAnnouncedRemoteRevoke(..), _) => true, + (InboundHTLCState::Committed, _) => true, + (InboundHTLCState::LocalRemoved(..), true) => true, + (InboundHTLCState::LocalRemoved(..), false) => false, + }) + .map(|&InboundHTLCOutput { amount_msat, .. }| HTLCAmountDirection { outbound: false, amount_msat }); + // `RemoteRemoved` HTLCs can still be present on the next remote commitment if + // local produces a commitment before acknowledging the update. These HTLCs + // will for sure not be present on the next local commitment. + let pending_outbound_htlcs = self + .pending_outbound_htlcs + .iter() + .filter(|OutboundHTLCOutput { state, .. }| match (state, local) { + (OutboundHTLCState::LocalAnnounced(..), _) => include_counterparty_unknown_htlcs, + (OutboundHTLCState::Committed, _) => true, + (OutboundHTLCState::RemoteRemoved(..), true) => false, + (OutboundHTLCState::RemoteRemoved(..), false) => true, + (OutboundHTLCState::AwaitingRemoteRevokeToRemove(..), _) => false, + (OutboundHTLCState::AwaitingRemovedRemoteRevoke(..), _) => false, + }) + .map(|&OutboundHTLCOutput { amount_msat, .. }| HTLCAmountDirection { outbound: true, amount_msat }); + + let holding_cell_htlcs = self.holding_cell_htlc_updates.iter().filter_map(|htlc| { + if let &HTLCUpdateAwaitingACK::AddHTLC { amount_msat, .. } = htlc { + Some(HTLCAmountDirection { outbound: true, amount_msat }) + } else { + None + } + }); + + if include_counterparty_unknown_htlcs { + commitment_htlcs.extend( + htlc_candidate.into_iter().chain(pending_inbound_htlcs).chain(pending_outbound_htlcs).chain(holding_cell_htlcs) + ); + } else { + commitment_htlcs.extend( + htlc_candidate.into_iter().chain(pending_inbound_htlcs).chain(pending_outbound_htlcs) + ); + } + + commitment_htlcs + } + + /// This returns the value of `value_to_self_msat` after accounting for all the + /// successful inbound and outbound HTLCs that won't be present on the next + /// commitment. + /// + /// To determine which HTLC claims to account for, we take the cases where a HTLC + /// will *not* be present on the next commitment from `next_commitment_htlcs`, and + /// check if their outcome is successful. If it is, we add the value of this claimed + /// HTLC to the balance of the claimer. + #[rustfmt::skip] + fn get_next_commitment_value_to_self_msat(&self, local: bool, funding: &FundingScope) -> u64 { + let inbound_claimed_htlc_msat: u64 = + self.pending_inbound_htlcs + .iter() + .filter(|InboundHTLCOutput { state, .. }| match (state, local) { + (InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::Fulfill(_, _)), true) => false, + (InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::Fulfill(_, _)), false) => true, + _ => false, + }) + .map(|InboundHTLCOutput { amount_msat, .. }| amount_msat) + .sum(); + let outbound_claimed_htlc_msat: u64 = + self.pending_outbound_htlcs + .iter() + .filter(|OutboundHTLCOutput { state, .. }| match (state, local) { + (OutboundHTLCState::RemoteRemoved(OutboundHTLCOutcome::Success(_, _)), true) => true, + (OutboundHTLCState::RemoteRemoved(OutboundHTLCOutcome::Success(_, _)), false) => false, + (OutboundHTLCState::AwaitingRemoteRevokeToRemove(OutboundHTLCOutcome::Success(_, _)), _) => true, + (OutboundHTLCState::AwaitingRemovedRemoteRevoke(OutboundHTLCOutcome::Success(_, _)), _) => true, + _ => false, + }) + .map(|OutboundHTLCOutput { amount_msat, .. }| amount_msat) + .sum(); + + funding + .value_to_self_msat + .saturating_sub(outbound_claimed_htlc_msat) + .saturating_add(inbound_claimed_htlc_msat) + } + + fn get_next_local_commitment_stats( + &self, funding: &FundingScope, htlc_candidate: Option, + include_counterparty_unknown_htlcs: bool, addl_nondust_htlc_count: usize, + feerate_per_kw: u32, dust_exposure_limiting_feerate: Option, + ) -> NextCommitmentStats { + let next_commitment_htlcs = self.get_next_commitment_htlcs( + true, + htlc_candidate, + include_counterparty_unknown_htlcs, + ); + let next_value_to_self_msat = self.get_next_commitment_value_to_self_msat(true, funding); + + let ret = SpecTxBuilder {}.get_next_commitment_stats( + true, + funding.is_outbound(), + funding.get_value_satoshis(), + next_value_to_self_msat, + &next_commitment_htlcs, + addl_nondust_htlc_count, + feerate_per_kw, + dust_exposure_limiting_feerate, + self.holder_dust_limit_satoshis, + funding.get_channel_type(), + ); + + #[cfg(any(test, fuzzing))] + { + if addl_nondust_htlc_count == 0 { + *funding.next_local_fee.lock().unwrap() = PredictedNextFee { + predicted_feerate: feerate_per_kw, + predicted_nondust_htlc_count: ret.nondust_htlc_count, + predicted_fee_sat: ret.commit_tx_fee_sat, + }; + } else { + let predicted_stats = SpecTxBuilder {}.get_next_commitment_stats( + true, + funding.is_outbound(), + funding.get_value_satoshis(), + next_value_to_self_msat, + &next_commitment_htlcs, + 0, + feerate_per_kw, + dust_exposure_limiting_feerate, + self.holder_dust_limit_satoshis, + funding.get_channel_type(), + ); + *funding.next_local_fee.lock().unwrap() = PredictedNextFee { + predicted_feerate: feerate_per_kw, + predicted_nondust_htlc_count: predicted_stats.nondust_htlc_count, + predicted_fee_sat: predicted_stats.commit_tx_fee_sat, + }; + } + } + + ret + } + + fn get_next_remote_commitment_stats( + &self, funding: &FundingScope, htlc_candidate: Option, + include_counterparty_unknown_htlcs: bool, addl_nondust_htlc_count: usize, + feerate_per_kw: u32, dust_exposure_limiting_feerate: Option, + ) -> NextCommitmentStats { + let next_commitment_htlcs = self.get_next_commitment_htlcs( + false, + htlc_candidate, + include_counterparty_unknown_htlcs, + ); + let next_value_to_self_msat = self.get_next_commitment_value_to_self_msat(false, funding); + + let ret = SpecTxBuilder {}.get_next_commitment_stats( + false, + funding.is_outbound(), + funding.get_value_satoshis(), + next_value_to_self_msat, + &next_commitment_htlcs, + addl_nondust_htlc_count, + feerate_per_kw, + dust_exposure_limiting_feerate, + self.counterparty_dust_limit_satoshis, + funding.get_channel_type(), + ); + + #[cfg(any(test, fuzzing))] + { + if addl_nondust_htlc_count == 0 { + *funding.next_remote_fee.lock().unwrap() = PredictedNextFee { + predicted_feerate: feerate_per_kw, + predicted_nondust_htlc_count: ret.nondust_htlc_count, + predicted_fee_sat: ret.commit_tx_fee_sat, + }; + } else { + let predicted_stats = SpecTxBuilder {}.get_next_commitment_stats( + false, + funding.is_outbound(), + funding.get_value_satoshis(), + next_value_to_self_msat, + &next_commitment_htlcs, + 0, + feerate_per_kw, + dust_exposure_limiting_feerate, + self.counterparty_dust_limit_satoshis, + funding.get_channel_type(), + ); + *funding.next_remote_fee.lock().unwrap() = PredictedNextFee { + predicted_feerate: feerate_per_kw, + predicted_nondust_htlc_count: predicted_stats.nondust_htlc_count, + predicted_fee_sat: predicted_stats.commit_tx_fee_sat, + }; + } + } + + ret + } + #[rustfmt::skip] fn validate_update_add_htlc( &self, funding: &FundingScope, msg: &msgs::UpdateAddHTLC, @@ -4124,15 +4338,25 @@ where let dust_exposure_limiting_feerate = self.get_dust_exposure_limiting_feerate( &fee_estimator, funding.get_channel_type(), ); - let htlc_stats = self.get_pending_htlc_stats(funding, None, dust_exposure_limiting_feerate); - if htlc_stats.pending_inbound_htlcs + 1 > self.holder_max_accepted_htlcs as usize { + // Don't include outbound update_add_htlc's in the holding cell, or those which haven't yet been ACK'ed by the counterparty (ie. LocalAnnounced HTLCs) + let include_counterparty_unknown_htlcs = false; + // Don't include the extra fee spike buffer HTLC in calculations + let fee_spike_buffer_htlc = 0; + let next_remote_commitment_stats = self.get_next_remote_commitment_stats(funding, Some(HTLCAmountDirection { outbound: false, amount_msat: msg.amount_msat }), include_counterparty_unknown_htlcs, fee_spike_buffer_htlc, self.feerate_per_kw, dust_exposure_limiting_feerate); + + if next_remote_commitment_stats.inbound_htlcs_count > self.holder_max_accepted_htlcs as usize { return Err(ChannelError::close(format!("Remote tried to push more than our max accepted HTLCs ({})", self.holder_max_accepted_htlcs))); } - if htlc_stats.pending_inbound_htlcs_value_msat + msg.amount_msat > self.holder_max_htlc_value_in_flight_msat { + if next_remote_commitment_stats.inbound_htlcs_value_msat > self.holder_max_htlc_value_in_flight_msat { return Err(ChannelError::close(format!("Remote HTLC add would put them over our max HTLC value ({})", self.holder_max_htlc_value_in_flight_msat))); } - // Check holder_selected_channel_reserve_satoshis (we're getting paid, so they have to at least meet + let remote_balance_before_fee_msat = next_remote_commitment_stats.counterparty_balance_before_fee_msat.ok_or(ChannelError::close("Remote HTLC add would overdraw remaining funds".to_owned()))?; + + // Check that the remote can afford to pay for this HTLC on-chain at the current + // feerate_per_kw, while maintaining their channel reserve (as required by the spec). + // + // We check holder_selected_channel_reserve_satoshis (we're getting paid, so they have to at least meet // the reserve_satoshis we told them to always have as direct payment so that they lose // something if we punish them for broadcasting an old state). // Note that we don't really care about having a small/no to_remote output in our local @@ -4144,50 +4368,23 @@ where // violate the reserve value if we do not do this (as we forget inbound HTLCs from the // Channel state once they will not be present in the next received commitment // transaction). - let (local_balance_before_fee_msat, remote_balance_before_fee_msat) = { - let removed_outbound_total_msat: u64 = self.pending_outbound_htlcs - .iter() - .filter_map(|htlc| { - matches!( - htlc.state, - OutboundHTLCState::AwaitingRemoteRevokeToRemove(OutboundHTLCOutcome::Success(_, _)) - | OutboundHTLCState::AwaitingRemovedRemoteRevoke(OutboundHTLCOutcome::Success(_, _)) - ) - .then_some(htlc.amount_msat) - }) - .sum(); - let pending_value_to_self_msat = - funding.value_to_self_msat + htlc_stats.pending_inbound_htlcs_value_msat - removed_outbound_total_msat; - let pending_remote_value_msat = - funding.get_value_satoshis() * 1000 - pending_value_to_self_msat; - - // Subtract any non-HTLC outputs from the local and remote balances - SpecTxBuilder {}.subtract_non_htlc_outputs(funding.is_outbound(), funding.value_to_self_msat, pending_remote_value_msat, funding.get_channel_type()) - }; - if remote_balance_before_fee_msat < msg.amount_msat { - return Err(ChannelError::close("Remote HTLC add would overdraw remaining funds".to_owned())); - } - - // Check that the remote can afford to pay for this HTLC on-chain at the current - // feerate_per_kw, while maintaining their channel reserve (as required by the spec). { let remote_commit_tx_fee_msat = if funding.is_outbound() { 0 } else { - let htlc_candidate = HTLCCandidate::new(msg.amount_msat, HTLCInitiator::RemoteOffered); - self.next_remote_commit_tx_fee_msat(funding, Some(htlc_candidate), None) // Don't include the extra fee spike buffer HTLC in calculations + next_remote_commitment_stats.commit_tx_fee_sat * 1000 }; - if remote_balance_before_fee_msat.saturating_sub(msg.amount_msat) < remote_commit_tx_fee_msat { + if remote_balance_before_fee_msat < remote_commit_tx_fee_msat { return Err(ChannelError::close("Remote HTLC add would not leave enough to pay for fees".to_owned())); }; - if remote_balance_before_fee_msat.saturating_sub(msg.amount_msat).saturating_sub(remote_commit_tx_fee_msat) < funding.holder_selected_channel_reserve_satoshis * 1000 { + if remote_balance_before_fee_msat.saturating_sub(remote_commit_tx_fee_msat) < funding.holder_selected_channel_reserve_satoshis * 1000 { return Err(ChannelError::close("Remote HTLC add would put them under remote reserve value".to_owned())); } } if funding.is_outbound() { + let next_local_commitment_stats = self.get_next_local_commitment_stats(funding, Some(HTLCAmountDirection { outbound: false, amount_msat: msg.amount_msat }), include_counterparty_unknown_htlcs, fee_spike_buffer_htlc, self.feerate_per_kw, dust_exposure_limiting_feerate); + let holder_balance_msat = next_local_commitment_stats.holder_balance_before_fee_msat.expect("Adding an inbound HTLC should never exhaust the holder's balance before fees"); // Check that they won't violate our local required channel reserve by adding this HTLC. - let htlc_candidate = HTLCCandidate::new(msg.amount_msat, HTLCInitiator::RemoteOffered); - let local_commit_tx_fee_msat = self.next_local_commit_tx_fee_msat(funding, htlc_candidate, None); - if local_balance_before_fee_msat < funding.counterparty_selected_channel_reserve_satoshis.unwrap() * 1000 + local_commit_tx_fee_msat { + if holder_balance_msat < funding.counterparty_selected_channel_reserve_satoshis.unwrap() * 1000 + next_local_commitment_stats.commit_tx_fee_sat * 1000 { return Err(ChannelError::close("Cannot accept HTLC that would put our balance under counterparty-announced channel reserve value".to_owned())); } } @@ -4207,22 +4404,27 @@ where let dust_exposure_limiting_feerate = self.get_dust_exposure_limiting_feerate( &fee_estimator, funding.get_channel_type(), ); - let htlc_stats = self.get_pending_htlc_stats(funding, None, dust_exposure_limiting_feerate); + // Do not include outbound update_add_htlc's in the holding cell, or those which haven't yet been ACK'ed by the counterparty (ie. LocalAnnounced HTLCs) + let include_counterparty_unknown_htlcs = false; + let next_local_commitment_stats = self.get_next_local_commitment_stats(funding, None, include_counterparty_unknown_htlcs, 0, msg.feerate_per_kw, dust_exposure_limiting_feerate); + let next_remote_commitment_stats = self.get_next_remote_commitment_stats(funding, None, include_counterparty_unknown_htlcs, 0, msg.feerate_per_kw, dust_exposure_limiting_feerate); + let max_dust_htlc_exposure_msat = self.get_max_dust_htlc_exposure_msat(dust_exposure_limiting_feerate); - if htlc_stats.on_holder_tx_dust_exposure_msat > max_dust_htlc_exposure_msat { + if next_local_commitment_stats.dust_exposure_msat > max_dust_htlc_exposure_msat { return Err(ChannelError::close(format!("Peer sent update_fee with a feerate ({}) which may over-expose us to dust-in-flight on our own transactions (totaling {} msat)", - msg.feerate_per_kw, htlc_stats.on_holder_tx_dust_exposure_msat))); + msg.feerate_per_kw, next_local_commitment_stats.dust_exposure_msat))); } - if htlc_stats.on_counterparty_tx_dust_exposure_msat > max_dust_htlc_exposure_msat { + if next_remote_commitment_stats.dust_exposure_msat > max_dust_htlc_exposure_msat { return Err(ChannelError::close(format!("Peer sent update_fee with a feerate ({}) which may over-expose us to dust-in-flight on our counterparty's transactions (totaling {} msat)", - msg.feerate_per_kw, htlc_stats.on_counterparty_tx_dust_exposure_msat))); + msg.feerate_per_kw, next_remote_commitment_stats.dust_exposure_msat))); } + Ok(()) } #[rustfmt::skip] fn validate_commitment_signed( - &self, funding: &FundingScope, holder_commitment_point: &HolderCommitmentPoint, + &self, funding: &FundingScope, transaction_number: u64, commitment_point: PublicKey, msg: &msgs::CommitmentSigned, logger: &L, ) -> Result<(HolderCommitmentTransaction, Vec<(HTLCOutputInCommitment, Option<&HTLCSource>)>), ChannelError> where @@ -4230,9 +4432,9 @@ where { let funding_script = funding.get_funding_redeemscript(); - let commitment_data = self.build_commitment_transaction(funding, - holder_commitment_point.transaction_number(), &holder_commitment_point.current_point(), - true, false, logger); + let commitment_data = self.build_commitment_transaction( + funding, transaction_number, &commitment_point, true, false, logger, + ); let commitment_txid = { let trusted_tx = commitment_data.tx.trust(); let bitcoin_tx = trusted_tx.built_transaction(); @@ -4262,19 +4464,9 @@ where } #[cfg(any(test, fuzzing))] { - if funding.is_outbound() { - let projected_commit_tx_info = funding.next_local_commitment_tx_fee_info_cached.lock().unwrap().take(); - *funding.next_remote_commitment_tx_fee_info_cached.lock().unwrap() = None; - if let Some(info) = projected_commit_tx_info { - let total_pending_htlcs = self.pending_inbound_htlcs.len() + self.pending_outbound_htlcs.len() - + self.holding_cell_htlc_updates.len(); - if info.total_pending_htlcs == total_pending_htlcs - && info.next_holder_htlc_id == self.next_holder_htlc_id - && info.next_counterparty_htlc_id == self.next_counterparty_htlc_id - && info.feerate == self.feerate_per_kw { - assert_eq!(commitment_data.stats.commit_tx_fee_sat, info.fee / 1000); - } - } + let PredictedNextFee { predicted_feerate, predicted_nondust_htlc_count, predicted_fee_sat } = *funding.next_local_fee.lock().unwrap(); + if predicted_feerate == commitment_data.tx.feerate_per_kw() && predicted_nondust_htlc_count == commitment_data.tx.nondust_htlcs().len() { + assert_eq!(predicted_fee_sat, commitment_data.stats.commit_tx_fee_sat); } } @@ -4326,11 +4518,12 @@ where let dust_exposure_limiting_feerate = self.get_dust_exposure_limiting_feerate( &fee_estimator, funding.get_channel_type(), ); - let htlc_stats = self.get_pending_htlc_stats(funding, Some(feerate_per_kw), dust_exposure_limiting_feerate); - let stats = self.build_commitment_stats(funding, true, true, Some(feerate_per_kw), Some(htlc_stats.on_holder_tx_outbound_holding_cell_htlcs_count as usize + CONCURRENT_INBOUND_HTLC_FEE_BUFFER as usize)); - let holder_balance_msat = stats.local_balance_before_fee_msat - htlc_stats.outbound_holding_cell_msat; + // Include outbound update_add_htlc's in the holding cell, and those which haven't yet been ACK'ed by the counterparty (ie. LocalAnnounced HTLCs) + let include_counterparty_unknown_htlcs = true; + let next_remote_commitment_stats = self.get_next_remote_commitment_stats(funding, None, include_counterparty_unknown_htlcs, CONCURRENT_INBOUND_HTLC_FEE_BUFFER as usize, feerate_per_kw, dust_exposure_limiting_feerate); + let holder_balance_msat = next_remote_commitment_stats.holder_balance_before_fee_msat.expect("The holder's balance before fees should never underflow."); // Note that `stats.commit_tx_fee_sat` accounts for any HTLCs that transition from non-dust to dust under a higher feerate (in the case where HTLC-transactions pay endogenous fees). - if holder_balance_msat < stats.commit_tx_fee_sat * 1000 + funding.counterparty_selected_channel_reserve_satoshis.unwrap() * 1000 { + if holder_balance_msat < next_remote_commitment_stats.commit_tx_fee_sat * 1000 + funding.counterparty_selected_channel_reserve_satoshis.unwrap() * 1000 { //TODO: auto-close after a number of failures? log_debug!(logger, "Cannot afford to send new feerate at {}", feerate_per_kw); return false; @@ -4338,11 +4531,13 @@ where // Note, we evaluate pending htlc "preemptive" trimmed-to-dust threshold at the proposed `feerate_per_kw`. let max_dust_htlc_exposure_msat = self.get_max_dust_htlc_exposure_msat(dust_exposure_limiting_feerate); - if htlc_stats.on_holder_tx_dust_exposure_msat > max_dust_htlc_exposure_msat { + if next_remote_commitment_stats.dust_exposure_msat > max_dust_htlc_exposure_msat { log_debug!(logger, "Cannot afford to send new feerate at {} without infringing max dust htlc exposure", feerate_per_kw); return false; } - if htlc_stats.on_counterparty_tx_dust_exposure_msat > max_dust_htlc_exposure_msat { + + let next_local_commitment_stats = self.get_next_local_commitment_stats(funding, None, include_counterparty_unknown_htlcs, CONCURRENT_INBOUND_HTLC_FEE_BUFFER as usize, feerate_per_kw, dust_exposure_limiting_feerate); + if next_local_commitment_stats.dust_exposure_msat > max_dust_htlc_exposure_msat { log_debug!(logger, "Cannot afford to send new feerate at {} without infringing max dust htlc exposure", feerate_per_kw); return false; } @@ -4352,79 +4547,50 @@ where #[rustfmt::skip] fn can_accept_incoming_htlc( - &self, funding: &FundingScope, msg: &msgs::UpdateAddHTLC, + &self, funding: &FundingScope, dust_exposure_limiting_feerate: Option, logger: &L, ) -> Result<(), LocalHTLCFailureReason> where L::Target: Logger, { - let htlc_stats = self.get_pending_htlc_stats(funding, None, dust_exposure_limiting_feerate); + // The fee spike buffer (an additional nondust HTLC) we keep for the remote if the channel + // is not zero fee. This deviates from the spec because the fee spike buffer requirement + // doesn't exist on the receiver's side, only on the sender's. + let fee_spike_buffer_htlc = if funding.get_channel_type().supports_anchor_zero_fee_commitments() { + 0 + } else { + 1 + }; + // Do not include outbound update_add_htlc's in the holding cell, or those which haven't yet been ACK'ed by the counterparty (ie. LocalAnnounced HTLCs) + let include_counterparty_unknown_htlcs = false; + // A `None` `HTLCCandidate` is used as in this case because we're already accounting for + // the incoming HTLC as it has been fully committed by both sides. + let next_local_commitment_stats = self.get_next_local_commitment_stats(funding, None, include_counterparty_unknown_htlcs, fee_spike_buffer_htlc, self.feerate_per_kw, dust_exposure_limiting_feerate); + let next_remote_commitment_stats = self.get_next_remote_commitment_stats(funding, None, include_counterparty_unknown_htlcs, fee_spike_buffer_htlc, self.feerate_per_kw, dust_exposure_limiting_feerate); + let max_dust_htlc_exposure_msat = self.get_max_dust_htlc_exposure_msat(dust_exposure_limiting_feerate); - let on_counterparty_tx_dust_htlc_exposure_msat = htlc_stats.on_counterparty_tx_dust_exposure_msat; - if on_counterparty_tx_dust_htlc_exposure_msat > max_dust_htlc_exposure_msat { + if next_remote_commitment_stats.dust_exposure_msat > max_dust_htlc_exposure_msat { // Note that the total dust exposure includes both the dust HTLCs and the excess mining fees of the counterparty commitment transaction log_info!(logger, "Cannot accept value that would put our total dust exposure at {} over the limit {} on counterparty commitment tx", - on_counterparty_tx_dust_htlc_exposure_msat, max_dust_htlc_exposure_msat); + next_remote_commitment_stats.dust_exposure_msat, max_dust_htlc_exposure_msat); return Err(LocalHTLCFailureReason::DustLimitCounterparty) } - let dust_buffer_feerate = self.get_dust_buffer_feerate(None); - let (htlc_success_tx_fee_sat, _) = second_stage_tx_fees_sat( - &funding.get_channel_type(), dust_buffer_feerate, - ); - let exposure_dust_limit_success_sats = htlc_success_tx_fee_sat + self.holder_dust_limit_satoshis; - if msg.amount_msat / 1000 < exposure_dust_limit_success_sats { - let on_holder_tx_dust_htlc_exposure_msat = htlc_stats.on_holder_tx_dust_exposure_msat; - if on_holder_tx_dust_htlc_exposure_msat > max_dust_htlc_exposure_msat { - log_info!(logger, "Cannot accept value that would put our exposure to dust HTLCs at {} over the limit {} on holder commitment tx", - on_holder_tx_dust_htlc_exposure_msat, max_dust_htlc_exposure_msat); - return Err(LocalHTLCFailureReason::DustLimitHolder) - } + if next_local_commitment_stats.dust_exposure_msat > max_dust_htlc_exposure_msat { + log_info!(logger, "Cannot accept value that would put our exposure to dust HTLCs at {} over the limit {} on holder commitment tx", + next_local_commitment_stats.dust_exposure_msat, max_dust_htlc_exposure_msat); + return Err(LocalHTLCFailureReason::DustLimitHolder) } if !funding.is_outbound() { - let removed_outbound_total_msat: u64 = self.pending_outbound_htlcs - .iter() - .filter_map(|htlc| { - matches!( - htlc.state, - OutboundHTLCState::AwaitingRemoteRevokeToRemove(OutboundHTLCOutcome::Success(_, _)) - | OutboundHTLCState::AwaitingRemovedRemoteRevoke(OutboundHTLCOutcome::Success(_, _)) - ) - .then_some(htlc.amount_msat) - }) - .sum(); - let pending_value_to_self_msat = - funding.value_to_self_msat + htlc_stats.pending_inbound_htlcs_value_msat - removed_outbound_total_msat; - let pending_remote_value_msat = - funding.get_value_satoshis() * 1000 - pending_value_to_self_msat; - // Subtract any non-HTLC outputs from the local and remote balances - let (_, remote_balance_before_fee_msat) = SpecTxBuilder {}.subtract_non_htlc_outputs( - funding.is_outbound(), - pending_value_to_self_msat, - pending_remote_value_msat, - funding.get_channel_type() - ); - - // `Some(())` is for the fee spike buffer we keep for the remote if the channel is - // not zero fee. This deviates from the spec because the fee spike buffer requirement - // doesn't exist on the receiver's side, only on the sender's. Note that with anchor - // outputs we are no longer as sensitive to fee spikes, so we need to account for them. - // - // A `None` `HTLCCandidate` is used as in this case because we're already accounting for - // the incoming HTLC as it has been fully committed by both sides. - let fee_spike_buffer_htlc = if funding.get_channel_type().supports_anchor_zero_fee_commitments() { - None - } else { - Some(()) - }; - - let mut remote_fee_cost_incl_stuck_buffer_msat = self.next_remote_commit_tx_fee_msat( - funding, None, fee_spike_buffer_htlc, - ); + let mut remote_fee_incl_fee_spike_buffer_htlc_msat = next_remote_commitment_stats.commit_tx_fee_sat * 1000; + // Note that with anchor outputs we are no longer as sensitive to fee spikes, so we don't need to account for them. if !funding.get_channel_type().supports_anchors_zero_fee_htlc_tx() { - remote_fee_cost_incl_stuck_buffer_msat *= FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE; + remote_fee_incl_fee_spike_buffer_htlc_msat *= FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE; } - if remote_balance_before_fee_msat.saturating_sub(funding.holder_selected_channel_reserve_satoshis * 1000) < remote_fee_cost_incl_stuck_buffer_msat { + // We unwrap here; if the HTLC exhausts the counterparty's balance, we should have rejected it at `update_add_htlc`, here the HTLC is already + // irrevocably committed to the channel. + let remote_balance_before_fee_msat = next_remote_commitment_stats.counterparty_balance_before_fee_msat.expect("The counterparty's balance before fees should never underflow"); + if remote_balance_before_fee_msat.saturating_sub(funding.holder_selected_channel_reserve_satoshis * 1000) < remote_fee_incl_fee_spike_buffer_htlc_msat { log_info!(logger, "Attempting to fail HTLC due to fee spike buffer violation in channel {}. Rebalancing is required.", &self.channel_id()); return Err(LocalHTLCFailureReason::FeeSpikeBuffer); } @@ -4544,7 +4710,7 @@ where /// which peer generated this transaction and "to whom" this transaction flows. #[inline] #[rustfmt::skip] - fn build_commitment_transaction(&self, funding: &FundingScope, commitment_number: u64, per_commitment_point: &PublicKey, local: bool, generated_by_local: bool, logger: &L) -> CommitmentData + fn build_commitment_transaction(&self, funding: &FundingScope, commitment_number: u64, per_commitment_point: &PublicKey, local: bool, generated_by_local: bool, logger: &L) -> CommitmentData<'_> where L::Target: Logger { let broadcaster_dust_limit_sat = if local { self.holder_dust_limit_satoshis } else { self.counterparty_dust_limit_satoshis }; @@ -4737,8 +4903,6 @@ where } let mut pending_outbound_htlcs_value_msat = 0; - let mut outbound_holding_cell_msat = 0; - let mut on_holder_tx_outbound_holding_cell_htlcs_count = 0; let mut pending_outbound_htlcs = self.pending_outbound_htlcs.len(); { let counterparty_dust_limit_success_sat = htlc_success_tx_fee_sat + context.counterparty_dust_limit_satoshis; @@ -4759,7 +4923,6 @@ where if let &HTLCUpdateAwaitingACK::AddHTLC { ref amount_msat, .. } = update { pending_outbound_htlcs += 1; pending_outbound_htlcs_value_msat += amount_msat; - outbound_holding_cell_msat += amount_msat; if *amount_msat / 1000 < counterparty_dust_limit_success_sat { on_counterparty_tx_dust_exposure_msat += amount_msat; } else { @@ -4767,8 +4930,6 @@ where } if *amount_msat / 1000 < holder_dust_limit_timeout_sat { on_holder_tx_dust_exposure_msat += amount_msat; - } else { - on_holder_tx_outbound_holding_cell_htlcs_count += 1; } } } @@ -4800,15 +4961,12 @@ where }); HTLCStats { - pending_inbound_htlcs: self.pending_inbound_htlcs.len(), pending_outbound_htlcs, pending_inbound_htlcs_value_msat, pending_outbound_htlcs_value_msat, on_counterparty_tx_dust_exposure_msat, extra_nondust_htlc_on_counterparty_tx_dust_exposure_msat, on_holder_tx_dust_exposure_msat, - outbound_holding_cell_msat, - on_holder_tx_outbound_holding_cell_htlcs_count, } } @@ -5139,31 +5297,7 @@ where } let num_htlcs = included_htlcs + addl_htlcs; - let commit_tx_fee_msat = SpecTxBuilder {}.commit_tx_fee_sat(context.feerate_per_kw, num_htlcs, funding.get_channel_type()) * 1000; - #[cfg(any(test, fuzzing))] - { - let mut fee = commit_tx_fee_msat; - if fee_spike_buffer_htlc.is_some() { - fee = SpecTxBuilder {}.commit_tx_fee_sat(context.feerate_per_kw, num_htlcs - 1, funding.get_channel_type()) * 1000; - } - let total_pending_htlcs = context.pending_inbound_htlcs.len() + context.pending_outbound_htlcs.len() - + context.holding_cell_htlc_updates.len(); - let commitment_tx_info = CommitmentTxInfoCached { - fee, - total_pending_htlcs, - next_holder_htlc_id: match htlc.origin { - HTLCInitiator::LocalOffered => context.next_holder_htlc_id + 1, - HTLCInitiator::RemoteOffered => context.next_holder_htlc_id, - }, - next_counterparty_htlc_id: match htlc.origin { - HTLCInitiator::LocalOffered => context.next_counterparty_htlc_id, - HTLCInitiator::RemoteOffered => context.next_counterparty_htlc_id + 1, - }, - feerate: context.feerate_per_kw, - }; - *funding.next_local_commitment_tx_fee_info_cached.lock().unwrap() = Some(commitment_tx_info); - } - commit_tx_fee_msat + SpecTxBuilder {}.commit_tx_fee_sat(context.feerate_per_kw, num_htlcs, funding.get_channel_type()) * 1000 } /// Get the commitment tx fee for the remote's next commitment transaction based on the number of @@ -5240,30 +5374,7 @@ where } let num_htlcs = included_htlcs + addl_htlcs; - let commit_tx_fee_msat = SpecTxBuilder {}.commit_tx_fee_sat(context.feerate_per_kw, num_htlcs, funding.get_channel_type()) * 1000; - #[cfg(any(test, fuzzing))] - if let Some(htlc) = &htlc { - let mut fee = commit_tx_fee_msat; - if fee_spike_buffer_htlc.is_some() { - fee = SpecTxBuilder {}.commit_tx_fee_sat(context.feerate_per_kw, num_htlcs - 1, funding.get_channel_type()) * 1000; - } - let total_pending_htlcs = context.pending_inbound_htlcs.len() + context.pending_outbound_htlcs.len(); - let commitment_tx_info = CommitmentTxInfoCached { - fee, - total_pending_htlcs, - next_holder_htlc_id: match htlc.origin { - HTLCInitiator::LocalOffered => context.next_holder_htlc_id + 1, - HTLCInitiator::RemoteOffered => context.next_holder_htlc_id, - }, - next_counterparty_htlc_id: match htlc.origin { - HTLCInitiator::LocalOffered => context.next_counterparty_htlc_id, - HTLCInitiator::RemoteOffered => context.next_counterparty_htlc_id + 1, - }, - feerate: context.feerate_per_kw, - }; - *funding.next_remote_commitment_tx_fee_info_cached.lock().unwrap() = Some(commitment_tx_info); - } - commit_tx_fee_msat + SpecTxBuilder {}.commit_tx_fee_sat(context.feerate_per_kw, num_htlcs, funding.get_channel_type()) * 1000 } #[rustfmt::skip] @@ -5340,6 +5451,50 @@ where _ => {}, } } + + // Once we're closed, the `ChannelMonitor` is responsible for resolving any remaining + // HTLCs. However, in the specific case of us pushing new HTLC(s) to the counterparty in + // the latest commitment transaction that we haven't actually sent due to a block + // `ChannelMonitorUpdate`, we may have some HTLCs that the `ChannelMonitor` won't know + // about and thus really need to be included in `dropped_outbound_htlcs`. + 'htlc_iter: for htlc in self.pending_outbound_htlcs.iter() { + if let OutboundHTLCState::LocalAnnounced(_) = htlc.state { + for update in self.blocked_monitor_updates.iter() { + for update in update.update.updates.iter() { + let have_htlc = match update { + ChannelMonitorUpdateStep::LatestCounterpartyCommitment { + htlc_data, + .. + } => { + let dust = + htlc_data.dust_htlcs.iter().map(|(_, source)| source.as_ref()); + let nondust = + htlc_data.nondust_htlc_sources.iter().map(|s| Some(s)); + dust.chain(nondust).any(|source| source == Some(&htlc.source)) + }, + ChannelMonitorUpdateStep::LatestCounterpartyCommitmentTXInfo { + htlc_outputs, + .. + } => htlc_outputs.iter().any(|(_, source)| { + source.as_ref().map(|s| &**s) == Some(&htlc.source) + }), + _ => continue, + }; + debug_assert!(have_htlc); + if have_htlc { + dropped_outbound_htlcs.push(( + htlc.source.clone(), + htlc.payment_hash, + counterparty_node_id, + self.channel_id, + )); + } + continue 'htlc_iter; + } + } + } + } + let monitor_update = if let Some(funding_txo) = funding.get_funding_txo() { // If we haven't yet exchanged funding signatures (ie channel_state < AwaitingChannelReady), // returning a channel monitor update here would imply a channel monitor update before @@ -5489,7 +5644,7 @@ where fn funding_tx_constructed( &mut self, funding: &mut FundingScope, signing_session: &mut InteractiveTxSigningSession, is_splice: bool, holder_commitment_transaction_number: u64, logger: &L - ) -> Result<(msgs::CommitmentSigned, Option), msgs::TxAbort> + ) -> Result where L::Target: Logger { @@ -5517,10 +5672,13 @@ where funding .channel_transaction_parameters.funding_outpoint = Some(outpoint); + self.channel_state = ChannelState::FundingNegotiated(FundingNegotiatedFlags::new()); + self.channel_state.set_interactive_signing(); + if is_splice { debug_assert_eq!( holder_commitment_transaction_number, - self.cur_counterparty_commitment_transaction_number, + self.counterparty_next_commitment_transaction_number, ); // TODO(splicing) Forced error, as the use case is not complete return Err(msgs::TxAbort { @@ -5531,96 +5689,77 @@ where self.assert_no_commitment_advancement(holder_commitment_transaction_number, "initial commitment_signed"); } - let commitment_signed = self.get_initial_commitment_signed(&funding, logger); + let commitment_signed = self.get_initial_commitment_signed_v2(&funding, logger); let commitment_signed = match commitment_signed { - Ok(commitment_signed) => commitment_signed, - Err(e) => { + Some(commitment_signed) => commitment_signed, + // TODO(splicing): Support async signing + None => { funding.channel_transaction_parameters.funding_outpoint = None; return Err(msgs::TxAbort { channel_id: self.channel_id(), - data: e.message().to_owned().into_bytes(), + data: "Failed to get signature for commitment_signed".to_owned().into_bytes(), }); }, }; - let funding_ready_for_sig_event = if signing_session.local_inputs_count() == 0 { - if signing_session.provide_holder_witnesses(self.channel_id, Vec::new()).is_err() { - debug_assert!( - false, - "Zero inputs were provided & zero witnesses were provided, but a count mismatch was somehow found", - ); - return Err(msgs::TxAbort { - channel_id: self.channel_id(), - data: "V2 channel rejected due to sender error".to_owned().into_bytes(), - }); - } - None - } else { - // TODO(dual_funding): Send event for signing if we've contributed funds. - // Inform the user that SIGHASH_ALL must be used for all signatures when contributing - // inputs/signatures. - // Also warn the user that we don't do anything to prevent the counterparty from - // providing non-standard witnesses which will prevent the funding transaction from - // confirming. This warning must appear in doc comments wherever the user is contributing - // funds, whether they are initiator or acceptor. - // - // The following warning can be used when the APIs allowing contributing inputs become available: - //
- // WARNING: LDK makes no attempt to prevent the counterparty from using non-standard inputs which - // will prevent the funding transaction from being relayed on the bitcoin network and hence being - // confirmed. - //
- debug_assert!( - false, - "We don't support users providing inputs but somehow we had more than zero inputs", - ); - return Err(msgs::TxAbort { - channel_id: self.channel_id(), - data: "V2 channel rejected due to sender error".to_owned().into_bytes(), - }); - }; - - let mut channel_state = ChannelState::FundingNegotiated(FundingNegotiatedFlags::new()); - channel_state.set_interactive_signing(); - self.channel_state = channel_state; - - Ok((commitment_signed, funding_ready_for_sig_event)) + Ok(commitment_signed) } /// Asserts that the commitment tx numbers have not advanced from their initial number. - #[rustfmt::skip] - fn assert_no_commitment_advancement(&self, holder_commitment_transaction_number: u64, msg_name: &str) { - if self.commitment_secrets.get_min_seen_secret() != (1 << 48) || - self.cur_counterparty_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER || - holder_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER { - debug_assert!(false, "Should not have advanced channel commitment tx numbers prior to {}", - msg_name); + fn assert_no_commitment_advancement( + &self, holder_commitment_transaction_number: u64, msg_name: &str, + ) { + if self.commitment_secrets.get_min_seen_secret() != (1 << 48) + || self.counterparty_next_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER + || holder_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER + { + debug_assert!( + false, + "Should not have advanced channel commitment tx numbers prior to {}", + msg_name + ); } } - #[rustfmt::skip] fn get_initial_counterparty_commitment_signature( - &self, funding: &FundingScope, logger: &L - ) -> Result + &self, funding: &FundingScope, logger: &L, + ) -> Option where SP::Target: SignerProvider, - L::Target: Logger + L::Target: Logger, { - let commitment_data = self.build_commitment_transaction(funding, - self.cur_counterparty_commitment_transaction_number, - &self.counterparty_cur_commitment_point.unwrap(), false, false, logger); + let mut commitment_number = self.counterparty_next_commitment_transaction_number; + let mut commitment_point = self.counterparty_next_commitment_point.unwrap(); + + // Use the previous commitment number and point when splicing since they shouldn't change. + if commitment_number != INITIAL_COMMITMENT_NUMBER { + commitment_number += 1; + commitment_point = self.counterparty_current_commitment_point.unwrap(); + } + + let commitment_data = self.build_commitment_transaction( + funding, + commitment_number, + &commitment_point, + false, + false, + logger, + ); let counterparty_initial_commitment_tx = commitment_data.tx; match self.holder_signer { // TODO (taproot|arik): move match into calling method for Taproot ChannelSignerType::Ecdsa(ref ecdsa) => { let channel_parameters = &funding.channel_transaction_parameters; - ecdsa.sign_counterparty_commitment(channel_parameters, &counterparty_initial_commitment_tx, Vec::new(), Vec::new(), &self.secp_ctx) + ecdsa + .sign_counterparty_commitment( + channel_parameters, + &counterparty_initial_commitment_tx, + Vec::new(), + Vec::new(), + &self.secp_ctx, + ) .map(|(signature, _)| signature) - .map_err(|()| { - let msg = "Failed to get signatures for new commitment_signed"; - let reason = ClosureReason::ProcessingError { err: msg.to_owned() }; - ChannelError::Close((msg.to_owned(), reason)) - }) + .ok() }, // TODO (taproot|arik) #[cfg(taproot)] @@ -5628,55 +5767,48 @@ where } } - #[rustfmt::skip] - fn get_initial_commitment_signed( - &mut self, funding: &FundingScope, logger: &L - ) -> Result + fn get_initial_commitment_signed_v2( + &mut self, funding: &FundingScope, logger: &L, + ) -> Option where SP::Target: SignerProvider, - L::Target: Logger + L::Target: Logger, { - if !matches!( - self.channel_state, ChannelState::NegotiatingFunding(flags) - if flags == (NegotiatingFundingFlags::OUR_INIT_SENT | NegotiatingFundingFlags::THEIR_INIT_SENT) - ) { - debug_assert!(false); - let msg = "Tried to get an initial commitment_signed messsage at a time other than \ - immediately after initial handshake completion (or tried to get funding_created twice)"; - let reason = ClosureReason::ProcessingError { err: msg.to_owned() }; - return Err(ChannelError::Close((msg.to_owned(), reason))); - } - - let signature = match self.get_initial_counterparty_commitment_signature(funding, logger) { - Ok(res) => res, - Err(e) => { - log_error!(logger, "Got bad signatures: {:?}!", e); - return Err(e); - } - }; - - log_info!(logger, "Generated commitment_signed for peer for channel {}", &self.channel_id()); + assert!( + matches!(self.channel_state, ChannelState::FundingNegotiated(flags) if flags.is_interactive_signing()) + ); - Ok(msgs::CommitmentSigned { - channel_id: self.channel_id, - htlc_signatures: vec![], - signature, - funding_txid: funding.get_funding_txo().map(|funding_txo| funding_txo.txid), - #[cfg(taproot)] - partial_signature_with_nonce: None, - }) + let signature = self.get_initial_counterparty_commitment_signature(funding, logger); + if let Some(signature) = signature { + log_info!( + logger, + "Generated commitment_signed for peer for channel {}", + &self.channel_id() + ); + Some(msgs::CommitmentSigned { + channel_id: self.channel_id, + htlc_signatures: vec![], + signature, + funding_txid: funding.get_funding_txo().map(|funding_txo| funding_txo.txid), + #[cfg(taproot)] + partial_signature_with_nonce: None, + }) + } else { + // TODO(splicing): Support async signing + None + } } #[cfg(all(test))] pub fn get_initial_counterparty_commitment_signature_for_test( &mut self, funding: &mut FundingScope, logger: &L, - counterparty_cur_commitment_point_override: PublicKey, - ) -> Result + counterparty_next_commitment_point_override: PublicKey, + ) -> Option where SP::Target: SignerProvider, L::Target: Logger, { - self.counterparty_cur_commitment_point = Some(counterparty_cur_commitment_point_override); + self.counterparty_next_commitment_point = Some(counterparty_next_commitment_point_override); self.get_initial_counterparty_commitment_signature(funding, logger) } @@ -5776,35 +5908,6 @@ where Ok(false) } - #[rustfmt::skip] - fn check_for_funding_tx_spent( - &mut self, funding: &FundingScope, tx: &Transaction, logger: &L, - ) -> Result<(), ClosureReason> - where - L::Target: Logger, - { - let funding_txo = match funding.get_funding_txo() { - Some(funding_txo) => funding_txo, - None => { - debug_assert!(false); - return Ok(()); - }, - }; - - for input in tx.input.iter() { - if input.previous_output == funding_txo.into_bitcoin_outpoint() { - log_info!( - logger, "Detected channel-closing tx {} spending {}:{}, closing channel {}", - tx.compute_txid(), input.previous_output.txid, input.previous_output.vout, - &self.channel_id(), - ); - return Err(ClosureReason::CommitmentTxConfirmed); - } - } - - Ok(()) - } - /// Returns SCIDs that have been associated with the channel's funding transactions. pub fn historical_scids(&self) -> &[u64] { &self.historical_scids[..] @@ -5875,22 +5978,61 @@ fn get_v2_channel_reserve_satoshis(channel_value_satoshis: u64, dust_limit_satos cmp::min(channel_value_satoshis, cmp::max(q, dust_limit_satoshis)) } +#[cfg(splicing)] +fn check_splice_contribution_sufficient( + channel_balance: Amount, contribution: &SpliceContribution, is_initiator: bool, + funding_feerate: FeeRate, +) -> Result { + let contribution_amount = contribution.value(); + if contribution_amount < SignedAmount::ZERO { + let estimated_fee = Amount::from_sat(estimate_v2_funding_transaction_fee( + contribution.inputs(), + contribution.outputs(), + is_initiator, + true, // is_splice + funding_feerate.to_sat_per_kwu() as u32, + )); + + if channel_balance >= contribution_amount.unsigned_abs() + estimated_fee { + Ok(estimated_fee) + } else { + Err(ChannelError::Warn(format!( + "Available channel balance {} is lower than needed for splicing out {}, considering fees of {}", + channel_balance, contribution_amount.unsigned_abs(), estimated_fee, + ))) + } + } else { + check_v2_funding_inputs_sufficient( + contribution_amount.to_sat(), + contribution.inputs(), + is_initiator, + true, + funding_feerate.to_sat_per_kwu() as u32, + ) + .map(Amount::from_sat) + } +} + /// Estimate our part of the fee of the new funding transaction. -/// input_count: Number of contributed inputs. -/// witness_weight: The witness weight for contributed inputs. #[allow(dead_code)] // TODO(dual_funding): TODO(splicing): Remove allow once used. #[rustfmt::skip] fn estimate_v2_funding_transaction_fee( - is_initiator: bool, input_count: usize, witness_weight: Weight, + funding_inputs: &[FundingTxInput], outputs: &[TxOut], is_initiator: bool, is_splice: bool, funding_feerate_sat_per_1000_weight: u32, ) -> u64 { - // Inputs - let mut weight = (input_count as u64) * BASE_INPUT_WEIGHT; + let input_weight: u64 = funding_inputs + .iter() + .map(|input| BASE_INPUT_WEIGHT.saturating_add(input.utxo.satisfaction_weight)) + .fold(0, |total_weight, input_weight| total_weight.saturating_add(input_weight)); - // Witnesses - weight = weight.saturating_add(witness_weight.to_wu()); + let output_weight: u64 = outputs + .iter() + .map(|txout| txout.weight().to_wu()) + .fold(0, |total_weight, output_weight| total_weight.saturating_add(output_weight)); - // If we are the initiator, we must pay for weight of all common fields in the funding transaction. + let mut weight = input_weight.saturating_add(output_weight); + + // The initiator pays for all common fields and the shared output in the funding transaction. if is_initiator { weight = weight .saturating_add(TX_COMMON_FIELDS_WEIGHT) @@ -5899,7 +6041,15 @@ fn estimate_v2_funding_transaction_fee( // to calculate the contributed weight, so we use an all-zero hash. .saturating_add(get_output_weight(&ScriptBuf::new_p2wsh( &WScriptHash::from_raw_hash(Hash::all_zeros()) - )).to_wu()) + )).to_wu()); + + // The splice initiator pays for the input spending the previous funding output. + if is_splice { + weight = weight + .saturating_add(BASE_INPUT_WEIGHT) + .saturating_add(EMPTY_SCRIPT_SIG_WEIGHT) + .saturating_add(FUNDING_TRANSACTION_WITNESS_WEIGHT); + } } fee_for_weight(funding_feerate_sat_per_1000_weight, weight) @@ -5914,28 +6064,16 @@ fn estimate_v2_funding_transaction_fee( #[cfg(splicing)] #[rustfmt::skip] fn check_v2_funding_inputs_sufficient( - contribution_amount: i64, funding_inputs: &[(TxIn, Transaction, Weight)], is_initiator: bool, + contribution_amount: i64, funding_inputs: &[FundingTxInput], is_initiator: bool, is_splice: bool, funding_feerate_sat_per_1000_weight: u32, ) -> Result { - let mut total_input_witness_weight = Weight::from_wu(funding_inputs.iter().map(|(_, _, w)| w.to_wu()).sum()); - let mut funding_inputs_len = funding_inputs.len(); - if is_initiator && is_splice { - // consider the weight of the input and witness needed for spending the old funding transaction - funding_inputs_len += 1; - total_input_witness_weight += Weight::from_wu(FUNDING_TRANSACTION_WITNESS_WEIGHT); - } - let estimated_fee = estimate_v2_funding_transaction_fee(is_initiator, funding_inputs_len, total_input_witness_weight, funding_feerate_sat_per_1000_weight); + let estimated_fee = estimate_v2_funding_transaction_fee( + funding_inputs, &[], is_initiator, is_splice, funding_feerate_sat_per_1000_weight, + ); let mut total_input_sats = 0u64; - for (idx, input) in funding_inputs.iter().enumerate() { - if let Some(output) = input.1.output.get(input.0.previous_output.vout as usize) { - total_input_sats = total_input_sats.saturating_add(output.value.to_sat()); - } else { - return Err(ChannelError::Warn(format!( - "Transaction with txid {} does not have an output with vout of {} corresponding to TxIn at funding_inputs[{}]", - input.1.compute_txid(), input.0.previous_output.vout, idx - ))); - } + for FundingTxInput { utxo, .. } in funding_inputs.iter() { + total_input_sats = total_input_sats.saturating_add(utxo.output.value.to_sat()); } // If the inputs are enough to cover intended contribution amount, with fees even when @@ -5965,10 +6103,7 @@ pub(super) struct FundingNegotiationContext { /// Whether we initiated the funding negotiation. pub is_initiator: bool, /// The amount in satoshis we will be contributing to the channel. - pub our_funding_contribution_satoshis: i64, - /// The amount in satoshis our counterparty will be contributing to the channel. - #[allow(dead_code)] // TODO(dual_funding): Remove once contribution to V2 channels is enabled. - pub their_funding_contribution_satoshis: Option, + pub our_funding_contribution: SignedAmount, /// The funding transaction locktime suggested by the initiator. If set by us, it is always set /// to the current block height to align incentives against fee-sniping. pub funding_tx_locktime: LockTime, @@ -5980,7 +6115,10 @@ pub(super) struct FundingNegotiationContext { pub shared_funding_input: Option, /// The funding inputs we will be contributing to the channel. #[allow(dead_code)] // TODO(dual_funding): Remove once contribution to V2 channels is enabled. - pub our_funding_inputs: Vec<(TxIn, TransactionU16LenLimited)>, + pub our_funding_inputs: Vec, + /// The funding outputs we will be contributing to the channel. + #[allow(dead_code)] // TODO(dual_funding): Remove once contribution to V2 channels is enabled. + pub our_funding_outputs: Vec, /// The change output script. This will be used if needed or -- if not set -- generated using /// `SignerProvider::get_destination_script`. #[allow(dead_code)] // TODO(splicing): Remove once splicing is enabled. @@ -6010,10 +6148,8 @@ impl FundingNegotiationContext { debug_assert!(matches!(context.channel_state, ChannelState::NegotiatingFunding(_))); } - // Add output for funding tx // Note: For the error case when the inputs are insufficient, it will be handled after // the `calculate_change_output_value` call below - let mut funding_outputs = Vec::new(); let shared_funding_output = TxOut { value: Amount::from_sat(funding.get_value_satoshis()), @@ -6021,36 +6157,47 @@ impl FundingNegotiationContext { }; // Optionally add change output - if self.our_funding_contribution_satoshis > 0 { - let change_value_opt = calculate_change_output_value( + let change_value_opt = if self.our_funding_contribution > SignedAmount::ZERO { + calculate_change_output_value( &self, self.shared_funding_input.is_some(), &shared_funding_output.script_pubkey, - &funding_outputs, context.holder_dust_limit_satoshis, - )?; - if let Some(change_value) = change_value_opt { - let change_script = if let Some(script) = self.change_script { - script - } else { - signer_provider - .get_destination_script(context.channel_keys_id) - .map_err(|_err| AbortReason::InternalError("Error getting change script"))? - }; - let mut change_output = - TxOut { value: Amount::from_sat(change_value), script_pubkey: change_script }; - let change_output_weight = get_output_weight(&change_output.script_pubkey).to_wu(); - let change_output_fee = - fee_for_weight(self.funding_feerate_sat_per_1000_weight, change_output_weight); - let change_value_decreased_with_fee = - change_value.saturating_sub(change_output_fee); - // Check dust limit again - if change_value_decreased_with_fee > context.holder_dust_limit_satoshis { - change_output.value = Amount::from_sat(change_value_decreased_with_fee); - funding_outputs.push(change_output); - } - } - } + )? + } else { + None + }; + + let mut funding_outputs = self.our_funding_outputs; + + if let Some(change_value) = change_value_opt { + let change_script = if let Some(script) = self.change_script { + script + } else { + signer_provider + .get_destination_script(context.channel_keys_id) + .map_err(|_err| AbortReason::InternalError("Error getting change script"))? + }; + let mut change_output = + TxOut { value: Amount::from_sat(change_value), script_pubkey: change_script }; + let change_output_weight = get_output_weight(&change_output.script_pubkey).to_wu(); + let change_output_fee = + fee_for_weight(self.funding_feerate_sat_per_1000_weight, change_output_weight); + let change_value_decreased_with_fee = change_value.saturating_sub(change_output_fee); + // Check dust limit again + if change_value_decreased_with_fee > context.holder_dust_limit_satoshis { + change_output.value = Amount::from_sat(change_value_decreased_with_fee); + funding_outputs.push(change_output); + } + } + + let funding_inputs = self + .our_funding_inputs + .into_iter() + .map(|FundingTxInput { utxo, sequence, prevtx }| { + (TxIn { previous_output: utxo.outpoint, sequence, ..Default::default() }, prevtx) + }) + .collect(); let constructor_args = InteractiveTxConstructorArgs { entropy_source, @@ -6060,7 +6207,7 @@ impl FundingNegotiationContext { feerate_sat_per_kw: self.funding_feerate_sat_per_1000_weight, is_initiator: self.is_initiator, funding_tx_locktime: self.funding_tx_locktime, - inputs_to_contribute: self.our_funding_inputs, + inputs_to_contribute: funding_inputs, shared_funding_input: self.shared_funding_input, shared_funding_output: SharedOwnedOutput::new( shared_funding_output, @@ -6093,6 +6240,12 @@ where /// Info about an in-progress, pending splice (if any), on the pre-splice channel #[cfg(splicing)] pending_splice: Option, + + /// Once we become quiescent, if we're the initiator, there's some action we'll want to take. + /// This keeps track of that action. Note that if we become quiescent and we're not the + /// initiator we may be able to merge this action into what the counterparty wanted to do (e.g. + /// in the case of splicing). + quiescent_action: Option, } #[cfg(splicing)] @@ -6110,12 +6263,11 @@ macro_rules! promote_splice_funding { } #[cfg(any(test, fuzzing))] -struct CommitmentTxInfoCached { - fee: u64, - total_pending_htlcs: usize, - next_holder_htlc_id: u64, - next_counterparty_htlc_id: u64, - feerate: u32, +#[derive(Clone, Copy, Default)] +struct PredictedNextFee { + predicted_feerate: u32, + predicted_nondust_htlc_count: usize, + predicted_fee_sat: u64, } /// Contents of a wire message that fails an HTLC backwards. Useful for [`FundedChannel::fail_htlc`] to @@ -6362,7 +6514,7 @@ where Ok((closing_transaction, total_fee_satoshis)) } - fn funding_outpoint(&self) -> OutPoint { + pub fn funding_outpoint(&self) -> OutPoint { self.funding.channel_transaction_parameters.funding_outpoint.unwrap() } @@ -6787,15 +6939,15 @@ where // They probably disconnected/reconnected and re-sent the channel_ready, which is // required, or they're sending a fresh SCID alias. let expected_point = - if self.context.cur_counterparty_commitment_transaction_number == INITIAL_COMMITMENT_NUMBER - 1 { + if self.context.counterparty_next_commitment_transaction_number == INITIAL_COMMITMENT_NUMBER - 1 { // If they haven't ever sent an updated point, the point they send should match - // the current one. - self.context.counterparty_cur_commitment_point - } else if self.context.cur_counterparty_commitment_transaction_number == INITIAL_COMMITMENT_NUMBER - 2 { + // the next one. + self.context.counterparty_next_commitment_point + } else if self.context.counterparty_next_commitment_transaction_number == INITIAL_COMMITMENT_NUMBER - 2 { // If we've advanced the commitment number once, the second commitment point is - // at `counterparty_prev_commitment_point`, which is not yet revoked. - debug_assert!(self.context.counterparty_prev_commitment_point.is_some()); - self.context.counterparty_prev_commitment_point + // at `counterparty_current_commitment_point`, which is not yet revoked. + debug_assert!(self.context.counterparty_current_commitment_point.is_some()); + self.context.counterparty_current_commitment_point } else { // If they have sent updated points, channel_ready is always supposed to match // their "first" point, which we re-derive here. @@ -6809,8 +6961,8 @@ where return Ok(None); } - self.context.counterparty_prev_commitment_point = self.context.counterparty_cur_commitment_point; - self.context.counterparty_cur_commitment_point = Some(msg.next_per_commitment_point); + self.context.counterparty_current_commitment_point = self.context.counterparty_next_commitment_point; + self.context.counterparty_next_commitment_point = Some(msg.next_per_commitment_point); // Clear any interactive signing session. self.interactive_tx_signing_session = None; @@ -6955,7 +7107,7 @@ where } #[rustfmt::skip] - pub fn commitment_signed_initial_v2( + pub fn initial_commitment_signed_v2( &mut self, msg: &msgs::CommitmentSigned, best_block: BestBlock, signer_provider: &SP, logger: &L ) -> Result::EcdsaSigner>, ChannelError> where L::Target: Logger @@ -6969,7 +7121,7 @@ where } let holder_commitment_point = &mut self.holder_commitment_point.clone(); - self.context.assert_no_commitment_advancement(holder_commitment_point.transaction_number(), "initial commitment_signed"); + self.context.assert_no_commitment_advancement(holder_commitment_point.next_transaction_number(), "initial commitment_signed"); let (channel_monitor, _) = self.initial_commitment_signed( self.context.channel_id(), msg.signature, holder_commitment_point, best_block, signer_provider, logger)?; @@ -6978,15 +7130,7 @@ where log_info!(logger, "Received initial commitment_signed from peer for channel {}", &self.context.channel_id()); self.monitor_updating_paused(false, false, false, Vec::new(), Vec::new(), Vec::new()); - - if let Some(tx_signatures) = self.interactive_tx_signing_session.as_mut().and_then( - |session| session.received_commitment_signed() - ) { - // We're up first for submitting our tx_signatures, but our monitor has not persisted yet - // so they'll be sent as soon as that's done. - self.context.monitor_pending_tx_signatures = Some(tx_signatures); - } - + self.interactive_tx_signing_session.as_mut().expect("signing session should be present").received_commitment_signed(); Ok(channel_monitor) } @@ -7026,9 +7170,17 @@ where }) .and_then(|funding_negotiation| funding_negotiation.as_funding()) .expect("Funding must exist for negotiated pending splice"); + let transaction_number = self.holder_commitment_point.current_transaction_number(); + let commitment_point = self.holder_commitment_point.current_point().ok_or_else(|| { + debug_assert!(false); + ChannelError::close( + "current_point should be set for channels initiating splicing".to_owned(), + ) + })?; let (holder_commitment_tx, _) = self.context.validate_commitment_signed( pending_splice_funding, - &self.holder_commitment_point, + transaction_number, + commitment_point, msg, logger, )?; @@ -7038,8 +7190,8 @@ where .context .build_commitment_transaction( pending_splice_funding, - self.context.cur_counterparty_commitment_transaction_number, - &self.context.counterparty_cur_commitment_point.unwrap(), + self.context.counterparty_next_commitment_transaction_number + 1, + &self.context.counterparty_current_commitment_point.unwrap(), false, false, logger, @@ -7072,13 +7224,11 @@ where channel_id: Some(self.context.channel_id()), }; - let tx_signatures = self - .interactive_tx_signing_session + self.interactive_tx_signing_session .as_mut() .expect("Signing session must exist for negotiated pending splice") .received_commitment_signed(); self.monitor_updating_paused(false, false, false, Vec::new(), Vec::new(), Vec::new()); - self.context.monitor_pending_tx_signatures = tx_signatures; Ok(self.push_ret_blockable_mon_update(monitor_update)) } @@ -7114,9 +7264,17 @@ where )); } + let transaction_number = self.holder_commitment_point.next_transaction_number(); + let commitment_point = self.holder_commitment_point.next_point(); let update = self .context - .validate_commitment_signed(&self.funding, &self.holder_commitment_point, msg, logger) + .validate_commitment_signed( + &self.funding, + transaction_number, + commitment_point, + msg, + logger, + ) .map(|(commitment_tx, htlcs_included)| { let (nondust_htlc_sources, dust_htlcs) = Self::get_commitment_htlc_data(&htlcs_included); @@ -7178,9 +7336,12 @@ where funding_txid )) })?; + let transaction_number = self.holder_commitment_point.next_transaction_number(); + let commitment_point = self.holder_commitment_point.next_point(); let (commitment_tx, htlcs_included) = self.context.validate_commitment_signed( funding, - &self.holder_commitment_point, + transaction_number, + commitment_point, msg, logger, )?; @@ -7617,11 +7778,11 @@ where "Peer provided an invalid per_commitment_secret".to_owned() ); - if let Some(counterparty_prev_commitment_point) = - self.context.counterparty_prev_commitment_point + if let Some(counterparty_current_commitment_point) = + self.context.counterparty_current_commitment_point { if PublicKey::from_secret_key(&self.context.secp_ctx, &secret) - != counterparty_prev_commitment_point + != counterparty_current_commitment_point { return Err(ChannelError::close("Got a revoke commitment secret which didn't correspond to their current pubkey".to_owned())); } @@ -7638,21 +7799,11 @@ where return Err(ChannelError::close("Received an unexpected revoke_and_ack".to_owned())); } - #[cfg(any(test, fuzzing))] - { - for funding in - core::iter::once(&mut self.funding).chain(self.pending_funding.iter_mut()) - { - *funding.next_local_commitment_tx_fee_info_cached.lock().unwrap() = None; - *funding.next_remote_commitment_tx_fee_info_cached.lock().unwrap() = None; - } - } - match &self.context.holder_signer { ChannelSignerType::Ecdsa(ecdsa) => { ecdsa .validate_counterparty_revocation( - self.context.cur_counterparty_commitment_transaction_number + 1, + self.context.counterparty_next_commitment_transaction_number + 1, &secret, ) .map_err(|_| { @@ -7667,7 +7818,7 @@ where self.context .commitment_secrets .provide_secret( - self.context.cur_counterparty_commitment_transaction_number + 1, + self.context.counterparty_next_commitment_transaction_number + 1, msg.per_commitment_secret, ) .map_err(|_| { @@ -7677,7 +7828,7 @@ where let mut monitor_update = ChannelMonitorUpdate { update_id: self.context.latest_monitor_update_id, updates: vec![ChannelMonitorUpdateStep::CommitmentSecret { - idx: self.context.cur_counterparty_commitment_transaction_number + 1, + idx: self.context.counterparty_next_commitment_transaction_number + 1, secret: msg.per_commitment_secret, }], channel_id: Some(self.context.channel_id()), @@ -7689,10 +7840,10 @@ where // channel based on that, but stepping stuff here should be safe either way. self.context.channel_state.clear_awaiting_remote_revoke(); self.mark_response_received(); - self.context.counterparty_prev_commitment_point = - self.context.counterparty_cur_commitment_point; - self.context.counterparty_cur_commitment_point = Some(msg.next_per_commitment_point); - self.context.cur_counterparty_commitment_transaction_number -= 1; + self.context.counterparty_current_commitment_point = + self.context.counterparty_next_commitment_point; + self.context.counterparty_next_commitment_point = Some(msg.next_per_commitment_point); + self.context.counterparty_next_commitment_transaction_number -= 1; if self.context.announcement_sigs_state == AnnouncementSigsState::Committed { self.context.announcement_sigs_state = AnnouncementSigsState::PeerReceived; @@ -7988,10 +8139,43 @@ where } } + pub fn funding_transaction_signed( + &mut self, witnesses: Vec, + ) -> Result<(Option, Option), APIError> { + let (funding_tx_opt, tx_signatures_opt) = self + .interactive_tx_signing_session + .as_mut() + .ok_or_else(|| APIError::APIMisuseError { + err: format!( + "Channel {} not expecting funding signatures", + self.context.channel_id + ), + }) + .and_then(|signing_session| { + signing_session + .provide_holder_witnesses( + &self.context.secp_ctx, + self.context.channel_id, + witnesses, + ) + .map_err(|err| APIError::APIMisuseError { err }) + })?; + + if tx_signatures_opt.is_some() { + self.context.channel_state.set_our_tx_signatures_ready(); + } + + if funding_tx_opt.is_some() { + self.funding.funding_transaction = funding_tx_opt.clone(); + self.context.channel_state = + ChannelState::AwaitingChannelReady(AwaitingChannelReadyFlags::new()); + } + + Ok((tx_signatures_opt, funding_tx_opt)) + } + #[rustfmt::skip] - pub fn tx_signatures(&mut self, msg: &msgs::TxSignatures, logger: &L) -> Result<(Option, Option), ChannelError> - where L::Target: Logger - { + pub fn tx_signatures(&mut self, msg: &msgs::TxSignatures) -> Result<(Option, Option), ChannelError> { if !self.context.channel_state.is_interactive_signing() || self.context.channel_state.is_their_tx_signatures_sent() { @@ -8014,50 +8198,29 @@ where return Err(ChannelError::Close((msg.to_owned(), reason))); } - if msg.witnesses.len() != signing_session.remote_inputs_count() { - return Err(ChannelError::Warn( - "Witness count did not match contributed input count".to_string() - )); - } - for witness in &msg.witnesses { if witness.is_empty() { let msg = "Unexpected empty witness in tx_signatures received"; let reason = ClosureReason::ProcessingError { err: msg.to_owned() }; return Err(ChannelError::Close((msg.to_owned(), reason))); } - - // TODO(dual_funding): Check all sigs are SIGHASH_ALL. - - // TODO(dual_funding): I don't see how we're going to be able to ensure witness-standardness - // for spending. Doesn't seem to be anything in rust-bitcoin. } let (holder_tx_signatures_opt, funding_tx_opt) = signing_session.received_tx_signatures(msg.clone()) - .map_err(|_| ChannelError::Warn("Witness count did not match contributed input count".to_string()))?; + .map_err(|msg| ChannelError::Warn(msg))?; // Set `THEIR_TX_SIGNATURES_SENT` flag after all potential errors. self.context.channel_state.set_their_tx_signatures_sent(); if funding_tx_opt.is_some() { + // TODO(splicing): Transition back to `ChannelReady` and not `AwaitingChannelReady` + // We will also need to use the pending `FundingScope` in the splicing case. + // // We have a finalized funding transaction, so we can set the funding transaction. self.funding.funding_transaction = funding_tx_opt.clone(); + self.context.channel_state = ChannelState::AwaitingChannelReady(AwaitingChannelReadyFlags::new()); } - // Note that `holder_tx_signatures_opt` will be `None` if we sent `tx_signatures` first, so this - // case checks if there is a monitor persist in progress when we need to respond with our `tx_signatures` - // and sets it as pending. - if holder_tx_signatures_opt.is_some() && self.is_awaiting_initial_mon_persist() { - log_debug!(logger, "Not sending tx_signatures: a monitor update is in progress. Setting monitor_pending_tx_signatures."); - self.context.monitor_pending_tx_signatures = holder_tx_signatures_opt; - return Ok((None, None)); - } - - if holder_tx_signatures_opt.is_some() { - self.context.channel_state.set_our_tx_signatures_ready(); - } - - self.context.channel_state = ChannelState::AwaitingChannelReady(AwaitingChannelReadyFlags::new()); Ok((funding_tx_opt, holder_tx_signatures_opt)) } else { let msg = "Unexpected tx_signatures. No funding transaction awaiting signatures"; @@ -8214,11 +8377,13 @@ where // Reset any quiescence-related state as it is implicitly terminated once disconnected. if matches!(self.context.channel_state, ChannelState::ChannelReady(_)) { - self.context.channel_state.clear_awaiting_quiescence(); + if self.quiescent_action.is_some() { + // If we were trying to get quiescent, try again after reconnection. + self.context.channel_state.set_awaiting_quiescence(); + } self.context.channel_state.clear_local_stfu_sent(); self.context.channel_state.clear_remote_stfu_sent(); self.context.channel_state.clear_quiescent(); - self.context.is_holder_quiescence_initiator.take(); } self.context.channel_state.set_peer_disconnected(); @@ -8309,17 +8474,6 @@ where mem::swap(&mut finalized_claimed_htlcs, &mut self.context.monitor_pending_finalized_fulfills); let mut pending_update_adds = Vec::new(); mem::swap(&mut pending_update_adds, &mut self.context.monitor_pending_update_adds); - // For channels established with V2 establishment we won't send a `tx_signatures` when we're in - // MonitorUpdateInProgress (and we assume the user will never directly broadcast the funding - // transaction and waits for us to do it). - let tx_signatures = self.context.monitor_pending_tx_signatures.take(); - if tx_signatures.is_some() { - if self.context.channel_state.is_their_tx_signatures_sent() { - self.context.channel_state = ChannelState::AwaitingChannelReady(AwaitingChannelReadyFlags::new()); - } else { - self.context.channel_state.set_our_tx_signatures_ready(); - } - } if self.context.channel_state.is_peer_disconnected() { self.context.monitor_pending_revoke_and_ack = false; @@ -8327,7 +8481,7 @@ where return MonitorRestoreUpdates { raa: None, commitment_update: None, order: RAACommitmentOrder::RevokeAndACKFirst, accepted_htlcs, failed_htlcs, finalized_claimed_htlcs, pending_update_adds, - funding_broadcastable, channel_ready, announcement_sigs, tx_signatures + funding_broadcastable, channel_ready, announcement_sigs, tx_signatures: None }; } @@ -8357,7 +8511,7 @@ where match order { RAACommitmentOrder::CommitmentFirst => "commitment", RAACommitmentOrder::RevokeAndACKFirst => "RAA"}); MonitorRestoreUpdates { raa, commitment_update, order, accepted_htlcs, failed_htlcs, finalized_claimed_htlcs, - pending_update_adds, funding_broadcastable, channel_ready, announcement_sigs, tx_signatures + pending_update_adds, funding_broadcastable, channel_ready, announcement_sigs, tx_signatures: None } } @@ -8422,14 +8576,17 @@ where /// blocked. #[rustfmt::skip] pub fn signer_maybe_unblocked(&mut self, logger: &L) -> SignerResumeUpdates where L::Target: Logger { - if !self.holder_commitment_point.is_available() { + if !self.holder_commitment_point.can_advance() { log_trace!(logger, "Attempting to update holder per-commitment point..."); self.holder_commitment_point.try_resolve_pending(&self.context.holder_signer, &self.context.secp_ctx, logger); } let funding_signed = if self.context.signer_pending_funding && !self.funding.is_outbound() { let commitment_data = self.context.build_commitment_transaction(&self.funding, - self.context.cur_counterparty_commitment_transaction_number + 1, - &self.context.counterparty_cur_commitment_point.unwrap(), false, false, logger); + // The previous transaction number (i.e., when adding 1) is used because this field + // is advanced when handling funding_created, but the point is not advanced until + // handling channel_ready. + self.context.counterparty_next_commitment_transaction_number + 1, + &self.context.counterparty_next_commitment_point.unwrap(), false, false, logger); let counterparty_initial_commitment_tx = commitment_data.tx; self.context.get_funding_signed_msg(&self.funding.channel_transaction_parameters, logger, counterparty_initial_commitment_tx) } else { None }; @@ -8519,38 +8676,39 @@ where #[rustfmt::skip] fn get_last_revoke_and_ack(&mut self, logger: &L) -> Option where L::Target: Logger { - debug_assert!(self.holder_commitment_point.transaction_number() <= INITIAL_COMMITMENT_NUMBER - 2); + debug_assert!(self.holder_commitment_point.next_transaction_number() <= INITIAL_COMMITMENT_NUMBER - 2); self.holder_commitment_point.try_resolve_pending(&self.context.holder_signer, &self.context.secp_ctx, logger); let per_commitment_secret = self.context.holder_signer.as_ref() - .release_commitment_secret(self.holder_commitment_point.transaction_number() + 2).ok(); - if let (HolderCommitmentPoint::Available { current, .. }, Some(per_commitment_secret)) = - (self.holder_commitment_point, per_commitment_secret) { - self.context.signer_pending_revoke_and_ack = false; - return Some(msgs::RevokeAndACK { - channel_id: self.context.channel_id, - per_commitment_secret, - next_per_commitment_point: current, - #[cfg(taproot)] - next_local_nonce: None, - }) + .release_commitment_secret(self.holder_commitment_point.next_transaction_number() + 2).ok(); + if let Some(per_commitment_secret) = per_commitment_secret { + if self.holder_commitment_point.can_advance() { + self.context.signer_pending_revoke_and_ack = false; + return Some(msgs::RevokeAndACK { + channel_id: self.context.channel_id, + per_commitment_secret, + next_per_commitment_point: self.holder_commitment_point.next_point(), + #[cfg(taproot)] + next_local_nonce: None, + }) + } } - if !self.holder_commitment_point.is_available() { + if !self.holder_commitment_point.can_advance() { log_trace!(logger, "Last revoke-and-ack pending in channel {} for sequence {} because the next per-commitment point is not available", - &self.context.channel_id(), self.holder_commitment_point.transaction_number()); + &self.context.channel_id(), self.holder_commitment_point.next_transaction_number()); } if per_commitment_secret.is_none() { log_trace!(logger, "Last revoke-and-ack pending in channel {} for sequence {} because the next per-commitment secret for {} is not available", - &self.context.channel_id(), self.holder_commitment_point.transaction_number(), - self.holder_commitment_point.transaction_number() + 2); + &self.context.channel_id(), self.holder_commitment_point.next_transaction_number(), + self.holder_commitment_point.next_transaction_number() + 2); } - // Technically if we're at HolderCommitmentPoint::PendingNext, + // Technically if HolderCommitmentPoint::can_advance is false, // we have a commitment point ready to send in an RAA, however we // choose to wait since if we send RAA now, we could get another // CS before we have any commitment point available. Blocking our // RAA here is a convenient way to make sure that post-funding // we're only ever waiting on one commitment point at a time. log_trace!(logger, "Last revoke-and-ack pending in channel {} for sequence {} because the next per-commitment point is not available", - &self.context.channel_id(), self.holder_commitment_point.transaction_number()); + &self.context.channel_id(), self.holder_commitment_point.next_transaction_number()); self.context.signer_pending_revoke_and_ack = true; None } @@ -8698,7 +8856,7 @@ where return Err(ChannelError::close("Peer sent an invalid channel_reestablish to force close in a non-standard way".to_owned())); } - let our_commitment_transaction = INITIAL_COMMITMENT_NUMBER - self.holder_commitment_point.transaction_number() - 1; + let our_commitment_transaction = INITIAL_COMMITMENT_NUMBER - self.holder_commitment_point.next_transaction_number() - 1; if msg.next_remote_commitment_number > 0 { let expected_point = self.context.holder_signer.as_ref() .get_per_commitment_point(INITIAL_COMMITMENT_NUMBER - msg.next_remote_commitment_number + 1, &self.context.secp_ctx) @@ -8793,14 +8951,14 @@ where ))); }; - // We increment cur_counterparty_commitment_transaction_number only upon receipt of + // We increment counterparty_next_commitment_transaction_number only upon receipt of // revoke_and_ack, not on sending commitment_signed, so we add one if have // AwaitingRemoteRevoke set, which indicates we sent a commitment_signed but haven't gotten // the corresponding revoke_and_ack back yet. let is_awaiting_remote_revoke = self.context.channel_state.is_awaiting_remote_revoke(); - let next_counterparty_commitment_number = INITIAL_COMMITMENT_NUMBER - self.context.cur_counterparty_commitment_transaction_number + if is_awaiting_remote_revoke { 1 } else { 0 }; + let next_counterparty_commitment_number = INITIAL_COMMITMENT_NUMBER - self.context.counterparty_next_commitment_transaction_number + if is_awaiting_remote_revoke { 1 } else { 0 }; - let channel_ready = if msg.next_local_commitment_number == 1 && INITIAL_COMMITMENT_NUMBER - self.holder_commitment_point.transaction_number() == 1 { + let channel_ready = if msg.next_local_commitment_number == 1 && INITIAL_COMMITMENT_NUMBER - self.holder_commitment_point.next_transaction_number() == 1 { // We should never have to worry about MonitorUpdateInProgress resending ChannelReady self.get_channel_ready(logger) } else { None }; @@ -8824,7 +8982,16 @@ where // if it has not received tx_signatures for that funding transaction AND // if next_commitment_number is zero: // MUST retransmit its commitment_signed for that funding transaction. - let commitment_signed = self.context.get_initial_commitment_signed(&self.funding, logger)?; + let commitment_signed = self.context.get_initial_commitment_signed_v2(&self.funding, logger) + // TODO(splicing): Support async signing + .ok_or_else(|| { + let message = "Failed to get signatures for new commitment_signed".to_owned(); + ChannelError::Close( + ( + message.clone(), + ClosureReason::HolderForceClosed { message, broadcasted_latest_txn: Some(false) }, + ) + )})?; Some(msgs::CommitmentUpdate { commitment_signed: vec![commitment_signed], update_add_htlcs: vec![], @@ -8834,7 +9001,6 @@ where update_fee: None, }) } else { None }; - // TODO(dual_funding): For async signing support we need to hold back `tx_signatures` until the `commitment_signed` is ready. let tx_signatures = if ( // if it has not received tx_signatures for that funding transaction AND // if it has already received commitment_signed AND it should sign first, as specified in the tx_signatures requirements: @@ -8843,19 +9009,13 @@ where // else if it has already received tx_signatures for that funding transaction: // MUST send its tx_signatures for that funding transaction. ) || self.context.channel_state.is_their_tx_signatures_sent() { - if self.context.channel_state.is_monitor_update_in_progress() { - // The `monitor_pending_tx_signatures` field should have already been set in `commitment_signed_initial_v2` - // if we were up first for signing and had a monitor update in progress, but check again just in case. - debug_assert!(self.context.monitor_pending_tx_signatures.is_some(), "monitor_pending_tx_signatures should already be set"); - log_debug!(logger, "Not sending tx_signatures: a monitor update is in progress. Setting monitor_pending_tx_signatures."); - if self.context.monitor_pending_tx_signatures.is_none() { - self.context.monitor_pending_tx_signatures = session.holder_tx_signatures().clone(); - } + // If `holder_tx_signatures` is `None` here, the `tx_signatures` message will be sent + // when the holder provides their witnesses as this will queue a `tx_signatures` if the + // holder must send one. + if session.holder_tx_signatures().is_none() { + log_debug!(logger, "Waiting for funding transaction signatures to be provided"); None } else { - // If `holder_tx_signatures` is `None` here, the `tx_signatures` message will be sent - // when the holder provides their witnesses as this will queue a `tx_signatures` if the - // holder must send one. session.holder_tx_signatures().clone() } } else { @@ -9603,7 +9763,7 @@ where /// this function determines whether to fail the HTLC, or forward / claim it. #[rustfmt::skip] pub fn can_accept_incoming_htlc( - &self, msg: &msgs::UpdateAddHTLC, fee_estimator: &LowerBoundedFeeEstimator, logger: L + &self, fee_estimator: &LowerBoundedFeeEstimator, logger: L ) -> Result<(), LocalHTLCFailureReason> where F::Target: FeeEstimator, @@ -9619,20 +9779,22 @@ where core::iter::once(&self.funding) .chain(self.pending_funding.iter()) - .try_for_each(|funding| self.context.can_accept_incoming_htlc(funding, msg, dust_exposure_limiting_feerate, &logger)) + .try_for_each(|funding| self.context.can_accept_incoming_htlc(funding, dust_exposure_limiting_feerate, &logger)) } pub fn get_cur_holder_commitment_transaction_number(&self) -> u64 { - self.holder_commitment_point.transaction_number() + 1 + self.holder_commitment_point.current_transaction_number() } pub fn get_cur_counterparty_commitment_transaction_number(&self) -> u64 { - self.context.cur_counterparty_commitment_transaction_number + 1 + self.context.counterparty_next_commitment_transaction_number + 1 - if self.context.channel_state.is_awaiting_remote_revoke() { 1 } else { 0 } } pub fn get_revoked_counterparty_commitment_transaction_number(&self) -> u64 { - self.context.cur_counterparty_commitment_transaction_number + 2 + let ret = self.context.counterparty_next_commitment_transaction_number + 2; + debug_assert_eq!(self.context.commitment_secrets.get_min_seen_secret(), ret); + ret } #[cfg(any(test, feature = "_externalize_tests"))] @@ -9747,8 +9909,8 @@ where debug_assert!(self.context.minimum_depth.unwrap_or(1) > 0); return true; } - if self.holder_commitment_point.transaction_number() == INITIAL_COMMITMENT_NUMBER - 1 && - self.context.cur_counterparty_commitment_transaction_number == INITIAL_COMMITMENT_NUMBER - 1 { + if self.holder_commitment_point.next_transaction_number() == INITIAL_COMMITMENT_NUMBER - 1 && + self.context.counterparty_next_commitment_transaction_number == INITIAL_COMMITMENT_NUMBER - 1 { // If we're a 0-conf channel, we'll move beyond AwaitingChannelReady immediately even while // waiting for the initial monitor persistence. Thus, we check if our commitment // transaction numbers have both been iterated only exactly once (for the @@ -9881,11 +10043,11 @@ where fn get_channel_ready( &mut self, logger: &L ) -> Option where L::Target: Logger { - if let HolderCommitmentPoint::Available { current, .. } = self.holder_commitment_point { + if self.holder_commitment_point.can_advance() { self.context.signer_pending_channel_ready = false; Some(msgs::ChannelReady { channel_id: self.context.channel_id(), - next_per_commitment_point: current, + next_per_commitment_point: self.holder_commitment_point.next_point(), short_channel_id_alias: Some(self.context.outbound_scid_alias), }) } else { @@ -10013,12 +10175,6 @@ where } if let Some(channel_ready) = self.check_get_channel_ready(height, logger) { - for &(idx, tx) in txdata.iter() { - if idx > index_in_block { - self.context.check_for_funding_tx_spent(&self.funding, tx, logger)?; - } - } - log_info!(logger, "Sending a channel_ready to our peer for channel {}", &self.context.channel_id); let announcement_sigs = self.get_announcement_sigs(node_signer, chain_hash, user_config, height, logger); return Ok((Some(FundingConfirmedMessage::Establishment(channel_ready)), announcement_sigs)); @@ -10059,12 +10215,6 @@ where let funding = self.pending_funding.get(confirmed_funding_index).unwrap(); if let Some(splice_locked) = pending_splice.check_get_splice_locked(&self.context, funding, height) { - for &(idx, tx) in txdata.iter() { - if idx > index_in_block { - self.context.check_for_funding_tx_spent(funding, tx, logger)?; - } - } - log_info!( logger, "Sending splice_locked txid {} to our peer for channel {}", @@ -10084,13 +10234,6 @@ where return Ok((Some(FundingConfirmedMessage::Splice(splice_locked, funding_txo, monitor_update)), announcement_sigs)); } } - - self.context.check_for_funding_tx_spent(&self.funding, tx, logger)?; - #[cfg(splicing)] - for funding in self.pending_funding.iter() { - self.context.check_for_funding_tx_spent(funding, tx, logger)?; - } - } Ok((None, None)) @@ -10554,7 +10697,7 @@ where #[rustfmt::skip] fn get_channel_reestablish(&mut self, logger: &L) -> msgs::ChannelReestablish where L::Target: Logger { assert!(self.context.channel_state.is_peer_disconnected()); - assert_ne!(self.context.cur_counterparty_commitment_transaction_number, INITIAL_COMMITMENT_NUMBER); + assert_ne!(self.context.counterparty_next_commitment_transaction_number, INITIAL_COMMITMENT_NUMBER); // This is generally the first function which gets called on any given channel once we're // up and running normally. Thus, we take this opportunity to attempt to resolve the // `holder_commitment_point` to get any keys which we are currently missing. @@ -10569,8 +10712,8 @@ where // valid, and valid in fuzzing mode's arbitrary validity criteria: let mut pk = [2; 33]; pk[1] = 0xff; let dummy_pubkey = PublicKey::from_slice(&pk).unwrap(); - let remote_last_secret = if self.context.cur_counterparty_commitment_transaction_number + 1 < INITIAL_COMMITMENT_NUMBER { - let remote_last_secret = self.context.commitment_secrets.get_secret(self.context.cur_counterparty_commitment_transaction_number + 2).unwrap(); + let remote_last_secret = if self.context.counterparty_next_commitment_transaction_number + 1 < INITIAL_COMMITMENT_NUMBER { + let remote_last_secret = self.context.commitment_secrets.get_secret(self.context.counterparty_next_commitment_transaction_number + 2).unwrap(); log_trace!(logger, "Enough info to generate a Data Loss Protect with per_commitment_secret {} for channel {}", log_bytes!(remote_last_secret), &self.context.channel_id()); remote_last_secret } else { @@ -10588,15 +10731,15 @@ where // next_local_commitment_number is the next commitment_signed number we expect to // receive (indicating if they need to resend one that we missed). - next_local_commitment_number: INITIAL_COMMITMENT_NUMBER - self.holder_commitment_point.transaction_number(), + next_local_commitment_number: INITIAL_COMMITMENT_NUMBER - self.holder_commitment_point.next_transaction_number(), // We have to set next_remote_commitment_number to the next revoke_and_ack we expect to // receive, however we track it by the next commitment number for a remote transaction // (which is one further, as they always revoke previous commitment transaction, not // the one we send) so we have to decrement by 1. Note that if - // cur_counterparty_commitment_transaction_number is INITIAL_COMMITMENT_NUMBER we will have + // counterparty_next_commitment_transaction_number is INITIAL_COMMITMENT_NUMBER we will have // dropped this channel on disconnect as it hasn't yet reached AwaitingChannelReady so we can't // overflow here. - next_remote_commitment_number: INITIAL_COMMITMENT_NUMBER - self.context.cur_counterparty_commitment_transaction_number - 1, + next_remote_commitment_number: INITIAL_COMMITMENT_NUMBER - self.context.counterparty_next_commitment_transaction_number - 1, your_last_per_commitment_secret: remote_last_secret, my_current_per_commitment_point: dummy_pubkey, next_funding_txid: self.maybe_get_next_funding_txid(), @@ -10610,10 +10753,17 @@ where /// generated by `SignerProvider::get_destination_script`. #[cfg(splicing)] pub fn splice_channel( - &mut self, our_funding_contribution_satoshis: i64, - our_funding_inputs: Vec<(TxIn, Transaction, Weight)>, change_script: Option, - funding_feerate_per_kw: u32, locktime: u32, + &mut self, contribution: SpliceContribution, funding_feerate_per_kw: u32, locktime: u32, ) -> Result { + if self.holder_commitment_point.current_point().is_none() { + return Err(APIError::APIMisuseError { + err: format!( + "Channel {} cannot be spliced until a payment is routed", + self.context.channel_id(), + ), + }); + } + // Check if a splice has been initiated already. // Note: only a single outstanding splice is supported (per spec) if self.pending_splice.is_some() { @@ -10636,53 +10786,110 @@ where // TODO(splicing): check for quiescence - if our_funding_contribution_satoshis < 0 { + let our_funding_contribution = contribution.value(); + if our_funding_contribution == SignedAmount::ZERO { return Err(APIError::APIMisuseError { err: format!( - "TODO(splicing): Splice-out not supported, only splice in; channel ID {}, contribution {}", - self.context.channel_id(), our_funding_contribution_satoshis, - ), + "Channel {} cannot be spliced; contribution cannot be zero", + self.context.channel_id(), + ), + }); + } + + if our_funding_contribution > SignedAmount::MAX_MONEY { + return Err(APIError::APIMisuseError { + err: format!( + "Channel {} cannot be spliced in; contribution exceeds total bitcoin supply: {}", + self.context.channel_id(), + our_funding_contribution, + ), }); } - // TODO(splicing): Once splice-out is supported, check that channel balance does not go below 0 - // (or below channel reserve) + if our_funding_contribution < -SignedAmount::MAX_MONEY { + return Err(APIError::APIMisuseError { + err: format!( + "Channel {} cannot be spliced out; contribution exhausts total bitcoin supply: {}", + self.context.channel_id(), + our_funding_contribution, + ), + }); + } // Note: post-splice channel value is not yet known at this point, counterparty contribution is not known // (Cannot test for miminum required post-splice channel value) - // Check that inputs are sufficient to cover our contribution. - let _fee = check_v2_funding_inputs_sufficient( - our_funding_contribution_satoshis, - &our_funding_inputs, - true, - true, - funding_feerate_per_kw, + let channel_balance = Amount::from_sat(self.funding.get_value_to_self_msat() / 1000); + let fees = check_splice_contribution_sufficient( + channel_balance, + &contribution, + true, // is_initiator + FeeRate::from_sat_per_kwu(funding_feerate_per_kw as u64), ) - .map_err(|err| APIError::APIMisuseError { - err: format!( - "Insufficient inputs for splicing; channel ID {}, err {}", - self.context.channel_id(), - err, - ), + .map_err(|e| { + let splice_type = if our_funding_contribution < SignedAmount::ZERO { + "spliced out" + } else { + "spliced in" + }; + APIError::APIMisuseError { + err: format!( + "Channel {} cannot be {}; {}", + self.context.channel_id(), + splice_type, + e, + ), + } })?; - // Convert inputs - let mut funding_inputs = Vec::new(); - for (tx_in, tx, _w) in our_funding_inputs.into_iter() { - let tx16 = TransactionU16LenLimited::new(tx) - .map_err(|_e| APIError::APIMisuseError { err: format!("Too large transaction") })?; - funding_inputs.push((tx_in, tx16)); + + // Fees for splice-out are paid from the channel balance whereas fees for splice-in are paid + // by the funding inputs. + let adjusted_funding_contribution = if our_funding_contribution < SignedAmount::ZERO { + let adjusted_funding_contribution = our_funding_contribution + - fees.to_signed().expect("fees should never exceed Amount::MAX_MONEY"); + + // TODO(splicing): Check that channel balance does not go below the channel reserve + let _post_channel_balance = AddSigned::checked_add_signed( + channel_balance.to_sat(), + adjusted_funding_contribution.to_sat(), + ); + + adjusted_funding_contribution + } else { + our_funding_contribution + }; + + for FundingTxInput { utxo, prevtx, .. } in contribution.inputs().iter() { + const MESSAGE_TEMPLATE: msgs::TxAddInput = msgs::TxAddInput { + channel_id: ChannelId([0; 32]), + serial_id: 0, + prevtx: None, + prevtx_out: 0, + sequence: 0, + // Mutually exclusive with prevtx, which is accounted for below. + shared_input_txid: None, + }; + let message_len = MESSAGE_TEMPLATE.serialized_length() + prevtx.serialized_length(); + if message_len > LN_MAX_MSG_LEN { + return Err(APIError::APIMisuseError { + err: format!( + "Funding input references a prevtx that is too large for tx_add_input: {}", + utxo.outpoint, + ), + }); + } } let prev_funding_input = self.funding.to_splice_funding_input(); + let (our_funding_inputs, our_funding_outputs, change_script) = contribution.into_tx_parts(); let funding_negotiation_context = FundingNegotiationContext { is_initiator: true, - our_funding_contribution_satoshis, - their_funding_contribution_satoshis: None, + our_funding_contribution: adjusted_funding_contribution, funding_tx_locktime: LockTime::from_consensus(locktime), funding_feerate_sat_per_1000_weight: funding_feerate_per_kw, shared_funding_input: Some(prev_funding_input), - our_funding_inputs: funding_inputs, + our_funding_inputs, + our_funding_outputs, change_script, }; @@ -10698,7 +10905,7 @@ where Ok(msgs::SpliceInit { channel_id: self.context.channel_id, - funding_contribution_satoshis: our_funding_contribution_satoshis, + funding_contribution_satoshis: adjusted_funding_contribution.to_sat(), funding_feerate_per_kw, locktime, funding_pubkey, @@ -10709,12 +10916,17 @@ where /// Checks during handling splice_init #[cfg(splicing)] pub fn validate_splice_init( - &self, msg: &msgs::SpliceInit, our_funding_contribution_satoshis: i64, + &self, msg: &msgs::SpliceInit, our_funding_contribution: SignedAmount, ) -> Result { - let their_funding_contribution_satoshis = msg.funding_contribution_satoshis; - // TODO(splicing): Add check that we are the quiescence acceptor + if self.holder_commitment_point.current_point().is_none() { + return Err(ChannelError::WarnAndDisconnect(format!( + "Channel {} commitment point needs to be advanced once before spliced", + self.context.channel_id(), + ))); + } + // Check if a splice has been initiated already. if self.pending_splice.is_some() { return Err(ChannelError::WarnAndDisconnect(format!( @@ -10732,21 +10944,36 @@ where ))); } - if their_funding_contribution_satoshis.saturating_add(our_funding_contribution_satoshis) < 0 - { + debug_assert_eq!(our_funding_contribution, SignedAmount::ZERO); + + // TODO(splicing): Move this check once user-provided contributions are supported for + // counterparty-initiated splices. + if our_funding_contribution > SignedAmount::MAX_MONEY { + return Err(ChannelError::WarnAndDisconnect(format!( + "Channel {} cannot be spliced in; our {} contribution exceeds the total bitcoin supply", + self.context.channel_id(), + our_funding_contribution, + ))); + } + + if our_funding_contribution < -SignedAmount::MAX_MONEY { return Err(ChannelError::WarnAndDisconnect(format!( - "Splice-out not supported, only splice in, contribution is {} ({} + {})", - their_funding_contribution_satoshis + our_funding_contribution_satoshis, - their_funding_contribution_satoshis, - our_funding_contribution_satoshis, + "Channel {} cannot be spliced out; our {} contribution exhausts the total bitcoin supply", + self.context.channel_id(), + our_funding_contribution, ))); } + let their_funding_contribution = SignedAmount::from_sat(msg.funding_contribution_satoshis); + self.validate_splice_contribution(their_funding_contribution)?; + + // TODO(splicing): Check that channel balance does not go below the channel reserve + let splice_funding = FundingScope::for_splice( &self.funding, &self.context, - our_funding_contribution_satoshis, - their_funding_contribution_satoshis, + our_funding_contribution, + their_funding_contribution, msg.funding_pubkey, )?; @@ -10761,6 +10988,45 @@ where Ok(splice_funding) } + #[cfg(splicing)] + fn validate_splice_contribution( + &self, their_funding_contribution: SignedAmount, + ) -> Result<(), ChannelError> { + if their_funding_contribution > SignedAmount::MAX_MONEY { + return Err(ChannelError::WarnAndDisconnect(format!( + "Channel {} cannot be spliced in; their {} contribution exceeds the total bitcoin supply", + self.context.channel_id(), + their_funding_contribution, + ))); + } + + if their_funding_contribution < -SignedAmount::MAX_MONEY { + return Err(ChannelError::WarnAndDisconnect(format!( + "Channel {} cannot be spliced out; their {} contribution exhausts the total bitcoin supply", + self.context.channel_id(), + their_funding_contribution, + ))); + } + + let their_channel_balance = Amount::from_sat(self.funding.get_value_satoshis()) + - Amount::from_sat(self.funding.get_value_to_self_msat() / 1000); + let post_channel_balance = AddSigned::checked_add_signed( + their_channel_balance.to_sat(), + their_funding_contribution.to_sat(), + ); + + if post_channel_balance.is_none() { + return Err(ChannelError::WarnAndDisconnect(format!( + "Channel {} cannot be spliced out; their {} contribution exhausts their channel balance: {}", + self.context.channel_id(), + their_funding_contribution, + their_channel_balance, + ))); + } + + Ok(()) + } + /// See also [`validate_splice_init`] #[cfg(splicing)] pub(crate) fn splice_init( @@ -10771,7 +11037,8 @@ where ES::Target: EntropySource, L::Target: Logger, { - let splice_funding = self.validate_splice_init(msg, our_funding_contribution_satoshis)?; + let our_funding_contribution = SignedAmount::from_sat(our_funding_contribution_satoshis); + let splice_funding = self.validate_splice_init(msg, our_funding_contribution)?; log_info!( logger, @@ -10781,16 +11048,15 @@ where self.funding.get_value_satoshis(), ); - let their_funding_contribution_satoshis = msg.funding_contribution_satoshis; let prev_funding_input = self.funding.to_splice_funding_input(); let funding_negotiation_context = FundingNegotiationContext { is_initiator: false, - our_funding_contribution_satoshis, - their_funding_contribution_satoshis: Some(their_funding_contribution_satoshis), + our_funding_contribution, funding_tx_locktime: LockTime::from_consensus(msg.locktime), funding_feerate_sat_per_1000_weight: msg.funding_feerate_per_kw, shared_funding_input: Some(prev_funding_input), our_funding_inputs: Vec::new(), + our_funding_outputs: Vec::new(), change_script: None, }; @@ -10823,7 +11089,7 @@ where Ok(msgs::SpliceAck { channel_id: self.context.channel_id, - funding_contribution_satoshis: our_funding_contribution_satoshis, + funding_contribution_satoshis: our_funding_contribution.to_sat(), funding_pubkey, require_confirmed_inputs: None, }) @@ -10870,15 +11136,17 @@ where }, }; - let our_funding_contribution_satoshis = - funding_negotiation_context.our_funding_contribution_satoshis; - let their_funding_contribution_satoshis = msg.funding_contribution_satoshis; + let our_funding_contribution = funding_negotiation_context.our_funding_contribution; + debug_assert!(our_funding_contribution.abs() <= SignedAmount::MAX_MONEY); + + let their_funding_contribution = SignedAmount::from_sat(msg.funding_contribution_satoshis); + self.validate_splice_contribution(their_funding_contribution)?; let splice_funding = FundingScope::for_splice( &self.funding, &self.context, - our_funding_contribution_satoshis, - their_funding_contribution_satoshis, + our_funding_contribution, + their_funding_contribution, msg.funding_pubkey, )?; @@ -10910,6 +11178,9 @@ where let tx_msg_opt = interactive_tx_constructor.take_initiator_first_message(); debug_assert!(self.interactive_tx_signing_session.is_none()); + + let pending_splice = + self.pending_splice.as_mut().expect("pending_splice should still be set"); pending_splice.funding_negotiation = Some(FundingNegotiation::ConstructingTransaction( splice_funding, interactive_tx_constructor, @@ -11199,8 +11470,8 @@ where ChannelMonitorUpdateStep::LatestCounterpartyCommitmentTXInfo { commitment_txid: counterparty_commitment_tx.trust().txid(), htlc_outputs, - commitment_number: self.context.cur_counterparty_commitment_transaction_number, - their_per_commitment_point: self.context.counterparty_cur_commitment_point.unwrap(), + commitment_number: self.context.counterparty_next_commitment_transaction_number, + their_per_commitment_point: self.context.counterparty_next_commitment_point.unwrap(), feerate_per_kw: Some(counterparty_commitment_tx.feerate_per_kw()), to_broadcaster_value_sat: Some(counterparty_commitment_tx.to_broadcaster_value_sat()), to_countersignatory_value_sat: Some(counterparty_commitment_tx.to_countersignatory_value_sat()), @@ -11256,25 +11527,16 @@ where L::Target: Logger, { let commitment_data = self.context.build_commitment_transaction( - funding, self.context.cur_counterparty_commitment_transaction_number, - &self.context.counterparty_cur_commitment_point.unwrap(), false, true, logger, + funding, self.context.counterparty_next_commitment_transaction_number, + &self.context.counterparty_next_commitment_point.unwrap(), false, true, logger, ); let counterparty_commitment_tx = commitment_data.tx; #[cfg(any(test, fuzzing))] { - if !funding.is_outbound() { - let projected_commit_tx_info = funding.next_remote_commitment_tx_fee_info_cached.lock().unwrap().take(); - *funding.next_local_commitment_tx_fee_info_cached.lock().unwrap() = None; - if let Some(info) = projected_commit_tx_info { - let total_pending_htlcs = self.context.pending_inbound_htlcs.len() + self.context.pending_outbound_htlcs.len(); - if info.total_pending_htlcs == total_pending_htlcs - && info.next_holder_htlc_id == self.context.next_holder_htlc_id - && info.next_counterparty_htlc_id == self.context.next_counterparty_htlc_id - && info.feerate == self.context.feerate_per_kw { - assert_eq!(commitment_data.stats.commit_tx_fee_sat, info.fee); - } - } + let PredictedNextFee { predicted_feerate, predicted_nondust_htlc_count, predicted_fee_sat } = *funding.next_remote_fee.lock().unwrap(); + if predicted_feerate == counterparty_commitment_tx.feerate_per_kw() && predicted_nondust_htlc_count == counterparty_commitment_tx.nondust_htlcs().len() { + assert_eq!(predicted_fee_sat, commitment_data.stats.commit_tx_fee_sat); } } @@ -11307,8 +11569,8 @@ where self.build_commitment_no_state_update(funding, logger); let commitment_data = self.context.build_commitment_transaction( - funding, self.context.cur_counterparty_commitment_transaction_number, - &self.context.counterparty_cur_commitment_point.unwrap(), false, true, logger, + funding, self.context.counterparty_next_commitment_transaction_number, + &self.context.counterparty_next_commitment_point.unwrap(), false, true, logger, ); let counterparty_commitment_tx = commitment_data.tx; @@ -11565,30 +11827,36 @@ where #[cfg(any(test, fuzzing))] #[rustfmt::skip] pub fn propose_quiescence( - &mut self, logger: &L, + &mut self, logger: &L, action: QuiescentAction, ) -> Result, ChannelError> where L::Target: Logger, { log_debug!(logger, "Attempting to initiate quiescence"); - if !self.context.is_live() { + if !self.context.is_usable() { return Err(ChannelError::Ignore( - "Channel is not in a live state to propose quiescence".to_owned() + "Channel is not in a usable state to propose quiescence".to_owned() )); } - if self.context.channel_state.is_quiescent() { - return Err(ChannelError::Ignore("Channel is already quiescent".to_owned())); + if self.quiescent_action.is_some() { + return Err(ChannelError::Ignore("Channel is already quiescing".to_owned())); } - if self.context.channel_state.is_awaiting_quiescence() + self.quiescent_action = Some(action); + if self.context.channel_state.is_quiescent() + || self.context.channel_state.is_awaiting_quiescence() || self.context.channel_state.is_local_stfu_sent() { return Ok(None); } self.context.channel_state.set_awaiting_quiescence(); - Ok(Some(self.send_stfu(logger)?)) + if self.context.is_live() { + Ok(Some(self.send_stfu(logger)?)) + } else { + Ok(None) + } } // Assumes we are either awaiting quiescence or our counterparty has requested quiescence. @@ -11598,7 +11866,6 @@ where L::Target: Logger, { debug_assert!(!self.context.channel_state.is_local_stfu_sent()); - // Either state being set implies the channel is live. debug_assert!( self.context.channel_state.is_awaiting_quiescence() || self.context.channel_state.is_remote_stfu_sent() @@ -11618,18 +11885,10 @@ where self.context.channel_state.clear_awaiting_quiescence(); self.context.channel_state.clear_remote_stfu_sent(); self.context.channel_state.set_quiescent(); - if let Some(initiator) = self.context.is_holder_quiescence_initiator.as_ref() { - log_debug!( - logger, - "Responding to counterparty stfu with our own, channel is now quiescent and we are{} the initiator", - if !initiator { " not" } else { "" } - ); - - *initiator - } else { - debug_assert!(false, "Quiescence initiator must have been set when we received stfu"); - false - } + // We are sending an stfu in response to our couterparty's stfu, but had not yet sent + // our own stfu (even if `awaiting_quiescence` was set). Thus, the counterparty is the + // initiator and they can do "something fundamental". + false } else { log_debug!(logger, "Sending stfu as quiescence initiator"); debug_assert!(self.context.channel_state.is_awaiting_quiescence()); @@ -11660,9 +11919,7 @@ where )); } - if self.context.channel_state.is_awaiting_quiescence() - || !self.context.channel_state.is_local_stfu_sent() - { + if !self.context.channel_state.is_local_stfu_sent() { if !msg.initiator { return Err(ChannelError::WarnAndDisconnect( "Peer sent unexpected `stfu` without signaling as initiator".to_owned() @@ -11676,15 +11933,6 @@ where // then. self.context.channel_state.set_remote_stfu_sent(); - let is_holder_initiator = if self.context.channel_state.is_awaiting_quiescence() { - // We were also planning to propose quiescence, let the tie-breaker decide the - // initiator. - self.funding.is_outbound() - } else { - false - }; - self.context.is_holder_quiescence_initiator = Some(is_holder_initiator); - log_debug!(logger, "Received counterparty stfu proposing quiescence"); return self.send_stfu(logger).map(|stfu| Some(stfu)); } @@ -11692,7 +11940,6 @@ where // We already sent `stfu` and are now processing theirs. It may be in response to ours, or // we happened to both send `stfu` at the same time and a tie-break is needed. let is_holder_quiescence_initiator = !msg.initiator || self.funding.is_outbound(); - self.context.is_holder_quiescence_initiator = Some(is_holder_quiescence_initiator); // We were expecting to receive `stfu` because we already sent ours. self.mark_response_received(); @@ -11720,6 +11967,21 @@ where if !is_holder_quiescence_initiator { " not" } else { "" } ); + if is_holder_quiescence_initiator { + match self.quiescent_action.take() { + None => { + debug_assert!(false); + return Err(ChannelError::WarnAndDisconnect( + "Internal Error: Didn't have anything to do after reaching quiescence".to_owned() + )); + }, + Some(QuiescentAction::DoNothing) => { + // In quiescence test we want to just hang out here, letting the test manually + // leave quiescence. + }, + } + } + Ok(None) } @@ -11735,6 +11997,10 @@ where && self.context.channel_state.is_remote_stfu_sent()) ); + if !self.context.is_live() { + return Ok(None); + } + // We need to send our `stfu`, either because we're trying to initiate quiescence, or the // counterparty is and we've yet to send ours. if self.context.channel_state.is_awaiting_quiescence() @@ -11760,16 +12026,13 @@ where debug_assert!(!self.context.channel_state.is_local_stfu_sent()); debug_assert!(!self.context.channel_state.is_remote_stfu_sent()); - if self.context.channel_state.is_quiescent() { - self.mark_response_received(); - self.context.channel_state.clear_quiescent(); - self.context.is_holder_quiescence_initiator.take().expect("Must always be set while quiescent") - } else { - false - } + self.mark_response_received(); + let was_quiescent = self.context.channel_state.is_quiescent(); + self.context.channel_state.clear_quiescent(); + was_quiescent } - pub fn remove_legacy_scids_before_block(&mut self, height: u32) -> alloc::vec::Drain { + pub fn remove_legacy_scids_before_block(&mut self, height: u32) -> alloc::vec::Drain<'_, u64> { let end = self .funding .get_short_channel_id() @@ -11875,8 +12138,8 @@ where #[rustfmt::skip] fn get_funding_created_msg(&mut self, logger: &L) -> Option where L::Target: Logger { let commitment_data = self.context.build_commitment_transaction(&self.funding, - self.context.cur_counterparty_commitment_transaction_number, - &self.context.counterparty_cur_commitment_point.unwrap(), false, false, logger); + self.context.counterparty_next_commitment_transaction_number, + &self.context.counterparty_next_commitment_point.unwrap(), false, false, logger); let counterparty_initial_commitment_tx = commitment_data.tx; let signature = match &self.context.holder_signer { // TODO (taproot|arik): move match into calling method for Taproot @@ -11994,9 +12257,9 @@ where } let first_per_commitment_point = match self.unfunded_context.holder_commitment_point { - Some(holder_commitment_point) if holder_commitment_point.is_available() => { + Some(holder_commitment_point) if holder_commitment_point.can_advance() => { self.signer_pending_open_channel = false; - holder_commitment_point.current_point() + holder_commitment_point.next_point() }, _ => { log_trace!(_logger, "Unable to generate open_channel message, waiting for commitment point"); @@ -12068,7 +12331,7 @@ where Some(point) => point, None => return Err((self, ChannelError::close("Received funding_signed before our first commitment point was available".to_owned()))), }; - self.context.assert_no_commitment_advancement(holder_commitment_point.transaction_number(), "funding_signed"); + self.context.assert_no_commitment_advancement(holder_commitment_point.next_transaction_number(), "funding_signed"); let (channel_monitor, _) = match self.initial_commitment_signed( self.context.channel_id(), msg.signature, @@ -12088,6 +12351,7 @@ where holder_commitment_point, #[cfg(splicing)] pending_splice: None, + quiescent_action: None, }; let need_channel_ready = channel.check_get_channel_ready(0, logger).is_some() @@ -12108,7 +12372,7 @@ where self.unfunded_context.holder_commitment_point = HolderCommitmentPoint::new(&self.context.holder_signer, &self.context.secp_ctx); } if let Some(ref mut point) = self.unfunded_context.holder_commitment_point { - if !point.is_available() { + if !point.can_advance() { point.try_resolve_pending(&self.context.holder_signer, &self.context.secp_ctx, logger); } } @@ -12268,9 +12532,9 @@ where &mut self, _logger: &L ) -> Option where L::Target: Logger { let first_per_commitment_point = match self.unfunded_context.holder_commitment_point { - Some(holder_commitment_point) if holder_commitment_point.is_available() => { + Some(holder_commitment_point) if holder_commitment_point.can_advance() => { self.signer_pending_accept_channel = false; - holder_commitment_point.current_point() + holder_commitment_point.next_point() }, _ => { log_trace!(_logger, "Unable to generate accept_channel message, waiting for commitment point"); @@ -12344,7 +12608,7 @@ where Some(point) => point, None => return Err((self, ChannelError::close("Received funding_created before our first commitment point was available".to_owned()))), }; - self.context.assert_no_commitment_advancement(holder_commitment_point.transaction_number(), "funding_created"); + self.context.assert_no_commitment_advancement(holder_commitment_point.next_transaction_number(), "funding_created"); let funding_txo = OutPoint { txid: msg.funding_txid, index: msg.funding_output_index }; self.funding.channel_transaction_parameters.funding_outpoint = Some(funding_txo); @@ -12374,6 +12638,7 @@ where holder_commitment_point, #[cfg(splicing)] pending_splice: None, + quiescent_action: None, }; let need_channel_ready = channel.check_get_channel_ready(0, logger).is_some() || channel.context.signer_pending_channel_ready; @@ -12392,7 +12657,7 @@ where self.unfunded_context.holder_commitment_point = HolderCommitmentPoint::new(&self.context.holder_signer, &self.context.secp_ctx); } if let Some(ref mut point) = self.unfunded_context.holder_commitment_point { - if !point.is_available() { + if !point.can_advance() { point.try_resolve_pending(&self.context.holder_signer, &self.context.secp_ctx, logger); } } @@ -12427,7 +12692,7 @@ where pub fn new_outbound( fee_estimator: &LowerBoundedFeeEstimator, entropy_source: &ES, signer_provider: &SP, counterparty_node_id: PublicKey, their_features: &InitFeatures, funding_satoshis: u64, - funding_inputs: Vec<(TxIn, TransactionU16LenLimited)>, user_id: u128, config: &UserConfig, + funding_inputs: Vec, user_id: u128, config: &UserConfig, current_chain_height: u32, outbound_scid_alias: u64, funding_confirmation_target: ConfirmationTarget, logger: L, ) -> Result @@ -12476,13 +12741,12 @@ where }; let funding_negotiation_context = FundingNegotiationContext { is_initiator: true, - our_funding_contribution_satoshis: funding_satoshis as i64, - // TODO(dual_funding) TODO(splicing) Include counterparty contribution, once that's enabled - their_funding_contribution_satoshis: None, + our_funding_contribution: SignedAmount::from_sat(funding_satoshis as i64), funding_tx_locktime, funding_feerate_sat_per_1000_weight, shared_funding_input: None, our_funding_inputs: funding_inputs, + our_funding_outputs: Vec::new(), change_script: None, }; let chan = Self { @@ -12586,10 +12850,11 @@ where L::Target: Logger, { // TODO(dual_funding): Take these as input once supported - let our_funding_satoshis = 0u64; + let (our_funding_contribution, our_funding_contribution_sats) = (SignedAmount::ZERO, 0u64); let our_funding_inputs = Vec::new(); - let channel_value_satoshis = our_funding_satoshis.saturating_add(msg.common_fields.funding_satoshis); + let channel_value_satoshis = + our_funding_contribution_sats.saturating_add(msg.common_fields.funding_satoshis); let counterparty_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis( channel_value_satoshis, msg.common_fields.dust_limit_satoshis); let holder_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis( @@ -12616,9 +12881,7 @@ where current_chain_height, logger, false, - - our_funding_satoshis, - + our_funding_contribution_sats, counterparty_pubkeys, channel_type, holder_selected_channel_reserve_satoshis, @@ -12633,18 +12896,24 @@ where let funding_negotiation_context = FundingNegotiationContext { is_initiator: false, - our_funding_contribution_satoshis: our_funding_satoshis as i64, - their_funding_contribution_satoshis: Some(msg.common_fields.funding_satoshis as i64), + our_funding_contribution, funding_tx_locktime: LockTime::from_consensus(msg.locktime), funding_feerate_sat_per_1000_weight: msg.funding_feerate_sat_per_1000_weight, shared_funding_input: None, our_funding_inputs: our_funding_inputs.clone(), + our_funding_outputs: Vec::new(), change_script: None, }; let shared_funding_output = TxOut { value: Amount::from_sat(funding.get_value_satoshis()), script_pubkey: funding.get_funding_redeemscript().to_p2wsh(), }; + let inputs_to_contribute = our_funding_inputs + .into_iter() + .map(|FundingTxInput { utxo, sequence, prevtx }| { + (TxIn { previous_output: utxo.outpoint, sequence, ..Default::default() }, prevtx) + }) + .collect(); let interactive_tx_constructor = Some(InteractiveTxConstructor::new( InteractiveTxConstructorArgs { @@ -12655,10 +12924,10 @@ where feerate_sat_per_kw: funding_negotiation_context.funding_feerate_sat_per_1000_weight, funding_tx_locktime: funding_negotiation_context.funding_tx_locktime, is_initiator: false, - inputs_to_contribute: our_funding_inputs, + inputs_to_contribute, shared_funding_input: None, - shared_funding_output: SharedOwnedOutput::new(shared_funding_output, our_funding_satoshis), - outputs_to_contribute: Vec::new(), + shared_funding_output: SharedOwnedOutput::new(shared_funding_output, our_funding_contribution_sats), + outputs_to_contribute: funding_negotiation_context.our_funding_outputs.clone(), } ).map_err(|err| { let reason = ClosureReason::ProcessingError { err: err.to_string() }; @@ -12738,7 +13007,7 @@ where }), channel_type: Some(self.funding.get_channel_type().clone()), }, - funding_satoshis: self.funding_negotiation_context.our_funding_contribution_satoshis + funding_satoshis: self.funding_negotiation_context.our_funding_contribution.to_sat() as u64, second_per_commitment_point, require_confirmed_inputs: None, @@ -12875,11 +13144,16 @@ where match channel_state { ChannelState::AwaitingChannelReady(_) => {}, ChannelState::ChannelReady(_) => { - channel_state.clear_awaiting_quiescence(); + if self.quiescent_action.is_some() { + // If we're trying to get quiescent to do something, try again when we + // reconnect to the peer. + channel_state.set_awaiting_quiescence(); + } channel_state.clear_local_stfu_sent(); channel_state.clear_remote_stfu_sent(); channel_state.clear_quiescent(); }, + ChannelState::FundingNegotiated(flags) if flags.is_interactive_signing() => {}, _ => debug_assert!(false, "Pre-funded/shutdown channels should not be written"), } channel_state.set_peer_disconnected(); @@ -12898,8 +13172,8 @@ where } self.context.destination_script.write(writer)?; - self.holder_commitment_point.transaction_number().write(writer)?; - self.context.cur_counterparty_commitment_transaction_number.write(writer)?; + self.holder_commitment_point.next_transaction_number().write(writer)?; + self.context.counterparty_next_commitment_transaction_number.write(writer)?; self.funding.value_to_self_msat.write(writer)?; let mut dropped_inbound_htlcs = 0; @@ -13156,8 +13430,8 @@ where self.funding.channel_transaction_parameters.write(writer)?; self.funding.funding_transaction.write(writer)?; - self.context.counterparty_cur_commitment_point.write(writer)?; - self.context.counterparty_prev_commitment_point.write(writer)?; + self.context.counterparty_next_commitment_point.write(writer)?; + self.context.counterparty_current_commitment_point.write(writer)?; self.context.counterparty_node_id.write(writer)?; self.context.counterparty_shutdown_scriptpubkey.write(writer)?; @@ -13229,9 +13503,9 @@ where } let is_manual_broadcast = Some(self.context.is_manual_broadcast); - // `current_point` will become optional when async signing is implemented. - let cur_holder_commitment_point = Some(self.holder_commitment_point.current_point()); - let next_holder_commitment_point = self.holder_commitment_point.next_point(); + let holder_commitment_point_current = self.holder_commitment_point.current_point(); + let holder_commitment_point_next = self.holder_commitment_point.next_point(); + let holder_commitment_point_pending_next = self.holder_commitment_point.pending_next_point; write_tlv_fields!(writer, { (0, self.context.announcement_sigs, option), @@ -13269,8 +13543,8 @@ where (39, pending_outbound_blinding_points, optional_vec), (41, holding_cell_blinding_points, optional_vec), (43, malformed_htlcs, optional_vec), // Added in 0.0.119 - (45, cur_holder_commitment_point, option), - (47, next_holder_commitment_point, option), + (45, holder_commitment_point_next, required), + (47, holder_commitment_point_pending_next, option), (49, self.context.local_initiated_shutdown, option), // Added in 0.0.122 (51, is_manual_broadcast, option), // Added in 0.0.124 (53, funding_tx_broadcast_safe_event_emitted, option), // Added in 0.0.124 @@ -13281,6 +13555,8 @@ where (59, self.funding.minimum_depth_override, option), // Added in 0.2 (60, self.context.historical_scids, optional_vec), // Added in 0.2 (61, fulfill_attribution_data, optional_vec), // Added in 0.2 + (63, holder_commitment_point_current, option), // Added in 0.2 + (65, self.quiescent_action, option), // Added in 0.2 }); Ok(()) @@ -13327,8 +13603,8 @@ where }; let destination_script = Readable::read(reader)?; - let cur_holder_commitment_transaction_number = Readable::read(reader)?; - let cur_counterparty_commitment_transaction_number = Readable::read(reader)?; + let holder_commitment_next_transaction_number = Readable::read(reader)?; + let counterparty_next_commitment_transaction_number = Readable::read(reader)?; let value_to_self_msat = Readable::read(reader)?; let pending_inbound_htlc_count: u64 = Readable::read(reader)?; @@ -13555,9 +13831,9 @@ where ReadableArgs::>::read(reader, Some(channel_value_satoshis))?; let funding_transaction: Option = Readable::read(reader)?; - let counterparty_cur_commitment_point = Readable::read(reader)?; + let counterparty_next_commitment_point = Readable::read(reader)?; - let counterparty_prev_commitment_point = Readable::read(reader)?; + let counterparty_current_commitment_point = Readable::read(reader)?; let counterparty_node_id = Readable::read(reader)?; let counterparty_shutdown_scriptpubkey = Readable::read(reader)?; @@ -13630,8 +13906,9 @@ where let mut malformed_htlcs: Option> = None; let mut monitor_pending_update_adds: Option> = None; - let mut cur_holder_commitment_point_opt: Option = None; - let mut next_holder_commitment_point_opt: Option = None; + let mut holder_commitment_point_current_opt: Option = None; + let mut holder_commitment_point_next_opt: Option = None; + let mut holder_commitment_point_pending_next_opt: Option = None; let mut is_manual_broadcast = None; let mut pending_funding = Some(Vec::new()); @@ -13641,6 +13918,8 @@ where let mut minimum_depth_override: Option = None; + let mut quiescent_action = None; + read_tlv_fields!(reader, { (0, announcement_sigs, option), (1, minimum_depth, option), @@ -13671,8 +13950,8 @@ where (39, pending_outbound_blinding_points_opt, optional_vec), (41, holding_cell_blinding_points_opt, optional_vec), (43, malformed_htlcs, optional_vec), // Added in 0.0.119 - (45, cur_holder_commitment_point_opt, option), - (47, next_holder_commitment_point_opt, option), + (45, holder_commitment_point_next_opt, option), + (47, holder_commitment_point_pending_next_opt, option), (49, local_initiated_shutdown, option), (51, is_manual_broadcast, option), (53, funding_tx_broadcast_safe_event_emitted, option), @@ -13683,6 +13962,8 @@ where (59, minimum_depth_override, option), // Added in 0.2 (60, historical_scids, optional_vec), // Added in 0.2 (61, fulfill_attribution_data, optional_vec), // Added in 0.2 + (63, holder_commitment_point_current_opt, option), // Added in 0.2 + (65, quiescent_action, upgradable_option), // Added in 0.2 }); let holder_signer = signer_provider.derive_channel_signer(channel_keys_id); @@ -13858,38 +14139,53 @@ where } // If we're restoring this channel for the first time after an upgrade, then we require that the - // signer be available so that we can immediately populate the current commitment point. Channel + // signer be available so that we can immediately populate the next commitment point. Channel // restoration will fail if this is not possible. - let holder_commitment_point = match ( - cur_holder_commitment_point_opt, - next_holder_commitment_point_opt, - ) { - (Some(current), Some(next)) => HolderCommitmentPoint::Available { - transaction_number: cur_holder_commitment_transaction_number, - current, - next, - }, - (Some(current), _) => HolderCommitmentPoint::PendingNext { - transaction_number: cur_holder_commitment_transaction_number, - current, - }, - (_, _) => { - let current = holder_signer.get_per_commitment_point(cur_holder_commitment_transaction_number, &secp_ctx) - .expect("Must be able to derive the current commitment point upon channel restoration"); - let next = holder_signer - .get_per_commitment_point( - cur_holder_commitment_transaction_number - 1, - &secp_ctx, - ) - .expect( - "Must be able to derive the next commitment point upon channel restoration", - ); - HolderCommitmentPoint::Available { - transaction_number: cur_holder_commitment_transaction_number, - current, - next, + let holder_commitment_point = { + let current_point = holder_commitment_point_current_opt.or_else(|| { + if holder_commitment_next_transaction_number == INITIAL_COMMITMENT_NUMBER { + None + } else { + // If the current point is not available then splicing can't be initiated + // until the next point is advanced and becomes the current point. + holder_signer + .get_per_commitment_point( + holder_commitment_next_transaction_number + 1, + &secp_ctx, + ) + .ok() } - }, + }); + + match (holder_commitment_point_next_opt, holder_commitment_point_pending_next_opt) { + (Some(next_point), pending_next_point) => HolderCommitmentPoint { + next_transaction_number: holder_commitment_next_transaction_number, + current_point, + next_point, + pending_next_point, + }, + (_, _) => { + let next_point = holder_signer + .get_per_commitment_point(holder_commitment_next_transaction_number, &secp_ctx) + .expect( + "Must be able to derive the next commitment point upon channel restoration", + ); + let pending_next_point = holder_signer + .get_per_commitment_point( + holder_commitment_next_transaction_number - 1, + &secp_ctx, + ) + .expect( + "Must be able to derive the pending next commitment point upon channel restoration", + ); + HolderCommitmentPoint { + next_transaction_number: holder_commitment_next_transaction_number, + current_point, + next_point, + pending_next_point: Some(pending_next_point), + } + }, + } }; Ok(FundedChannel { @@ -13905,9 +14201,9 @@ where counterparty_max_commitment_tx_output: Mutex::new((0, 0)), #[cfg(any(test, fuzzing))] - next_local_commitment_tx_fee_info_cached: Mutex::new(None), + next_local_fee: Mutex::new(PredictedNextFee::default()), #[cfg(any(test, fuzzing))] - next_remote_commitment_tx_fee_info_cached: Mutex::new(None), + next_remote_fee: Mutex::new(PredictedNextFee::default()), channel_transaction_parameters: channel_parameters, funding_transaction, @@ -13940,7 +14236,7 @@ where shutdown_scriptpubkey, destination_script, - cur_counterparty_commitment_transaction_number, + counterparty_next_commitment_transaction_number, holder_max_accepted_htlcs, pending_inbound_htlcs, @@ -13956,7 +14252,6 @@ where monitor_pending_failures, monitor_pending_finalized_fulfills: monitor_pending_finalized_fulfills.unwrap(), monitor_pending_update_adds: monitor_pending_update_adds.unwrap_or_default(), - monitor_pending_tx_signatures: None, signer_pending_revoke_and_ack: false, signer_pending_commitment_update: false, @@ -13993,8 +14288,8 @@ where is_batch_funding, - counterparty_cur_commitment_point, - counterparty_prev_commitment_point, + counterparty_next_commitment_point, + counterparty_current_commitment_point, counterparty_node_id, counterparty_shutdown_scriptpubkey, @@ -14026,13 +14321,12 @@ where blocked_monitor_updates: blocked_monitor_updates.unwrap(), is_manual_broadcast: is_manual_broadcast.unwrap_or(false), - - is_holder_quiescence_initiator: None, }, interactive_tx_signing_session, holder_commitment_point, #[cfg(splicing)] pending_splice: None, + quiescent_action, }) } } @@ -14081,12 +14375,14 @@ mod tests { }; use crate::ln::channel_keys::{RevocationBasepoint, RevocationKey}; use crate::ln::channelmanager::{self, HTLCSource, PaymentId}; + use crate::ln::funding::FundingTxInput; use crate::ln::msgs; use crate::ln::msgs::{ChannelUpdate, UnsignedChannelUpdate, MAX_VALUE_MSAT}; use crate::ln::onion_utils::{AttributionData, LocalHTLCFailureReason}; use crate::ln::script::ShutdownScript; use crate::prelude::*; use crate::routing::router::{Path, RouteHop}; + #[cfg(ldk_test_vectors)] use crate::sign::{ChannelSigner, EntropySource, InMemorySigner, SignerProvider}; #[cfg(splicing)] use crate::sync::Mutex; @@ -14107,17 +14403,12 @@ mod tests { use bitcoin::hex::FromHex; use bitcoin::locktime::absolute::LockTime; use bitcoin::network::Network; - use bitcoin::opcodes; - use bitcoin::script::{Builder, ScriptBuf}; + use bitcoin::script::Builder; use bitcoin::secp256k1::ffi::Signature as FFISignature; use bitcoin::secp256k1::{ecdsa::Signature, Secp256k1}; use bitcoin::secp256k1::{PublicKey, SecretKey}; - #[cfg(splicing)] - use bitcoin::transaction::TxIn; use bitcoin::transaction::{Transaction, TxOut, Version}; - #[cfg(splicing)] - use bitcoin::Weight; - use bitcoin::{WPubkeyHash, WitnessProgram, WitnessVersion}; + use bitcoin::{ScriptBuf, WPubkeyHash, WitnessProgram, WitnessVersion}; use std::cmp; #[test] @@ -14143,16 +14434,19 @@ mod tests { ); } + #[cfg(ldk_test_vectors)] struct Keys { - signer: InMemorySigner, + signer: crate::sign::InMemorySigner, } + #[cfg(ldk_test_vectors)] impl EntropySource for Keys { fn get_secure_random_bytes(&self) -> [u8; 32] { [0; 32] } } + #[cfg(ldk_test_vectors)] impl SignerProvider for Keys { type EcdsaSigner = InMemorySigner; #[cfg(taproot)] @@ -14166,16 +14460,18 @@ mod tests { self.signer.clone() } - fn get_destination_script(&self, _channel_keys_id: [u8; 32]) -> Result { + fn get_destination_script( + &self, _channel_keys_id: [u8; 32], + ) -> Result { let secp_ctx = Secp256k1::signing_only(); let hex = "0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; let channel_monitor_claim_key = SecretKey::from_slice(&>::from_hex(hex).unwrap()[..]).unwrap(); - let channel_monitor_claim_key_hash = WPubkeyHash::hash( + let channel_monitor_claim_key_hash = bitcoin::WPubkeyHash::hash( &PublicKey::from_secret_key(&secp_ctx, &channel_monitor_claim_key).serialize(), ); Ok(Builder::new() - .push_opcode(opcodes::all::OP_PUSHBYTES_0) + .push_opcode(bitcoin::opcodes::all::OP_PUSHBYTES_0) .push_slice(channel_monitor_claim_key_hash) .into_script()) } @@ -15818,54 +16114,65 @@ mod tests { #[rustfmt::skip] fn test_estimate_v2_funding_transaction_fee() { use crate::ln::channel::estimate_v2_funding_transaction_fee; - use bitcoin::Weight; - // 2 inputs with weight 300, initiator, 2000 sat/kw feerate + let one_input = [funding_input_sats(1_000)]; + let two_inputs = [funding_input_sats(1_000), funding_input_sats(1_000)]; + + // 2 inputs, initiator, 2000 sat/kw feerate assert_eq!( - estimate_v2_funding_transaction_fee(true, 2, Weight::from_wu(300), 2000), - 1668 + estimate_v2_funding_transaction_fee(&two_inputs, &[], true, false, 2000), + 1520, ); // higher feerate assert_eq!( - estimate_v2_funding_transaction_fee(true, 2, Weight::from_wu(300), 3000), - 2502 + estimate_v2_funding_transaction_fee(&two_inputs, &[], true, false, 3000), + 2280, ); // only 1 input assert_eq!( - estimate_v2_funding_transaction_fee(true, 1, Weight::from_wu(300), 2000), - 1348 + estimate_v2_funding_transaction_fee(&one_input, &[], true, false, 2000), + 974, ); - // 0 input weight + // 0 inputs assert_eq!( - estimate_v2_funding_transaction_fee(true, 1, Weight::from_wu(0), 2000), - 748 + estimate_v2_funding_transaction_fee(&[], &[], true, false, 2000), + 428, ); // not initiator assert_eq!( - estimate_v2_funding_transaction_fee(false, 1, Weight::from_wu(0), 2000), - 320 + estimate_v2_funding_transaction_fee(&[], &[], false, false, 2000), + 0, + ); + + // splice initiator + assert_eq!( + estimate_v2_funding_transaction_fee(&one_input, &[], true, true, 2000), + 1746, + ); + + // splice acceptor + assert_eq!( + estimate_v2_funding_transaction_fee(&one_input, &[], false, true, 2000), + 546, ); } - #[cfg(splicing)] #[rustfmt::skip] - fn funding_input_sats(input_value_sats: u64) -> (TxIn, Transaction, Weight) { - use crate::sign::P2WPKH_WITNESS_WEIGHT; - - let input_1_prev_out = TxOut { value: Amount::from_sat(input_value_sats), script_pubkey: ScriptBuf::default() }; - let input_1_prev_tx = Transaction { - input: vec![], output: vec![input_1_prev_out], - version: Version::TWO, lock_time: bitcoin::absolute::LockTime::ZERO, + fn funding_input_sats(input_value_sats: u64) -> FundingTxInput { + let prevout = TxOut { + value: Amount::from_sat(input_value_sats), + script_pubkey: ScriptBuf::new_p2wpkh(&WPubkeyHash::all_zeros()), }; - let input_1_txin = TxIn { - previous_output: bitcoin::OutPoint { txid: input_1_prev_tx.compute_txid(), vout: 0 }, - ..Default::default() + let prevtx = Transaction { + input: vec![], output: vec![prevout], + version: Version::TWO, lock_time: bitcoin::absolute::LockTime::ZERO, }; - (input_1_txin, input_1_prev_tx, Weight::from_wu(P2WPKH_WITNESS_WEIGHT)) + + FundingTxInput::new_p2wpkh(prevtx, 0).unwrap() } #[cfg(splicing)] @@ -15886,7 +16193,7 @@ mod tests { true, 2000, ).unwrap(), - 2268, + 2292, ); // negative case, inputs clearly insufficient @@ -15902,13 +16209,13 @@ mod tests { ); assert_eq!( format!("{:?}", res.err().unwrap()), - "Warn: Total input amount 100000 is lower than needed for contribution 220000, considering fees of 1730. Need more inputs.", + "Warn: Total input amount 100000 is lower than needed for contribution 220000, considering fees of 1746. Need more inputs.", ); } // barely covers { - let expected_fee: u64 = 2268; + let expected_fee: u64 = 2292; assert_eq!( check_v2_funding_inputs_sufficient( (300_000 - expected_fee - 20) as i64, @@ -15938,13 +16245,13 @@ mod tests { ); assert_eq!( format!("{:?}", res.err().unwrap()), - "Warn: Total input amount 300000 is lower than needed for contribution 298032, considering fees of 2495. Need more inputs.", + "Warn: Total input amount 300000 is lower than needed for contribution 298032, considering fees of 2522. Need more inputs.", ); } // barely covers, less fees (no extra weight, no init) { - let expected_fee: u64 = 1076; + let expected_fee: u64 = 1092; assert_eq!( check_v2_funding_inputs_sufficient( (300_000 - expected_fee - 20) as i64, @@ -15965,7 +16272,7 @@ mod tests { fn get_pre_and_post( pre_channel_value: u64, our_funding_contribution: i64, their_funding_contribution: i64, ) -> (u64, u64) { - use crate::ln::channel::FundingScope; + use crate::ln::channel::{FundingScope, PredictedNextFee}; let funding = FundingScope { value_to_self_msat: 0, @@ -15978,9 +16285,9 @@ mod tests { counterparty_max_commitment_tx_output: Mutex::new((0, 0)), #[cfg(any(test, fuzzing))] - next_local_commitment_tx_fee_info_cached: Mutex::new(None), + next_local_fee: Mutex::new(PredictedNextFee::default()), #[cfg(any(test, fuzzing))] - next_remote_commitment_tx_fee_info_cached: Mutex::new(None), + next_remote_fee: Mutex::new(PredictedNextFee::default()), channel_transaction_parameters: ChannelTransactionParameters::test_dummy( pre_channel_value, diff --git a/lightning/src/ln/channel_acceptance_tests.rs b/lightning/src/ln/channel_acceptance_tests.rs deleted file mode 100644 index c0ab1d31ff5..00000000000 --- a/lightning/src/ln/channel_acceptance_tests.rs +++ /dev/null @@ -1,419 +0,0 @@ -use crate::events::Event; -use crate::ln::channelmanager::{MAX_UNFUNDED_CHANNEL_PEERS, MAX_UNFUNDED_CHANS_PER_PEER}; -use crate::ln::msgs::{ - AcceptChannel, BaseMessageHandler, ChannelMessageHandler, ErrorAction, MessageSendEvent, -}; -use crate::ln::types::ChannelId; -use crate::ln::{functional_test_utils::*, msgs}; -use crate::sign::EntropySource; -use crate::util::config::{ChannelConfigOverrides, ChannelHandshakeConfigUpdate, UserConfig}; -use crate::util::errors::APIError; -use bitcoin::secp256k1::{PublicKey, SecretKey}; -use lightning_types::features::ChannelTypeFeatures; - -#[test] -fn test_outbound_chans_unlimited() { - // Test that we never refuse an outbound channel even if a peer is unfuned-channel-limited - let chanmon_cfgs = create_chanmon_cfgs(2); - let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); - let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); - - // Note that create_network connects the nodes together for us - let nodes = create_network(2, &node_cfgs, &node_chanmgrs); - let node_a = nodes[0].node.get_our_node_id(); - let node_b = nodes[1].node.get_our_node_id(); - nodes[0].node.create_channel(node_b, 100_000, 0, 42, None, None).unwrap(); - let mut open_channel_msg = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b); - - for _ in 0..MAX_UNFUNDED_CHANS_PER_PEER { - nodes[1].node.handle_open_channel(node_a, &open_channel_msg); - get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, node_a); - open_channel_msg.common_fields.temporary_channel_id = - ChannelId::temporary_from_entropy_source(&nodes[0].keys_manager); - } - - // Once we have MAX_UNFUNDED_CHANS_PER_PEER unfunded channels, new inbound channels will be - // rejected. - nodes[1].node.handle_open_channel(node_a, &open_channel_msg); - assert_eq!( - get_err_msg(&nodes[1], &node_a).channel_id, - open_channel_msg.common_fields.temporary_channel_id - ); - - // but we can still open an outbound channel. - nodes[1].node.create_channel(node_a, 100_000, 0, 42, None, None).unwrap(); - get_event_msg!(nodes[1], MessageSendEvent::SendOpenChannel, node_a); - - // but even with such an outbound channel, additional inbound channels will still fail. - nodes[1].node.handle_open_channel(node_a, &open_channel_msg); - assert_eq!( - get_err_msg(&nodes[1], &node_a).channel_id, - open_channel_msg.common_fields.temporary_channel_id - ); -} - -#[test] -fn test_0conf_limiting() { - // Tests that we properly limit inbound channels when we have the manual-channel-acceptance - // flag set and (sometimes) accept channels as 0conf. - let chanmon_cfgs = create_chanmon_cfgs(2); - let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); - let mut settings = test_default_channel_config(); - settings.manually_accept_inbound_channels = true; - let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, Some(settings)]); - let nodes = create_network(2, &node_cfgs, &node_chanmgrs); - - // Note that create_network connects the nodes together for us - let node_b = nodes[1].node.get_our_node_id(); - nodes[0].node.create_channel(node_b, 100_000, 0, 42, None, None).unwrap(); - let mut open_channel_msg = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b); - let init_msg = &msgs::Init { - features: nodes[0].node.init_features(), - networks: None, - remote_network_address: None, - }; - - // First, get us up to MAX_UNFUNDED_CHANNEL_PEERS so we can test at the edge - for _ in 0..MAX_UNFUNDED_CHANNEL_PEERS - 1 { - let random_pk = PublicKey::from_secret_key( - &nodes[0].node.secp_ctx, - &SecretKey::from_slice(&nodes[1].keys_manager.get_secure_random_bytes()).unwrap(), - ); - nodes[1].node.peer_connected(random_pk, init_msg, true).unwrap(); - - nodes[1].node.handle_open_channel(random_pk, &open_channel_msg); - let events = nodes[1].node.get_and_clear_pending_events(); - match events[0] { - Event::OpenChannelRequest { temporary_channel_id, .. } => { - nodes[1] - .node - .accept_inbound_channel(&temporary_channel_id, &random_pk, 23, None) - .unwrap(); - }, - _ => panic!("Unexpected event"), - } - get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, random_pk); - open_channel_msg.common_fields.temporary_channel_id = - ChannelId::temporary_from_entropy_source(&nodes[0].keys_manager); - } - - // If we try to accept a channel from another peer non-0conf it will fail. - let last_random_pk = PublicKey::from_secret_key( - &nodes[0].node.secp_ctx, - &SecretKey::from_slice(&nodes[1].keys_manager.get_secure_random_bytes()).unwrap(), - ); - nodes[1].node.peer_connected(last_random_pk, init_msg, true).unwrap(); - nodes[1].node.handle_open_channel(last_random_pk, &open_channel_msg); - let events = nodes[1].node.get_and_clear_pending_events(); - match events[0] { - Event::OpenChannelRequest { temporary_channel_id, .. } => { - match nodes[1].node.accept_inbound_channel( - &temporary_channel_id, - &last_random_pk, - 23, - None, - ) { - Err(APIError::APIMisuseError { err }) => assert_eq!( - err, - "Too many peers with unfunded channels, refusing to accept new ones" - ), - _ => panic!(), - } - }, - _ => panic!("Unexpected event"), - } - assert_eq!( - get_err_msg(&nodes[1], &last_random_pk).channel_id, - open_channel_msg.common_fields.temporary_channel_id - ); - - // ...however if we accept the same channel 0conf it should work just fine. - nodes[1].node.handle_open_channel(last_random_pk, &open_channel_msg); - let events = nodes[1].node.get_and_clear_pending_events(); - match events[0] { - Event::OpenChannelRequest { temporary_channel_id, .. } => { - nodes[1] - .node - .accept_inbound_channel_from_trusted_peer_0conf( - &temporary_channel_id, - &last_random_pk, - 23, - None, - ) - .unwrap(); - }, - _ => panic!("Unexpected event"), - } - get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, last_random_pk); -} - -#[test] -fn test_inbound_anchors_manual_acceptance() { - let mut anchors_cfg = test_default_channel_config(); - anchors_cfg.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true; - do_test_manual_inbound_accept_with_override(anchors_cfg, None); -} - -#[test] -fn test_inbound_anchors_manual_acceptance_overridden() { - let overrides = ChannelConfigOverrides { - handshake_overrides: Some(ChannelHandshakeConfigUpdate { - max_inbound_htlc_value_in_flight_percent_of_channel: Some(5), - htlc_minimum_msat: Some(1000), - minimum_depth: Some(2), - to_self_delay: Some(200), - max_accepted_htlcs: Some(5), - channel_reserve_proportional_millionths: Some(20000), - }), - update_overrides: None, - }; - - let mut anchors_cfg = test_default_channel_config(); - anchors_cfg.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true; - - let accept_message = do_test_manual_inbound_accept_with_override(anchors_cfg, Some(overrides)); - assert_eq!(accept_message.common_fields.max_htlc_value_in_flight_msat, 5_000_000); - assert_eq!(accept_message.common_fields.htlc_minimum_msat, 1_000); - assert_eq!(accept_message.common_fields.minimum_depth, 2); - assert_eq!(accept_message.common_fields.to_self_delay, 200); - assert_eq!(accept_message.common_fields.max_accepted_htlcs, 5); - assert_eq!(accept_message.channel_reserve_satoshis, 2_000); -} - -#[test] -fn test_inbound_zero_fee_commitments_manual_acceptance() { - let mut zero_fee_cfg = test_default_channel_config(); - zero_fee_cfg.channel_handshake_config.negotiate_anchor_zero_fee_commitments = true; - do_test_manual_inbound_accept_with_override(zero_fee_cfg, None); -} - -fn do_test_manual_inbound_accept_with_override( - start_cfg: UserConfig, config_overrides: Option, -) -> AcceptChannel { - let mut mannual_accept_cfg = start_cfg.clone(); - mannual_accept_cfg.manually_accept_inbound_channels = true; - - let chanmon_cfgs = create_chanmon_cfgs(3); - let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); - let node_chanmgrs = create_node_chanmgrs( - 3, - &node_cfgs, - &[Some(start_cfg.clone()), Some(start_cfg.clone()), Some(mannual_accept_cfg.clone())], - ); - let nodes = create_network(3, &node_cfgs, &node_chanmgrs); - - let node_a = nodes[0].node.get_our_node_id(); - let node_b = nodes[1].node.get_our_node_id(); - nodes[0].node.create_channel(node_b, 100_000, 0, 42, None, None).unwrap(); - let open_channel_msg = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b); - - nodes[1].node.handle_open_channel(node_a, &open_channel_msg); - assert!(nodes[1].node.get_and_clear_pending_events().is_empty()); - let msg_events = nodes[1].node.get_and_clear_pending_msg_events(); - match &msg_events[0] { - MessageSendEvent::HandleError { node_id, action } => { - assert_eq!(*node_id, node_a); - match action { - ErrorAction::SendErrorMessage { msg } => { - assert_eq!(msg.data, "No channels with anchor outputs accepted".to_owned()) - }, - _ => panic!("Unexpected error action"), - } - }, - _ => panic!("Unexpected event"), - } - - nodes[2].node.handle_open_channel(node_a, &open_channel_msg); - let events = nodes[2].node.get_and_clear_pending_events(); - match events[0] { - Event::OpenChannelRequest { temporary_channel_id, .. } => nodes[2] - .node - .accept_inbound_channel(&temporary_channel_id, &node_a, 23, config_overrides) - .unwrap(), - _ => panic!("Unexpected event"), - } - get_event_msg!(nodes[2], MessageSendEvent::SendAcceptChannel, node_a) -} - -#[test] -fn test_anchors_zero_fee_htlc_tx_downgrade() { - // Tests that if both nodes support anchors, but the remote node does not want to accept - // anchor channels at the moment, an error it sent to the local node such that it can retry - // the channel without the anchors feature. - let mut initiator_cfg = test_default_channel_config(); - initiator_cfg.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true; - - let mut receiver_cfg = test_default_channel_config(); - receiver_cfg.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true; - receiver_cfg.manually_accept_inbound_channels = true; - - let start_type = ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies(); - let end_type = ChannelTypeFeatures::only_static_remote_key(); - do_test_channel_type_downgrade(initiator_cfg, receiver_cfg, start_type, vec![end_type]); -} - -#[test] -fn test_scid_privacy_downgrade() { - // Tests downgrade from `anchors_zero_fee_commitments` with `option_scid_alias` when the - // remote node advertises the features but does not accept the channel, asserting that - // `option_scid_alias` is the last feature to be downgraded. - let mut initiator_cfg = test_default_channel_config(); - initiator_cfg.channel_handshake_config.negotiate_anchor_zero_fee_commitments = true; - initiator_cfg.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true; - initiator_cfg.channel_handshake_config.negotiate_scid_privacy = true; - initiator_cfg.channel_handshake_config.announce_for_forwarding = false; - - let mut receiver_cfg = test_default_channel_config(); - receiver_cfg.channel_handshake_config.negotiate_anchor_zero_fee_commitments = true; - receiver_cfg.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true; - receiver_cfg.channel_handshake_config.negotiate_scid_privacy = true; - receiver_cfg.manually_accept_inbound_channels = true; - - let mut start_type = ChannelTypeFeatures::anchors_zero_fee_commitments(); - start_type.set_scid_privacy_required(); - let mut with_anchors = ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies(); - with_anchors.set_scid_privacy_required(); - let mut with_scid_privacy = ChannelTypeFeatures::only_static_remote_key(); - with_scid_privacy.set_scid_privacy_required(); - let static_remote = ChannelTypeFeatures::only_static_remote_key(); - let downgrade_types = vec![with_anchors, with_scid_privacy, static_remote]; - - do_test_channel_type_downgrade(initiator_cfg, receiver_cfg, start_type, downgrade_types); -} - -#[test] -fn test_zero_fee_commitments_downgrade() { - // Tests that the local node will retry without zero fee commitments in the case where the - // remote node supports the feature but does not accept it. - let mut initiator_cfg = test_default_channel_config(); - initiator_cfg.channel_handshake_config.negotiate_anchor_zero_fee_commitments = true; - initiator_cfg.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true; - - let mut receiver_cfg = test_default_channel_config(); - receiver_cfg.channel_handshake_config.negotiate_anchor_zero_fee_commitments = true; - receiver_cfg.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true; - receiver_cfg.manually_accept_inbound_channels = true; - - let start_type = ChannelTypeFeatures::anchors_zero_fee_commitments(); - let downgrade_types = vec![ - ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies(), - ChannelTypeFeatures::only_static_remote_key(), - ]; - do_test_channel_type_downgrade(initiator_cfg, receiver_cfg, start_type, downgrade_types); -} - -#[test] -fn test_zero_fee_commitments_downgrade_to_static_remote() { - // Tests that the local node will retry with static remote key when zero fee commitments - // are supported (but not accepted), but not legacy anchors. - let mut initiator_cfg = test_default_channel_config(); - initiator_cfg.channel_handshake_config.negotiate_anchor_zero_fee_commitments = true; - initiator_cfg.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true; - - let mut receiver_cfg = test_default_channel_config(); - receiver_cfg.channel_handshake_config.negotiate_anchor_zero_fee_commitments = true; - receiver_cfg.manually_accept_inbound_channels = true; - - let start_type = ChannelTypeFeatures::anchors_zero_fee_commitments(); - let end_type = ChannelTypeFeatures::only_static_remote_key(); - do_test_channel_type_downgrade(initiator_cfg, receiver_cfg, start_type, vec![end_type]); -} - -fn do_test_channel_type_downgrade( - initiator_cfg: UserConfig, acceptor_cfg: UserConfig, start_type: ChannelTypeFeatures, - downgrade_types: Vec, -) { - let chanmon_cfgs = create_chanmon_cfgs(2); - let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); - let node_chanmgrs = - create_node_chanmgrs(2, &node_cfgs, &[Some(initiator_cfg), Some(acceptor_cfg)]); - let nodes = create_network(2, &node_cfgs, &node_chanmgrs); - let error_message = "Channel force-closed"; - - let node_a = nodes[0].node.get_our_node_id(); - let node_b = nodes[1].node.get_our_node_id(); - nodes[0].node.create_channel(node_b, 100_000, 0, 0, None, None).unwrap(); - let mut open_channel_msg = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b); - assert_eq!(open_channel_msg.common_fields.channel_type.as_ref().unwrap(), &start_type); - - for downgrade_type in downgrade_types { - nodes[1].node.handle_open_channel(node_a, &open_channel_msg); - let events = nodes[1].node.get_and_clear_pending_events(); - match events[0] { - Event::OpenChannelRequest { temporary_channel_id, .. } => { - nodes[1] - .node - .force_close_broadcasting_latest_txn( - &temporary_channel_id, - &node_a, - error_message.to_string(), - ) - .unwrap(); - }, - _ => panic!("Unexpected event"), - } - - let error_msg = get_err_msg(&nodes[1], &node_a); - nodes[0].node.handle_error(node_b, &error_msg); - - open_channel_msg = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b); - let channel_type = open_channel_msg.common_fields.channel_type.as_ref().unwrap(); - assert_eq!(channel_type, &downgrade_type); - - // Since nodes[1] should not have accepted the channel, it should - // not have generated any events. - assert!(nodes[1].node.get_and_clear_pending_events().is_empty()); - } -} - -#[test] -fn test_no_channel_downgrade() { - // Tests that the local node will not retry when a `option_static_remote` channel is - // rejected by a peer that advertises support for the feature. - let initiator_cfg = test_default_channel_config(); - let mut receiver_cfg = test_default_channel_config(); - receiver_cfg.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true; - receiver_cfg.manually_accept_inbound_channels = true; - - let chanmon_cfgs = create_chanmon_cfgs(2); - let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); - let node_chanmgrs = - create_node_chanmgrs(2, &node_cfgs, &[Some(initiator_cfg), Some(receiver_cfg)]); - let nodes = create_network(2, &node_cfgs, &node_chanmgrs); - let error_message = "Channel force-closed"; - - let node_a = nodes[0].node.get_our_node_id(); - let node_b = nodes[1].node.get_our_node_id(); - - nodes[0].node.create_channel(node_b, 100_000, 0, 0, None, None).unwrap(); - let open_channel_msg = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b); - let start_type = ChannelTypeFeatures::only_static_remote_key(); - assert_eq!(open_channel_msg.common_fields.channel_type.as_ref().unwrap(), &start_type); - - nodes[1].node.handle_open_channel(node_a, &open_channel_msg); - let events = nodes[1].node.get_and_clear_pending_events(); - match events[0] { - Event::OpenChannelRequest { temporary_channel_id, .. } => { - nodes[1] - .node - .force_close_broadcasting_latest_txn( - &temporary_channel_id, - &node_a, - error_message.to_string(), - ) - .unwrap(); - }, - _ => panic!("Unexpected event"), - } - - let error_msg = get_err_msg(&nodes[1], &node_a); - nodes[0].node.handle_error(node_b, &error_msg); - - // Since nodes[0] could not retry the channel with a different type, it should close it. - let chan_closed_events = nodes[0].node.get_and_clear_pending_events(); - assert_eq!(chan_closed_events.len(), 1); - if let Event::ChannelClosed { .. } = chan_closed_events[0] { - } else { - panic!(); - } -} diff --git a/lightning/src/ln/channel_open_tests.rs b/lightning/src/ln/channel_open_tests.rs new file mode 100644 index 00000000000..72d9ccafaf6 --- /dev/null +++ b/lightning/src/ln/channel_open_tests.rs @@ -0,0 +1,2546 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +//! Tests that test the channel open process. + +use crate::chain::chaininterface::LowerBoundedFeeEstimator; +use crate::chain::channelmonitor::{self, ChannelMonitorUpdateStep}; +use crate::chain::transaction::OutPoint; +use crate::chain::{self, ChannelMonitorUpdateStatus}; +use crate::events::{ClosureReason, Event, FundingInfo}; +use crate::ln::channel::{ + get_holder_selected_channel_reserve_satoshis, ChannelError, InboundV1Channel, + OutboundV1Channel, COINBASE_MATURITY, UNFUNDED_CHANNEL_AGE_LIMIT_TICKS, +}; +use crate::ln::channelmanager::{ + self, BREAKDOWN_TIMEOUT, MAX_UNFUNDED_CHANNEL_PEERS, MAX_UNFUNDED_CHANS_PER_PEER, +}; +use crate::ln::msgs::{ + AcceptChannel, BaseMessageHandler, ChannelMessageHandler, ErrorAction, MessageSendEvent, +}; +use crate::ln::types::ChannelId; +use crate::ln::{functional_test_utils::*, msgs}; +use crate::sign::EntropySource; +use crate::util::config::{ + ChannelConfigOverrides, ChannelConfigUpdate, ChannelHandshakeConfigUpdate, UserConfig, +}; +use crate::util::errors::APIError; +use crate::util::test_utils::{self, TestLogger}; + +use bitcoin::constants::ChainHash; +use bitcoin::hashes::Hash; +use bitcoin::locktime::absolute::LockTime; +use bitcoin::network::Network; +use bitcoin::script::ScriptBuf; +use bitcoin::secp256k1::{PublicKey, SecretKey}; +use bitcoin::transaction::Version; +use bitcoin::OutPoint as BitcoinOutPoint; +use bitcoin::{Amount, Sequence, Transaction, TxIn, TxOut, Witness}; + +use lightning_macros::xtest; + +use lightning_types::features::ChannelTypeFeatures; + +#[test] +fn test_outbound_chans_unlimited() { + // Test that we never refuse an outbound channel even if a peer is unfuned-channel-limited + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + + // Note that create_network connects the nodes together for us + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + let node_a = nodes[0].node.get_our_node_id(); + let node_b = nodes[1].node.get_our_node_id(); + nodes[0].node.create_channel(node_b, 100_000, 0, 42, None, None).unwrap(); + let mut open_channel_msg = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b); + + for _ in 0..MAX_UNFUNDED_CHANS_PER_PEER { + nodes[1].node.handle_open_channel(node_a, &open_channel_msg); + get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, node_a); + open_channel_msg.common_fields.temporary_channel_id = + ChannelId::temporary_from_entropy_source(&nodes[0].keys_manager); + } + + // Once we have MAX_UNFUNDED_CHANS_PER_PEER unfunded channels, new inbound channels will be + // rejected. + nodes[1].node.handle_open_channel(node_a, &open_channel_msg); + assert_eq!( + get_err_msg(&nodes[1], &node_a).channel_id, + open_channel_msg.common_fields.temporary_channel_id + ); + + // but we can still open an outbound channel. + nodes[1].node.create_channel(node_a, 100_000, 0, 42, None, None).unwrap(); + get_event_msg!(nodes[1], MessageSendEvent::SendOpenChannel, node_a); + + // but even with such an outbound channel, additional inbound channels will still fail. + nodes[1].node.handle_open_channel(node_a, &open_channel_msg); + assert_eq!( + get_err_msg(&nodes[1], &node_a).channel_id, + open_channel_msg.common_fields.temporary_channel_id + ); +} + +#[test] +fn test_0conf_limiting() { + // Tests that we properly limit inbound channels when we have the manual-channel-acceptance + // flag set and (sometimes) accept channels as 0conf. + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let mut settings = test_default_channel_config(); + settings.manually_accept_inbound_channels = true; + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, Some(settings)]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + // Note that create_network connects the nodes together for us + let node_b = nodes[1].node.get_our_node_id(); + nodes[0].node.create_channel(node_b, 100_000, 0, 42, None, None).unwrap(); + let mut open_channel_msg = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b); + let init_msg = &msgs::Init { + features: nodes[0].node.init_features(), + networks: None, + remote_network_address: None, + }; + + // First, get us up to MAX_UNFUNDED_CHANNEL_PEERS so we can test at the edge + for _ in 0..MAX_UNFUNDED_CHANNEL_PEERS - 1 { + let random_pk = PublicKey::from_secret_key( + &nodes[0].node.secp_ctx, + &SecretKey::from_slice(&nodes[1].keys_manager.get_secure_random_bytes()).unwrap(), + ); + nodes[1].node.peer_connected(random_pk, init_msg, true).unwrap(); + + nodes[1].node.handle_open_channel(random_pk, &open_channel_msg); + let events = nodes[1].node.get_and_clear_pending_events(); + match events[0] { + Event::OpenChannelRequest { temporary_channel_id, .. } => { + nodes[1] + .node + .accept_inbound_channel(&temporary_channel_id, &random_pk, 23, None) + .unwrap(); + }, + _ => panic!("Unexpected event"), + } + get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, random_pk); + open_channel_msg.common_fields.temporary_channel_id = + ChannelId::temporary_from_entropy_source(&nodes[0].keys_manager); + } + + // If we try to accept a channel from another peer non-0conf it will fail. + let last_random_pk = PublicKey::from_secret_key( + &nodes[0].node.secp_ctx, + &SecretKey::from_slice(&nodes[1].keys_manager.get_secure_random_bytes()).unwrap(), + ); + nodes[1].node.peer_connected(last_random_pk, init_msg, true).unwrap(); + nodes[1].node.handle_open_channel(last_random_pk, &open_channel_msg); + let events = nodes[1].node.get_and_clear_pending_events(); + match events[0] { + Event::OpenChannelRequest { temporary_channel_id, .. } => { + match nodes[1].node.accept_inbound_channel( + &temporary_channel_id, + &last_random_pk, + 23, + None, + ) { + Err(APIError::APIMisuseError { err }) => assert_eq!( + err, + "Too many peers with unfunded channels, refusing to accept new ones" + ), + _ => panic!(), + } + }, + _ => panic!("Unexpected event"), + } + assert_eq!( + get_err_msg(&nodes[1], &last_random_pk).channel_id, + open_channel_msg.common_fields.temporary_channel_id + ); + + // ...however if we accept the same channel 0conf it should work just fine. + nodes[1].node.handle_open_channel(last_random_pk, &open_channel_msg); + let events = nodes[1].node.get_and_clear_pending_events(); + match events[0] { + Event::OpenChannelRequest { temporary_channel_id, .. } => { + nodes[1] + .node + .accept_inbound_channel_from_trusted_peer_0conf( + &temporary_channel_id, + &last_random_pk, + 23, + None, + ) + .unwrap(); + }, + _ => panic!("Unexpected event"), + } + get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, last_random_pk); +} + +#[test] +fn test_inbound_anchors_manual_acceptance() { + let mut anchors_cfg = test_default_channel_config(); + anchors_cfg.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true; + do_test_manual_inbound_accept_with_override(anchors_cfg, None); +} + +#[test] +fn test_inbound_anchors_manual_acceptance_overridden() { + let overrides = ChannelConfigOverrides { + handshake_overrides: Some(ChannelHandshakeConfigUpdate { + max_inbound_htlc_value_in_flight_percent_of_channel: Some(5), + htlc_minimum_msat: Some(1000), + minimum_depth: Some(2), + to_self_delay: Some(200), + max_accepted_htlcs: Some(5), + channel_reserve_proportional_millionths: Some(20000), + }), + update_overrides: None, + }; + + let mut anchors_cfg = test_default_channel_config(); + anchors_cfg.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true; + + let accept_message = do_test_manual_inbound_accept_with_override(anchors_cfg, Some(overrides)); + assert_eq!(accept_message.common_fields.max_htlc_value_in_flight_msat, 5_000_000); + assert_eq!(accept_message.common_fields.htlc_minimum_msat, 1_000); + assert_eq!(accept_message.common_fields.minimum_depth, 2); + assert_eq!(accept_message.common_fields.to_self_delay, 200); + assert_eq!(accept_message.common_fields.max_accepted_htlcs, 5); + assert_eq!(accept_message.channel_reserve_satoshis, 2_000); +} + +#[test] +fn test_inbound_zero_fee_commitments_manual_acceptance() { + let mut zero_fee_cfg = test_default_channel_config(); + zero_fee_cfg.channel_handshake_config.negotiate_anchor_zero_fee_commitments = true; + do_test_manual_inbound_accept_with_override(zero_fee_cfg, None); +} + +fn do_test_manual_inbound_accept_with_override( + start_cfg: UserConfig, config_overrides: Option, +) -> AcceptChannel { + let mut mannual_accept_cfg = start_cfg.clone(); + mannual_accept_cfg.manually_accept_inbound_channels = true; + + let chanmon_cfgs = create_chanmon_cfgs(3); + let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs( + 3, + &node_cfgs, + &[Some(start_cfg.clone()), Some(start_cfg.clone()), Some(mannual_accept_cfg.clone())], + ); + let nodes = create_network(3, &node_cfgs, &node_chanmgrs); + + let node_a = nodes[0].node.get_our_node_id(); + let node_b = nodes[1].node.get_our_node_id(); + nodes[0].node.create_channel(node_b, 100_000, 0, 42, None, None).unwrap(); + let open_channel_msg = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b); + + nodes[1].node.handle_open_channel(node_a, &open_channel_msg); + assert!(nodes[1].node.get_and_clear_pending_events().is_empty()); + let msg_events = nodes[1].node.get_and_clear_pending_msg_events(); + match &msg_events[0] { + MessageSendEvent::HandleError { node_id, action } => { + assert_eq!(*node_id, node_a); + match action { + ErrorAction::SendErrorMessage { msg } => { + assert_eq!(msg.data, "No channels with anchor outputs accepted".to_owned()) + }, + _ => panic!("Unexpected error action"), + } + }, + _ => panic!("Unexpected event"), + } + + nodes[2].node.handle_open_channel(node_a, &open_channel_msg); + let events = nodes[2].node.get_and_clear_pending_events(); + match events[0] { + Event::OpenChannelRequest { temporary_channel_id, .. } => nodes[2] + .node + .accept_inbound_channel(&temporary_channel_id, &node_a, 23, config_overrides) + .unwrap(), + _ => panic!("Unexpected event"), + } + get_event_msg!(nodes[2], MessageSendEvent::SendAcceptChannel, node_a) +} + +#[test] +fn test_anchors_zero_fee_htlc_tx_downgrade() { + // Tests that if both nodes support anchors, but the remote node does not want to accept + // anchor channels at the moment, an error it sent to the local node such that it can retry + // the channel without the anchors feature. + let mut initiator_cfg = test_default_channel_config(); + initiator_cfg.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true; + + let mut receiver_cfg = test_default_channel_config(); + receiver_cfg.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true; + receiver_cfg.manually_accept_inbound_channels = true; + + let start_type = ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies(); + let end_type = ChannelTypeFeatures::only_static_remote_key(); + do_test_channel_type_downgrade(initiator_cfg, receiver_cfg, start_type, vec![end_type]); +} + +#[test] +fn test_scid_privacy_downgrade() { + // Tests downgrade from `anchors_zero_fee_commitments` with `option_scid_alias` when the + // remote node advertises the features but does not accept the channel, asserting that + // `option_scid_alias` is the last feature to be downgraded. + let mut initiator_cfg = test_default_channel_config(); + initiator_cfg.channel_handshake_config.negotiate_anchor_zero_fee_commitments = true; + initiator_cfg.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true; + initiator_cfg.channel_handshake_config.negotiate_scid_privacy = true; + initiator_cfg.channel_handshake_config.announce_for_forwarding = false; + + let mut receiver_cfg = test_default_channel_config(); + receiver_cfg.channel_handshake_config.negotiate_anchor_zero_fee_commitments = true; + receiver_cfg.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true; + receiver_cfg.channel_handshake_config.negotiate_scid_privacy = true; + receiver_cfg.manually_accept_inbound_channels = true; + + let mut start_type = ChannelTypeFeatures::anchors_zero_fee_commitments(); + start_type.set_scid_privacy_required(); + let mut with_anchors = ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies(); + with_anchors.set_scid_privacy_required(); + let mut with_scid_privacy = ChannelTypeFeatures::only_static_remote_key(); + with_scid_privacy.set_scid_privacy_required(); + let static_remote = ChannelTypeFeatures::only_static_remote_key(); + let downgrade_types = vec![with_anchors, with_scid_privacy, static_remote]; + + do_test_channel_type_downgrade(initiator_cfg, receiver_cfg, start_type, downgrade_types); +} + +#[test] +fn test_zero_fee_commitments_downgrade() { + // Tests that the local node will retry without zero fee commitments in the case where the + // remote node supports the feature but does not accept it. + let mut initiator_cfg = test_default_channel_config(); + initiator_cfg.channel_handshake_config.negotiate_anchor_zero_fee_commitments = true; + initiator_cfg.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true; + + let mut receiver_cfg = test_default_channel_config(); + receiver_cfg.channel_handshake_config.negotiate_anchor_zero_fee_commitments = true; + receiver_cfg.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true; + receiver_cfg.manually_accept_inbound_channels = true; + + let start_type = ChannelTypeFeatures::anchors_zero_fee_commitments(); + let downgrade_types = vec![ + ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies(), + ChannelTypeFeatures::only_static_remote_key(), + ]; + do_test_channel_type_downgrade(initiator_cfg, receiver_cfg, start_type, downgrade_types); +} + +#[test] +fn test_zero_fee_commitments_downgrade_to_static_remote() { + // Tests that the local node will retry with static remote key when zero fee commitments + // are supported (but not accepted), but not legacy anchors. + let mut initiator_cfg = test_default_channel_config(); + initiator_cfg.channel_handshake_config.negotiate_anchor_zero_fee_commitments = true; + initiator_cfg.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true; + + let mut receiver_cfg = test_default_channel_config(); + receiver_cfg.channel_handshake_config.negotiate_anchor_zero_fee_commitments = true; + receiver_cfg.manually_accept_inbound_channels = true; + + let start_type = ChannelTypeFeatures::anchors_zero_fee_commitments(); + let end_type = ChannelTypeFeatures::only_static_remote_key(); + do_test_channel_type_downgrade(initiator_cfg, receiver_cfg, start_type, vec![end_type]); +} + +fn do_test_channel_type_downgrade( + initiator_cfg: UserConfig, acceptor_cfg: UserConfig, start_type: ChannelTypeFeatures, + downgrade_types: Vec, +) { + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = + create_node_chanmgrs(2, &node_cfgs, &[Some(initiator_cfg), Some(acceptor_cfg)]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + let error_message = "Channel force-closed"; + + let node_a = nodes[0].node.get_our_node_id(); + let node_b = nodes[1].node.get_our_node_id(); + nodes[0].node.create_channel(node_b, 100_000, 0, 0, None, None).unwrap(); + let mut open_channel_msg = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b); + assert_eq!(open_channel_msg.common_fields.channel_type.as_ref().unwrap(), &start_type); + + for downgrade_type in downgrade_types { + nodes[1].node.handle_open_channel(node_a, &open_channel_msg); + let events = nodes[1].node.get_and_clear_pending_events(); + match events[0] { + Event::OpenChannelRequest { temporary_channel_id, .. } => { + nodes[1] + .node + .force_close_broadcasting_latest_txn( + &temporary_channel_id, + &node_a, + error_message.to_string(), + ) + .unwrap(); + }, + _ => panic!("Unexpected event"), + } + + let error_msg = get_err_msg(&nodes[1], &node_a); + nodes[0].node.handle_error(node_b, &error_msg); + + open_channel_msg = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b); + let channel_type = open_channel_msg.common_fields.channel_type.as_ref().unwrap(); + assert_eq!(channel_type, &downgrade_type); + + // Since nodes[1] should not have accepted the channel, it should + // not have generated any events. + assert!(nodes[1].node.get_and_clear_pending_events().is_empty()); + } +} + +#[test] +fn test_no_channel_downgrade() { + // Tests that the local node will not retry when a `option_static_remote` channel is + // rejected by a peer that advertises support for the feature. + let initiator_cfg = test_default_channel_config(); + let mut receiver_cfg = test_default_channel_config(); + receiver_cfg.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true; + receiver_cfg.manually_accept_inbound_channels = true; + + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = + create_node_chanmgrs(2, &node_cfgs, &[Some(initiator_cfg), Some(receiver_cfg)]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + let error_message = "Channel force-closed"; + + let node_a = nodes[0].node.get_our_node_id(); + let node_b = nodes[1].node.get_our_node_id(); + + nodes[0].node.create_channel(node_b, 100_000, 0, 0, None, None).unwrap(); + let open_channel_msg = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b); + let start_type = ChannelTypeFeatures::only_static_remote_key(); + assert_eq!(open_channel_msg.common_fields.channel_type.as_ref().unwrap(), &start_type); + + nodes[1].node.handle_open_channel(node_a, &open_channel_msg); + let events = nodes[1].node.get_and_clear_pending_events(); + match events[0] { + Event::OpenChannelRequest { temporary_channel_id, .. } => { + nodes[1] + .node + .force_close_broadcasting_latest_txn( + &temporary_channel_id, + &node_a, + error_message.to_string(), + ) + .unwrap(); + }, + _ => panic!("Unexpected event"), + } + + let error_msg = get_err_msg(&nodes[1], &node_a); + nodes[0].node.handle_error(node_b, &error_msg); + + // Since nodes[0] could not retry the channel with a different type, it should close it. + let chan_closed_events = nodes[0].node.get_and_clear_pending_events(); + assert_eq!(chan_closed_events.len(), 1); + if let Event::ChannelClosed { .. } = chan_closed_events[0] { + } else { + panic!(); + } +} + +#[xtest(feature = "_externalize_tests")] +fn test_channel_resumption_fail_post_funding() { + // If we fail to exchange funding with a peer prior to it disconnecting we'll resume the + // channel open on reconnect, however if we do exchange funding we do not currently support + // replaying it and here test that the channel closes. + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let node_a_id = nodes[0].node.get_our_node_id(); + let node_b_id = nodes[1].node.get_our_node_id(); + + nodes[0].node.create_channel(node_b_id, 1_000_000, 0, 42, None, None).unwrap(); + let open_chan = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); + nodes[1].node.handle_open_channel(node_a_id, &open_chan); + let accept_chan = get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, node_a_id); + nodes[0].node.handle_accept_channel(node_b_id, &accept_chan); + + let (temp_chan_id, tx, funding_output) = + create_funding_transaction(&nodes[0], &node_b_id, 1_000_000, 42); + let new_chan_id = ChannelId::v1_from_funding_outpoint(funding_output); + nodes[0].node.funding_transaction_generated(temp_chan_id, node_b_id, tx).unwrap(); + + nodes[0].node.peer_disconnected(node_b_id); + check_closed_events( + &nodes[0], + &[ExpectedCloseEvent::from_id_reason(new_chan_id, true, ClosureReason::DisconnectedPeer)], + ); + + // After ddf75afd16 we'd panic on reconnection if we exchanged funding info, so test that + // explicitly here. + let init_msg = msgs::Init { + features: nodes[1].node.init_features(), + networks: None, + remote_network_address: None, + }; + nodes[0].node.peer_connected(node_b_id, &init_msg, true).unwrap(); + assert_eq!(nodes[0].node.get_and_clear_pending_msg_events(), Vec::new()); +} + +#[xtest(feature = "_externalize_tests")] +pub fn test_insane_channel_opens() { + // Stand up a network of 2 nodes + use crate::ln::channel::TOTAL_BITCOIN_SUPPLY_SATOSHIS; + let mut cfg = UserConfig::default(); + cfg.channel_handshake_limits.max_funding_satoshis = TOTAL_BITCOIN_SUPPLY_SATOSHIS + 1; + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, Some(cfg.clone())]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let node_a_id = nodes[0].node.get_our_node_id(); + let node_b_id = nodes[1].node.get_our_node_id(); + + // Instantiate channel parameters where we push the maximum msats given our + // funding satoshis + let channel_value_sat = 31337; // same as funding satoshis + let channel_reserve_satoshis = + get_holder_selected_channel_reserve_satoshis(channel_value_sat, &cfg); + let push_msat = (channel_value_sat - channel_reserve_satoshis) * 1000; + + // Have node0 initiate a channel to node1 with aforementioned parameters + nodes[0].node.create_channel(node_b_id, channel_value_sat, push_msat, 42, None, None).unwrap(); + + // Extract the channel open message from node0 to node1 + let open_channel_message = + get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); + + // Test helper that asserts we get the correct error string given a mutator + // that supposedly makes the channel open message insane + let insane_open_helper = + |expected_error_str: &str, message_mutator: fn(msgs::OpenChannel) -> msgs::OpenChannel| { + let open_channel_mutated = message_mutator(open_channel_message.clone()); + nodes[1].node.handle_open_channel(node_a_id, &open_channel_mutated); + let msg_events = nodes[1].node.get_and_clear_pending_msg_events(); + assert_eq!(msg_events.len(), 1); + let expected_regex = regex::Regex::new(expected_error_str).unwrap(); + if let MessageSendEvent::HandleError { ref action, .. } = msg_events[0] { + match action { + &ErrorAction::SendErrorMessage { .. } => { + nodes[1].logger.assert_log_regex( + "lightning::ln::channelmanager", + expected_regex, + 1, + ); + }, + _ => panic!("unexpected event!"), + } + } else { + assert!(false); + } + }; + + use crate::ln::channelmanager::MAX_LOCAL_BREAKDOWN_TIMEOUT; + + // Test all mutations that would make the channel open message insane + insane_open_helper( + format!( + "Per our config, funding must be at most {}. It was {}", + TOTAL_BITCOIN_SUPPLY_SATOSHIS + 1, + TOTAL_BITCOIN_SUPPLY_SATOSHIS + 2 + ) + .as_str(), + |mut msg| { + msg.common_fields.funding_satoshis = TOTAL_BITCOIN_SUPPLY_SATOSHIS + 2; + msg + }, + ); + insane_open_helper( + format!( + "Funding must be smaller than the total bitcoin supply. It was {}", + TOTAL_BITCOIN_SUPPLY_SATOSHIS + ) + .as_str(), + |mut msg| { + msg.common_fields.funding_satoshis = TOTAL_BITCOIN_SUPPLY_SATOSHIS; + msg + }, + ); + + insane_open_helper("Bogus channel_reserve_satoshis", |mut msg| { + msg.channel_reserve_satoshis = msg.common_fields.funding_satoshis + 1; + msg + }); + + insane_open_helper( + r"push_msat \d+ was larger than channel amount minus reserve \(\d+\)", + |mut msg| { + msg.push_msat = + (msg.common_fields.funding_satoshis - msg.channel_reserve_satoshis) * 1000 + 1; + msg + }, + ); + + insane_open_helper("Peer never wants payout outputs?", |mut msg| { + msg.common_fields.dust_limit_satoshis = msg.common_fields.funding_satoshis + 1; + msg + }); + + insane_open_helper( + r"Minimum htlc value \(\d+\) was larger than full channel value \(\d+\)", + |mut msg| { + msg.common_fields.htlc_minimum_msat = + (msg.common_fields.funding_satoshis - msg.channel_reserve_satoshis) * 1000; + msg + }, + ); + + insane_open_helper( + "They wanted our payments to be delayed by a needlessly long period", + |mut msg| { + msg.common_fields.to_self_delay = MAX_LOCAL_BREAKDOWN_TIMEOUT + 1; + msg + }, + ); + + insane_open_helper("0 max_accepted_htlcs makes for a useless channel", |mut msg| { + msg.common_fields.max_accepted_htlcs = 0; + msg + }); + + insane_open_helper("max_accepted_htlcs was 484. It must not be larger than 483", |mut msg| { + msg.common_fields.max_accepted_htlcs = 484; + msg + }); +} + +#[test] +fn test_insane_zero_fee_channel_open() { + let mut cfg = UserConfig::default(); + cfg.manually_accept_inbound_channels = true; + cfg.channel_handshake_config.negotiate_anchor_zero_fee_commitments = true; + + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = + create_node_chanmgrs(2, &node_cfgs, &[Some(cfg.clone()), Some(cfg.clone())]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let node_a_id = nodes[0].node.get_our_node_id(); + let node_b_id = nodes[1].node.get_our_node_id(); + + nodes[0].node.create_channel(node_b_id, 100_000, 0, 42, None, None).unwrap(); + + let open_channel_message = + get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); + + let insane_open_helper = + |expected_error_str: &str, message_mutator: fn(msgs::OpenChannel) -> msgs::OpenChannel| { + let open_channel_mutated = message_mutator(open_channel_message.clone()); + nodes[1].node.handle_open_channel(node_a_id, &open_channel_mutated); + + let events = nodes[1].node.get_and_clear_pending_events(); + match events[0] { + Event::OpenChannelRequest { temporary_channel_id, .. } => { + match nodes[1].node.accept_inbound_channel( + &temporary_channel_id, + &nodes[0].node.get_our_node_id(), + 23, + None, + ) { + Ok(_) => panic!("Unexpected successful channel accept"), + Err(e) => assert!(format!("{:?}", e).contains(expected_error_str)), + } + }, + _ => panic!("Unexpected event"), + } + + let events = nodes[1].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + assert!(matches!(events[0], MessageSendEvent::HandleError { .. })); + }; + + insane_open_helper( + "max_accepted_htlcs was 115. It must not be larger than 114".into(), + |mut msg| { + msg.common_fields.max_accepted_htlcs = 115; + msg + }, + ); + + insane_open_helper("Zero Fee Channels must never attempt to use a fee".into(), |mut msg| { + msg.common_fields.commitment_feerate_sat_per_1000_weight = 123; + msg + }); +} + +#[xtest(feature = "_externalize_tests")] +pub fn test_funding_exceeds_no_wumbo_limit() { + // Test that if a peer does not support wumbo channels, we'll refuse to open a wumbo channel to + // them. + use crate::ln::channel::MAX_FUNDING_SATOSHIS_NO_WUMBO; + let chanmon_cfgs = create_chanmon_cfgs(2); + let mut node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let mut features = channelmanager::provided_init_features(&test_default_channel_config()); + features.clear_wumbo(); + *node_cfgs[1].override_init_features.borrow_mut() = Some(features); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let node_b_id = nodes[1].node.get_our_node_id(); + + match nodes[0].node.create_channel( + node_b_id, + MAX_FUNDING_SATOSHIS_NO_WUMBO + 1, + 0, + 42, + None, + None, + ) { + Err(APIError::APIMisuseError { err }) => { + let exp_err = format!( + "funding_value must not exceed {}, it was {}", + MAX_FUNDING_SATOSHIS_NO_WUMBO, + MAX_FUNDING_SATOSHIS_NO_WUMBO + 1 + ); + assert_eq!(err, exp_err); + }, + _ => panic!(), + } +} + +fn do_test_sanity_on_in_flight_opens(steps: u8) { + // Previously, we had issues deserializing channels when we hadn't connected the first block + // after creation. To catch that and similar issues, we lean on the Node::drop impl to test + // serialization round-trips and simply do steps towards opening a channel and then drop the + // Node objects. + + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let node_a_id = nodes[0].node.get_our_node_id(); + let node_b_id = nodes[1].node.get_our_node_id(); + + if steps & 0b1000_0000 != 0 { + let block = create_dummy_block(nodes[0].best_block_hash(), 42, Vec::new()); + connect_block(&nodes[0], &block); + connect_block(&nodes[1], &block); + } + + if steps & 0x0f == 0 { + return; + } + nodes[0].node.create_channel(node_b_id, 100000, 10001, 42, None, None).unwrap(); + let open_channel = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); + + if steps & 0x0f == 1 { + return; + } + nodes[1].node.handle_open_channel(node_a_id, &open_channel); + let accept_channel = get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, node_a_id); + + if steps & 0x0f == 2 { + return; + } + nodes[0].node.handle_accept_channel(node_b_id, &accept_channel); + + let (temporary_channel_id, tx, _) = + create_funding_transaction(&nodes[0], &node_b_id, 100000, 42); + + if steps & 0x0f == 3 { + return; + } + nodes[0] + .node + .funding_transaction_generated(temporary_channel_id, node_b_id, tx.clone()) + .unwrap(); + check_added_monitors(&nodes[0], 0); + let funding_created = get_event_msg!(nodes[0], MessageSendEvent::SendFundingCreated, node_b_id); + + let channel_id = ChannelId::v1_from_funding_txid( + funding_created.funding_txid.as_byte_array(), + funding_created.funding_output_index, + ); + + if steps & 0x0f == 4 { + return; + } + nodes[1].node.handle_funding_created(node_a_id, &funding_created); + { + let mut added_monitors = nodes[1].chain_monitor.added_monitors.lock().unwrap(); + assert_eq!(added_monitors.len(), 1); + assert_eq!(added_monitors[0].0, channel_id); + added_monitors.clear(); + } + expect_channel_pending_event(&nodes[1], &node_a_id); + + let funding_signed = get_event_msg!(nodes[1], MessageSendEvent::SendFundingSigned, node_a_id); + + if steps & 0x0f == 5 { + return; + } + nodes[0].node.handle_funding_signed(node_b_id, &funding_signed); + { + let mut added_monitors = nodes[0].chain_monitor.added_monitors.lock().unwrap(); + assert_eq!(added_monitors.len(), 1); + assert_eq!(added_monitors[0].0, channel_id); + added_monitors.clear(); + } + + expect_channel_pending_event(&nodes[0], &node_b_id); + let events_4 = nodes[0].node.get_and_clear_pending_events(); + assert_eq!(events_4.len(), 0); + + if steps & 0x0f == 6 { + return; + } + create_chan_between_nodes_with_value_confirm_first(&nodes[0], &nodes[1], &tx, 2); + + if steps & 0x0f == 7 { + return; + } + confirm_transaction_at(&nodes[0], &tx, 2); + connect_blocks(&nodes[0], CHAN_CONFIRM_DEPTH); + create_chan_between_nodes_with_value_confirm_second(&nodes[1], &nodes[0]); + expect_channel_ready_event(&nodes[0], &node_b_id); +} + +#[xtest(feature = "_externalize_tests")] +pub fn test_sanity_on_in_flight_opens() { + do_test_sanity_on_in_flight_opens(0); + do_test_sanity_on_in_flight_opens(0 | 0b1000_0000); + do_test_sanity_on_in_flight_opens(1); + do_test_sanity_on_in_flight_opens(1 | 0b1000_0000); + do_test_sanity_on_in_flight_opens(2); + do_test_sanity_on_in_flight_opens(2 | 0b1000_0000); + do_test_sanity_on_in_flight_opens(3); + do_test_sanity_on_in_flight_opens(3 | 0b1000_0000); + do_test_sanity_on_in_flight_opens(4); + do_test_sanity_on_in_flight_opens(4 | 0b1000_0000); + do_test_sanity_on_in_flight_opens(5); + do_test_sanity_on_in_flight_opens(5 | 0b1000_0000); + do_test_sanity_on_in_flight_opens(6); + do_test_sanity_on_in_flight_opens(6 | 0b1000_0000); + do_test_sanity_on_in_flight_opens(7); + do_test_sanity_on_in_flight_opens(7 | 0b1000_0000); + do_test_sanity_on_in_flight_opens(8); + do_test_sanity_on_in_flight_opens(8 | 0b1000_0000); +} + +#[xtest(feature = "_externalize_tests")] +#[should_panic] +pub fn bolt2_open_channel_sending_node_checks_part1() { + //This test needs to be on its own as we are catching a panic + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let node_a_id = nodes[0].node.get_our_node_id(); + let node_b_id = nodes[1].node.get_our_node_id(); + + // Force duplicate randomness for every get-random call + for node in nodes.iter() { + *node.keys_manager.override_random_bytes.lock().unwrap() = Some([0; 32]); + } + + // BOLT #2 spec: Sending node must ensure temporary_channel_id is unique from any other channel ID with the same peer. + let channel_value_satoshis = 10000; + let push_msat = 10001; + nodes[0] + .node + .create_channel(node_b_id, channel_value_satoshis, push_msat, 42, None, None) + .unwrap(); + let node0_to_1_send_open_channel = + get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); + nodes[1].node.handle_open_channel(node_a_id, &node0_to_1_send_open_channel); + get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, node_a_id); + + // Create a second channel with the same random values. This used to panic due to a colliding + // channel_id, but now panics due to a colliding outbound SCID alias. + assert!(nodes[0] + .node + .create_channel(node_b_id, channel_value_satoshis, push_msat, 42, None, None) + .is_err()); +} + +#[xtest(feature = "_externalize_tests")] +pub fn bolt2_open_channel_sending_node_checks_part2() { + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let node_b_id = nodes[1].node.get_our_node_id(); + + // BOLT #2 spec: Sending node must set push_msat to equal or less than 1000 * funding_satoshis + let channel_value_satoshis = 10000; + // Test when push_msat is equal to 1000 * funding_satoshis. + let push_msat = 1000 * channel_value_satoshis + 1; + assert!(nodes[0] + .node + .create_channel(node_b_id, channel_value_satoshis, push_msat, 42, None, None) + .is_err()); + + nodes[0].node.create_channel(node_b_id, 100_000, 0, 42, None, None).unwrap(); + + let node0_to_1_send_open_channel = + get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); + + // BOLT #2 spec: Sending node should set to_self_delay sufficient to ensure the sender can irreversibly spend a commitment transaction output, in case of misbehaviour by the receiver. + assert!(BREAKDOWN_TIMEOUT > 0); + assert!(node0_to_1_send_open_channel.common_fields.to_self_delay == BREAKDOWN_TIMEOUT); + + // BOLT #2 spec: Sending node must ensure the chain_hash value identifies the chain it wishes to open the channel within. + let chain_hash = ChainHash::using_genesis_block(Network::Testnet); + assert_eq!(node0_to_1_send_open_channel.common_fields.chain_hash, chain_hash); +} + +#[xtest(feature = "_externalize_tests")] +pub fn bolt2_open_channel_sane_dust_limit() { + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let node_a_id = nodes[0].node.get_our_node_id(); + let node_b_id = nodes[1].node.get_our_node_id(); + + let value_sats = 1000000; + let push_msat = 10001; + nodes[0].node.create_channel(node_b_id, value_sats, push_msat, 42, None, None).unwrap(); + let mut node0_to_1_send_open_channel = + get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); + node0_to_1_send_open_channel.common_fields.dust_limit_satoshis = 547; + node0_to_1_send_open_channel.channel_reserve_satoshis = 100001; + + nodes[1].node.handle_open_channel(node_a_id, &node0_to_1_send_open_channel); + let events = nodes[1].node.get_and_clear_pending_msg_events(); + let err_msg = match events[0] { + MessageSendEvent::HandleError { + action: ErrorAction::SendErrorMessage { ref msg }, .. + } => msg.clone(), + _ => panic!("Unexpected event"), + }; + assert_eq!( + err_msg.data, + "dust_limit_satoshis (547) is greater than the implementation limit (546)" + ); +} + +#[xtest(feature = "_externalize_tests")] +pub fn test_user_configurable_csv_delay() { + // We test our channel constructors yield errors when we pass them absurd csv delay + + let mut low_our_to_self_config = UserConfig::default(); + low_our_to_self_config.channel_handshake_config.our_to_self_delay = 6; + let mut high_their_to_self_config = UserConfig::default(); + high_their_to_self_config.channel_handshake_limits.their_to_self_delay = 100; + let user_cfgs = [Some(high_their_to_self_config.clone()), None]; + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &user_cfgs); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let node_a_id = nodes[0].node.get_our_node_id(); + let node_b_id = nodes[1].node.get_our_node_id(); + + let logger = TestLogger::new(); + + // We test config.our_to_self > BREAKDOWN_TIMEOUT is enforced in OutboundV1Channel::new() + if let Err(error) = OutboundV1Channel::new( + &LowerBoundedFeeEstimator::new(&test_utils::TestFeeEstimator::new(253)), + &nodes[0].keys_manager, + &nodes[0].keys_manager, + node_b_id, + &nodes[1].node.init_features(), + 1000000, + 1000000, + 0, + &low_our_to_self_config, + 0, + 42, + None, + &logger, + ) { + match error { + APIError::APIMisuseError { err } => { + assert!(regex::Regex::new( + r"Configured with an unreasonable our_to_self_delay \(\d+\) putting user funds at risks" + ) + .unwrap() + .is_match(err.as_str())); + }, + _ => panic!("Unexpected event"), + } + } else { + assert!(false) + } + + // We test config.our_to_self > BREAKDOWN_TIMEOUT is enforced in InboundV1Channel::new() + nodes[1].node.create_channel(node_a_id, 1000000, 1000000, 42, None, None).unwrap(); + let mut open_channel = get_event_msg!(nodes[1], MessageSendEvent::SendOpenChannel, node_a_id); + open_channel.common_fields.to_self_delay = 200; + if let Err(error) = InboundV1Channel::new( + &LowerBoundedFeeEstimator::new(&test_utils::TestFeeEstimator::new(253)), + &nodes[0].keys_manager, + &nodes[0].keys_manager, + node_b_id, + &nodes[0].node.channel_type_features(), + &nodes[1].node.init_features(), + &open_channel, + 0, + &low_our_to_self_config, + 0, + &nodes[0].logger, + /*is_0conf=*/ false, + ) { + match error { + ChannelError::Close((err, _)) => { + let regex = regex::Regex::new( + r"Configured with an unreasonable our_to_self_delay \(\d+\) putting user funds at risks", + ) + .unwrap(); + assert!(regex.is_match(err.as_str())); + }, + _ => panic!("Unexpected event"), + } + } else { + assert!(false); + } + + // We test msg.to_self_delay <= config.their_to_self_delay is enforced in Chanel::accept_channel() + nodes[0].node.create_channel(node_b_id, 1000000, 1000000, 42, None, None).unwrap(); + let open_channel = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); + nodes[1].node.handle_open_channel(node_a_id, &open_channel); + + let mut accept_channel = + get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, node_a_id); + accept_channel.common_fields.to_self_delay = 200; + nodes[0].node.handle_accept_channel(node_b_id, &accept_channel); + let reason_msg; + if let MessageSendEvent::HandleError { ref action, .. } = + nodes[0].node.get_and_clear_pending_msg_events()[0] + { + match action { + &ErrorAction::SendErrorMessage { ref msg } => { + assert!(regex::Regex::new(r"They wanted our payments to be delayed by a needlessly long period\. Upper limit: \d+\. Actual: \d+").unwrap().is_match(msg.data.as_str())); + reason_msg = msg.data.clone(); + }, + _ => { + panic!(); + }, + } + } else { + panic!(); + } + let reason = ClosureReason::ProcessingError { err: reason_msg }; + check_closed_event!(nodes[0], 1, reason, [node_b_id], 1000000); + + // We test msg.to_self_delay <= config.their_to_self_delay is enforced in InboundV1Channel::new() + nodes[1].node.create_channel(node_a_id, 1000000, 1000000, 42, None, None).unwrap(); + let mut open_channel = get_event_msg!(nodes[1], MessageSendEvent::SendOpenChannel, node_a_id); + open_channel.common_fields.to_self_delay = 200; + if let Err(error) = InboundV1Channel::new( + &LowerBoundedFeeEstimator::new(&test_utils::TestFeeEstimator::new(253)), + &nodes[0].keys_manager, + &nodes[0].keys_manager, + node_b_id, + &nodes[0].node.channel_type_features(), + &nodes[1].node.init_features(), + &open_channel, + 0, + &high_their_to_self_config, + 0, + &nodes[0].logger, + /*is_0conf=*/ false, + ) { + match error { + ChannelError::Close((err, _)) => { + let regex = regex::Regex::new(r"They wanted our payments to be delayed by a needlessly long period\. Upper limit: \d+\. Actual: \d+").unwrap(); + assert!(regex.is_match(err.as_str())); + }, + _ => panic!("Unexpected event"), + } + } else { + assert!(false); + } +} + +#[xtest(feature = "_externalize_tests")] +pub fn test_manually_accept_inbound_channel_request() { + let mut manually_accept_conf = UserConfig::default(); + manually_accept_conf.manually_accept_inbound_channels = true; + manually_accept_conf.channel_handshake_config.minimum_depth = 1; + + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = + create_node_chanmgrs(2, &node_cfgs, &[None, Some(manually_accept_conf.clone())]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let node_a_id = nodes[0].node.get_our_node_id(); + let node_b_id = nodes[1].node.get_our_node_id(); + + nodes[0] + .node + .create_channel(node_b_id, 100000, 10001, 42, None, Some(manually_accept_conf)) + .unwrap(); + let res = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); + + nodes[1].node.handle_open_channel(node_a_id, &res); + + // Assert that `nodes[1]` has no `MessageSendEvent::SendAcceptChannel` in `msg_events` before + // accepting the inbound channel request. + assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty()); + + let config_overrides = ChannelConfigOverrides { + handshake_overrides: Some(ChannelHandshakeConfigUpdate { + max_inbound_htlc_value_in_flight_percent_of_channel: None, + htlc_minimum_msat: None, + minimum_depth: None, + to_self_delay: None, + max_accepted_htlcs: Some(3), + channel_reserve_proportional_millionths: None, + }), + update_overrides: Some(ChannelConfigUpdate { + forwarding_fee_proportional_millionths: None, + forwarding_fee_base_msat: Some(555), + cltv_expiry_delta: None, + max_dust_htlc_exposure_msat: None, + force_close_avoidance_max_fee_satoshis: None, + accept_underpaying_htlcs: None, + }), + }; + let events = nodes[1].node.get_and_clear_pending_events(); + match events[0] { + Event::OpenChannelRequest { temporary_channel_id, .. } => { + let config = Some(config_overrides); + nodes[1] + .node + .accept_inbound_channel(&temporary_channel_id, &node_a_id, 23, config) + .unwrap(); + }, + _ => panic!("Unexpected event"), + } + + let accept_msg_ev = nodes[1].node.get_and_clear_pending_msg_events(); + assert_eq!(accept_msg_ev.len(), 1); + + let ref accept_channel: AcceptChannel; + match accept_msg_ev[0] { + MessageSendEvent::SendAcceptChannel { ref node_id, ref msg } => { + assert_eq!(*node_id, node_a_id); + + // Assert overriden handshake parameter. + assert_eq!(msg.common_fields.max_accepted_htlcs, 3); + + accept_channel = msg; + }, + _ => panic!("Unexpected event"), + } + + // Continue channel opening process until channel update messages are sent. + nodes[0].node.handle_accept_channel(node_b_id, &accept_channel); + let (temp_channel_id, tx, funding_outpoint) = + create_funding_transaction(&nodes[0], &node_b_id, 100_000, 42); + nodes[0] + .node + .unsafe_manual_funding_transaction_generated(temp_channel_id, node_b_id, funding_outpoint) + .unwrap(); + check_added_monitors(&nodes[0], 0); + + let funding_created = get_event_msg!(nodes[0], MessageSendEvent::SendFundingCreated, node_b_id); + nodes[1].node.handle_funding_created(node_a_id, &funding_created); + check_added_monitors(&nodes[1], 1); + expect_channel_pending_event(&nodes[1], &node_a_id); + + let funding_signed = get_event_msg!(nodes[1], MessageSendEvent::SendFundingSigned, node_a_id); + nodes[0].node.handle_funding_signed(node_b_id, &funding_signed); + check_added_monitors(&nodes[0], 1); + let events = &nodes[0].node.get_and_clear_pending_events(); + assert_eq!(events.len(), 2); + match &events[0] { + crate::events::Event::FundingTxBroadcastSafe { funding_txo, .. } => { + assert_eq!(funding_txo.txid, funding_outpoint.txid); + assert_eq!(funding_txo.vout, funding_outpoint.index.into()); + }, + _ => panic!("Unexpected event"), + }; + match &events[1] { + crate::events::Event::ChannelPending { counterparty_node_id, .. } => { + assert_eq!(node_b_id, *counterparty_node_id); + }, + _ => panic!("Unexpected event"), + }; + + mine_transaction(&nodes[0], &tx); + mine_transaction(&nodes[1], &tx); + + let as_channel_ready = get_event_msg!(nodes[1], MessageSendEvent::SendChannelReady, node_a_id); + nodes[1].node.handle_channel_ready(node_a_id, &as_channel_ready); + let as_channel_ready = get_event_msg!(nodes[0], MessageSendEvent::SendChannelReady, node_b_id); + nodes[0].node.handle_channel_ready(node_b_id, &as_channel_ready); + + expect_channel_ready_event(&nodes[0], &node_b_id); + expect_channel_ready_event(&nodes[1], &node_a_id); + + // Assert that the overriden base fee surfaces in the channel update. + let channel_update = get_event_msg!(nodes[1], MessageSendEvent::SendChannelUpdate, node_a_id); + assert_eq!(channel_update.contents.fee_base_msat, 555); + + get_event_msg!(nodes[0], MessageSendEvent::SendChannelUpdate, node_b_id); +} + +#[xtest(feature = "_externalize_tests")] +pub fn test_manually_reject_inbound_channel_request() { + let mut manually_accept_conf = UserConfig::default(); + manually_accept_conf.manually_accept_inbound_channels = true; + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = + create_node_chanmgrs(2, &node_cfgs, &[None, Some(manually_accept_conf.clone())]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let node_a_id = nodes[0].node.get_our_node_id(); + let node_b_id = nodes[1].node.get_our_node_id(); + + nodes[0] + .node + .create_channel(node_b_id, 100000, 10001, 42, None, Some(manually_accept_conf)) + .unwrap(); + let res = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); + + nodes[1].node.handle_open_channel(node_a_id, &res); + + // Assert that `nodes[1]` has no `MessageSendEvent::SendAcceptChannel` in `msg_events` before + // rejecting the inbound channel request. + assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty()); + let err = "Channel force-closed".to_string(); + let events = nodes[1].node.get_and_clear_pending_events(); + match events[0] { + Event::OpenChannelRequest { temporary_channel_id, .. } => { + nodes[1] + .node + .force_close_broadcasting_latest_txn(&temporary_channel_id, &node_a_id, err) + .unwrap(); + }, + _ => panic!("Unexpected event"), + } + + let close_msg_ev = nodes[1].node.get_and_clear_pending_msg_events(); + assert_eq!(close_msg_ev.len(), 1); + + match close_msg_ev[0] { + MessageSendEvent::HandleError { ref node_id, .. } => { + assert_eq!(*node_id, node_a_id); + }, + _ => panic!("Unexpected event"), + } + + // There should be no more events to process, as the channel was never opened. + assert!(nodes[1].node.get_and_clear_pending_events().is_empty()); +} + +#[xtest(feature = "_externalize_tests")] +pub fn test_can_not_accept_inbound_channel_twice() { + let mut manually_accept_conf = UserConfig::default(); + manually_accept_conf.manually_accept_inbound_channels = true; + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = + create_node_chanmgrs(2, &node_cfgs, &[None, Some(manually_accept_conf.clone())]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let node_a_id = nodes[0].node.get_our_node_id(); + let node_b_id = nodes[1].node.get_our_node_id(); + + nodes[0] + .node + .create_channel(node_b_id, 100000, 10001, 42, None, Some(manually_accept_conf)) + .unwrap(); + let res = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); + + nodes[1].node.handle_open_channel(node_a_id, &res); + + // Assert that `nodes[1]` has no `MessageSendEvent::SendAcceptChannel` in `msg_events` before + // accepting the inbound channel request. + assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty()); + + let events = nodes[1].node.get_and_clear_pending_events(); + match events[0] { + Event::OpenChannelRequest { temporary_channel_id, .. } => { + nodes[1] + .node + .accept_inbound_channel(&temporary_channel_id, &node_a_id, 0, None) + .unwrap(); + let api_res = + nodes[1].node.accept_inbound_channel(&temporary_channel_id, &node_a_id, 0, None); + match api_res { + Err(APIError::APIMisuseError { err }) => { + assert_eq!(err, "No such channel awaiting to be accepted."); + }, + Ok(_) => panic!("Channel shouldn't be possible to be accepted twice"), + Err(e) => panic!("Unexpected Error {:?}", e), + } + }, + _ => panic!("Unexpected event"), + } + + // Ensure that the channel wasn't closed after attempting to accept it twice. + let accept_msg_ev = nodes[1].node.get_and_clear_pending_msg_events(); + assert_eq!(accept_msg_ev.len(), 1); + + match accept_msg_ev[0] { + MessageSendEvent::SendAcceptChannel { ref node_id, .. } => { + assert_eq!(*node_id, node_a_id); + }, + _ => panic!("Unexpected event"), + } +} + +#[xtest(feature = "_externalize_tests")] +pub fn test_can_not_accept_unknown_inbound_channel() { + let chanmon_cfg = create_chanmon_cfgs(2); + let node_cfg = create_node_cfgs(2, &chanmon_cfg); + let node_chanmgr = create_node_chanmgrs(2, &node_cfg, &[None, None]); + let nodes = create_network(2, &node_cfg, &node_chanmgr); + + let node_b_id = nodes[1].node.get_our_node_id(); + + let unknown_channel_id = ChannelId::new_zero(); + let api_res = nodes[0].node.accept_inbound_channel(&unknown_channel_id, &node_b_id, 0, None); + match api_res { + Err(APIError::APIMisuseError { err }) => { + assert_eq!(err, "No such channel awaiting to be accepted."); + }, + Ok(_) => panic!("It shouldn't be possible to accept an unkown channel"), + Err(e) => panic!("Unexpected Error: {:?}", e), + } +} + +#[xtest(feature = "_externalize_tests")] +pub fn test_duplicate_temporary_channel_id_from_different_peers() { + // Tests that we can accept two different `OpenChannel` requests with the same + // `temporary_channel_id`, as long as they are from different peers. + let chanmon_cfgs = create_chanmon_cfgs(3); + let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]); + let nodes = create_network(3, &node_cfgs, &node_chanmgrs); + + let node_a_id = nodes[0].node.get_our_node_id(); + let node_b_id = nodes[1].node.get_our_node_id(); + let node_c_id = nodes[2].node.get_our_node_id(); + + // Create an first channel channel + nodes[1].node.create_channel(node_a_id, 100000, 10001, 42, None, None).unwrap(); + let mut open_chan_msg_chan_1_0 = + get_event_msg!(nodes[1], MessageSendEvent::SendOpenChannel, node_a_id); + + // Create an second channel + nodes[2].node.create_channel(node_a_id, 100000, 10001, 43, None, None).unwrap(); + let mut open_chan_msg_chan_2_0 = + get_event_msg!(nodes[2], MessageSendEvent::SendOpenChannel, node_a_id); + + // Modify the `OpenChannel` from `nodes[2]` to `nodes[0]` to ensure that it uses the same + // `temporary_channel_id` as the `OpenChannel` from nodes[1] to nodes[0]. + open_chan_msg_chan_2_0.common_fields.temporary_channel_id = + open_chan_msg_chan_1_0.common_fields.temporary_channel_id; + + // Assert that `nodes[0]` can accept both `OpenChannel` requests, even though they use the same + // `temporary_channel_id` as they are from different peers. + nodes[0].node.handle_open_channel(node_b_id, &open_chan_msg_chan_1_0); + { + let events = nodes[0].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + match &events[0] { + MessageSendEvent::SendAcceptChannel { node_id, msg } => { + assert_eq!(node_id, &node_b_id); + assert_eq!( + msg.common_fields.temporary_channel_id, + open_chan_msg_chan_1_0.common_fields.temporary_channel_id + ); + }, + _ => panic!("Unexpected event"), + } + } + + nodes[0].node.handle_open_channel(node_c_id, &open_chan_msg_chan_2_0); + { + let events = nodes[0].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + match &events[0] { + MessageSendEvent::SendAcceptChannel { node_id, msg } => { + assert_eq!(node_id, &node_c_id); + assert_eq!( + msg.common_fields.temporary_channel_id, + open_chan_msg_chan_1_0.common_fields.temporary_channel_id + ); + }, + _ => panic!("Unexpected event"), + } + } +} + +#[xtest(feature = "_externalize_tests")] +pub fn test_duplicate_funding_err_in_funding() { + // Test that if we have a live channel with one peer, then another peer comes along and tries + // to create a second channel with the same txid we'll fail and not overwrite the + // outpoint_to_peer map in `ChannelManager`. + // + // This was previously broken. + let chanmon_cfgs = create_chanmon_cfgs(3); + let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]); + let nodes = create_network(3, &node_cfgs, &node_chanmgrs); + + let node_b_id = nodes[1].node.get_our_node_id(); + let node_c_id = nodes[2].node.get_our_node_id(); + + let (_, _, _, real_channel_id, funding_tx) = create_chan_between_nodes(&nodes[0], &nodes[1]); + let real_chan_funding_txo = + chain::transaction::OutPoint { txid: funding_tx.compute_txid(), index: 0 }; + assert_eq!(ChannelId::v1_from_funding_outpoint(real_chan_funding_txo), real_channel_id); + + nodes[2].node.create_channel(node_b_id, 100_000, 0, 42, None, None).unwrap(); + let mut open_chan_msg = get_event_msg!(nodes[2], MessageSendEvent::SendOpenChannel, node_b_id); + let node_c_temp_chan_id = open_chan_msg.common_fields.temporary_channel_id; + open_chan_msg.common_fields.temporary_channel_id = real_channel_id; + nodes[1].node.handle_open_channel(node_c_id, &open_chan_msg); + let mut accept_chan_msg = + get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, node_c_id); + accept_chan_msg.common_fields.temporary_channel_id = node_c_temp_chan_id; + nodes[2].node.handle_accept_channel(node_b_id, &accept_chan_msg); + + // Now that we have a second channel with the same funding txo, send a bogus funding message + // and let nodes[1] remove the inbound channel. + let (_, fund_tx, _) = create_funding_transaction(&nodes[2], &node_b_id, 100_000, 42); + + nodes[2].node.funding_transaction_generated(node_c_temp_chan_id, node_b_id, fund_tx).unwrap(); + + let mut funding_created_msg = + get_event_msg!(nodes[2], MessageSendEvent::SendFundingCreated, node_b_id); + funding_created_msg.temporary_channel_id = real_channel_id; + // Make the signature invalid by changing the funding output + funding_created_msg.funding_output_index += 10; + nodes[1].node.handle_funding_created(node_c_id, &funding_created_msg); + get_err_msg(&nodes[1], &node_c_id); + let err = "Invalid funding_created signature from peer".to_owned(); + let reason = ClosureReason::ProcessingError { err }; + let expected_closing = ExpectedCloseEvent::from_id_reason(real_channel_id, false, reason); + check_closed_events(&nodes[1], &[expected_closing]); +} + +#[xtest(feature = "_externalize_tests")] +pub fn test_duplicate_chan_id() { + // Test that if a given peer tries to open a channel with the same channel_id as one that is + // already open we reject it and keep the old channel. + // + // Previously, full_stack_target managed to figure out that if you tried to open two channels + // with the same funding output (ie post-funding channel_id), we'd create a monitor update for + // the existing channel when we detect the duplicate new channel, screwing up our monitor + // updating logic for the existing channel. + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let node_a_id = nodes[0].node.get_our_node_id(); + let node_b_id = nodes[1].node.get_our_node_id(); + + // Create an initial channel + nodes[0].node.create_channel(node_b_id, 100000, 10001, 42, None, None).unwrap(); + let mut open_chan_msg = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); + nodes[1].node.handle_open_channel(node_a_id, &open_chan_msg); + nodes[0].node.handle_accept_channel( + node_b_id, + &get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, node_a_id), + ); + + // Try to create a second channel with the same temporary_channel_id as the first and check + // that it is rejected. + nodes[1].node.handle_open_channel(node_a_id, &open_chan_msg); + { + let events = nodes[1].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + match events[0] { + MessageSendEvent::HandleError { + action: ErrorAction::SendErrorMessage { ref msg }, + node_id, + } => { + // Technically, at this point, nodes[1] would be justified in thinking both the + // first (valid) and second (invalid) channels are closed, given they both have + // the same non-temporary channel_id. However, currently we do not, so we just + // move forward with it. + assert_eq!(msg.channel_id, open_chan_msg.common_fields.temporary_channel_id); + assert_eq!(node_id, node_a_id); + }, + _ => panic!("Unexpected event"), + } + } + + // Move the first channel through the funding flow... + let (temp_channel_id, tx, _) = create_funding_transaction(&nodes[0], &node_b_id, 100000, 42); + + nodes[0].node.funding_transaction_generated(temp_channel_id, node_b_id, tx.clone()).unwrap(); + check_added_monitors(&nodes[0], 0); + + let mut funding_created_msg = + get_event_msg!(nodes[0], MessageSendEvent::SendFundingCreated, node_b_id); + let channel_id = ChannelId::v1_from_funding_txid( + funding_created_msg.funding_txid.as_byte_array(), + funding_created_msg.funding_output_index, + ); + + nodes[1].node.handle_funding_created(node_a_id, &funding_created_msg); + { + let mut added_monitors = nodes[1].chain_monitor.added_monitors.lock().unwrap(); + assert_eq!(added_monitors.len(), 1); + assert_eq!(added_monitors[0].0, channel_id); + added_monitors.clear(); + } + expect_channel_pending_event(&nodes[1], &node_a_id); + + let funding_signed_msg = + get_event_msg!(nodes[1], MessageSendEvent::SendFundingSigned, node_a_id); + + let funding_outpoint = crate::chain::transaction::OutPoint { + txid: funding_created_msg.funding_txid, + index: funding_created_msg.funding_output_index, + }; + let channel_id = ChannelId::v1_from_funding_outpoint(funding_outpoint); + + // Now we have the first channel past funding_created (ie it has a txid-based channel_id, not a + // temporary one). + + // First try to open a second channel with a temporary channel id equal to the txid-based one. + // Technically this is allowed by the spec, but we don't support it and there's little reason + // to. Still, it shouldn't cause any other issues. + open_chan_msg.common_fields.temporary_channel_id = channel_id; + nodes[1].node.handle_open_channel(node_a_id, &open_chan_msg); + { + let events = nodes[1].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + match events[0] { + MessageSendEvent::HandleError { + action: ErrorAction::SendErrorMessage { ref msg }, + node_id, + } => { + // Technically, at this point, nodes[1] would be justified in thinking both + // channels are closed, but currently we do not, so we just move forward with it. + assert_eq!(msg.channel_id, open_chan_msg.common_fields.temporary_channel_id); + assert_eq!(node_id, node_a_id); + }, + _ => panic!("Unexpected event"), + } + } + + // Now try to create a second channel which has a duplicate funding output. + nodes[0].node.create_channel(node_b_id, 100000, 10001, 42, None, None).unwrap(); + let open_chan_2_msg = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); + nodes[1].node.handle_open_channel(node_a_id, &open_chan_2_msg); + nodes[0].node.handle_accept_channel( + node_b_id, + &get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, node_a_id), + ); + create_funding_transaction(&nodes[0], &node_b_id, 100000, 42); // Get and check the FundingGenerationReady event + + let funding_created = { + let per_peer_state = nodes[0].node.per_peer_state.read().unwrap(); + let mut a_peer_state = per_peer_state.get(&node_b_id).unwrap().lock().unwrap(); + // Once we call `get_funding_created` the channel has a duplicate channel_id as + // another channel in the ChannelManager - an invalid state. Thus, we'd panic later when we + // try to create another channel. Instead, we drop the channel entirely here (leaving the + // channelmanager in a possibly nonsense state instead). + let chan_id = open_chan_2_msg.common_fields.temporary_channel_id; + let mut channel = a_peer_state.channel_by_id.remove(&chan_id).unwrap(); + + if let Some(mut chan) = channel.as_unfunded_outbound_v1_mut() { + let logger = test_utils::TestLogger::new(); + chan.get_funding_created(tx.clone(), funding_outpoint, false, &&logger) + .map_err(|_| ()) + .unwrap() + } else { + panic!("Unexpected Channel phase") + } + .unwrap() + }; + check_added_monitors(&nodes[0], 0); + nodes[1].node.handle_funding_created(node_a_id, &funding_created); + // At this point we'll look up if the channel_id is present and immediately fail the channel + // without trying to persist the `ChannelMonitor`. + check_added_monitors(&nodes[1], 0); + + let reason = ClosureReason::ProcessingError { + err: "Already had channel with the new channel_id".to_owned(), + }; + let close_event = + ExpectedCloseEvent::from_id_reason(funding_created.temporary_channel_id, false, reason); + check_closed_events(&nodes[1], &[close_event]); + + // ...still, nodes[1] will reject the duplicate channel. + { + let events = nodes[1].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + match events[0] { + MessageSendEvent::HandleError { + action: ErrorAction::SendErrorMessage { ref msg }, + node_id, + } => { + // Technically, at this point, nodes[1] would be justified in thinking both + // channels are closed, but currently we do not, so we just move forward with it. + assert_eq!(msg.channel_id, funding_created.temporary_channel_id); + assert_eq!(node_id, node_a_id); + }, + _ => panic!("Unexpected event"), + } + } + + // finally, finish creating the original channel and send a payment over it to make sure + // everything is functional. + nodes[0].node.handle_funding_signed(node_b_id, &funding_signed_msg); + { + let mut added_monitors = nodes[0].chain_monitor.added_monitors.lock().unwrap(); + assert_eq!(added_monitors.len(), 1); + assert_eq!(added_monitors[0].0, channel_id); + added_monitors.clear(); + } + expect_channel_pending_event(&nodes[0], &node_b_id); + + let events_4 = nodes[0].node.get_and_clear_pending_events(); + assert_eq!(events_4.len(), 0); + assert_eq!(nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().len(), 1); + assert_eq!(nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap()[0], tx); + + let (channel_ready, _) = + create_chan_between_nodes_with_value_confirm(&nodes[0], &nodes[1], &tx); + let (announcement, as_update, bs_update) = + create_chan_between_nodes_with_value_b(&nodes[0], &nodes[1], &channel_ready); + update_nodes_with_chan_announce(&nodes, 0, 1, &announcement, &as_update, &bs_update); + + send_payment(&nodes[0], &[&nodes[1]], 8000000); +} + +#[xtest(feature = "_externalize_tests")] +pub fn test_invalid_funding_tx() { + // Test that we properly handle invalid funding transactions sent to us from a peer. + // + // Previously, all other major lightning implementations had failed to properly sanitize + // funding transactions from their counterparties, leading to a multi-implementation critical + // security vulnerability (though we always sanitized properly, we've previously had + // un-released crashes in the sanitization process). + // + // Further, if the funding transaction is consensus-valid, confirms, and is later spent, we'd + // previously have crashed in `ChannelMonitor` even though we closed the channel as bogus and + // gave up on it. We test this here by generating such a transaction. + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let node_a_id = nodes[0].node.get_our_node_id(); + let node_b_id = nodes[1].node.get_our_node_id(); + + nodes[0].node.create_channel(node_b_id, 100_000, 10_000, 42, None, None).unwrap(); + nodes[1].node.handle_open_channel( + node_a_id, + &get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id), + ); + nodes[0].node.handle_accept_channel( + node_b_id, + &get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, node_a_id), + ); + + let (temporary_channel_id, mut tx, _) = + create_funding_transaction(&nodes[0], &node_b_id, 100_000, 42); + + // Create a witness program which can be spent by a 4-empty-stack-elements witness and which is + // 136 bytes long. This matches our "accepted HTLC preimage spend" matching, previously causing + // a panic as we'd try to extract a 32 byte preimage from a witness element without checking + // its length. + let mut wit_program: Vec = + channelmonitor::deliberately_bogus_accepted_htlc_witness_program(); + let wit_program_script: ScriptBuf = wit_program.into(); + for output in tx.output.iter_mut() { + // Make the confirmed funding transaction have a bogus script_pubkey + output.script_pubkey = ScriptBuf::new_p2wsh(&wit_program_script.wscript_hash()); + } + + nodes[0] + .node + .funding_transaction_generated_unchecked(temporary_channel_id, node_b_id, tx.clone(), 0) + .unwrap(); + nodes[1].node.handle_funding_created( + node_a_id, + &get_event_msg!(nodes[0], MessageSendEvent::SendFundingCreated, node_b_id), + ); + check_added_monitors(&nodes[1], 1); + expect_channel_pending_event(&nodes[1], &node_a_id); + + nodes[0].node.handle_funding_signed( + node_b_id, + &get_event_msg!(nodes[1], MessageSendEvent::SendFundingSigned, node_a_id), + ); + check_added_monitors(&nodes[0], 1); + expect_channel_pending_event(&nodes[0], &node_b_id); + + let events_1 = nodes[0].node.get_and_clear_pending_events(); + assert_eq!(events_1.len(), 0); + + assert_eq!(nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().len(), 1); + assert_eq!(nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap()[0], tx); + nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().clear(); + + let expected_err = "funding tx had wrong script/value or output index"; + confirm_transaction_at(&nodes[1], &tx, 1); + + let reason = ClosureReason::ProcessingError { err: expected_err.to_string() }; + check_closed_event!(nodes[1], 1, reason, [node_a_id], 100000); + + check_added_monitors(&nodes[1], 1); + let events_2 = nodes[1].node.get_and_clear_pending_msg_events(); + assert_eq!(events_2.len(), 1); + if let MessageSendEvent::HandleError { node_id, action } = &events_2[0] { + assert_eq!(*node_id, node_a_id); + if let msgs::ErrorAction::SendErrorMessage { msg } = action { + assert_eq!( + msg.data, + "Channel closed because of an exception: ".to_owned() + expected_err + ); + } else { + panic!(); + } + } else { + panic!(); + } + assert_eq!(nodes[1].node.list_channels().len(), 0); + + // Now confirm a spend of the (bogus) funding transaction. As long as the witness is 5 elements + // long the ChannelMonitor will try to read 32 bytes from the second-to-last element, panicing + // as its not 32 bytes long. + let mut spend_tx = Transaction { + version: Version::TWO, + lock_time: LockTime::ZERO, + input: tx + .output + .iter() + .enumerate() + .map(|(idx, _)| TxIn { + previous_output: BitcoinOutPoint { txid: tx.compute_txid(), vout: idx as u32 }, + script_sig: ScriptBuf::new(), + sequence: Sequence::ENABLE_RBF_NO_LOCKTIME, + witness: Witness::from_slice( + &channelmonitor::deliberately_bogus_accepted_htlc_witness(), + ), + }) + .collect(), + output: vec![TxOut { value: Amount::from_sat(1000), script_pubkey: ScriptBuf::new() }], + }; + check_spends!(spend_tx, tx); + mine_transaction(&nodes[1], &spend_tx); +} + +#[xtest(feature = "_externalize_tests")] +pub fn test_coinbase_funding_tx() { + // Miners are able to fund channels directly from coinbase transactions, however + // by consensus rules, outputs of a coinbase transaction are encumbered by a 100 + // block maturity timelock. To ensure that a (non-0conf) channel like this is enforceable + // on-chain, the minimum depth is updated to 100 blocks for coinbase funding transactions. + // + // Note that 0conf channels with coinbase funding transactions are unaffected and are + // immediately operational after opening. + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let node_a_id = nodes[0].node.get_our_node_id(); + let node_b_id = nodes[1].node.get_our_node_id(); + + nodes[0].node.create_channel(node_b_id, 100000, 10001, 42, None, None).unwrap(); + let open_channel = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); + + nodes[1].node.handle_open_channel(node_a_id, &open_channel); + let accept_channel = get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, node_a_id); + + nodes[0].node.handle_accept_channel(node_b_id, &accept_channel); + + // Create the coinbase funding transaction. + let (channel_id, tx, _) = + create_coinbase_funding_transaction(&nodes[0], &node_b_id, 100000, 42); + + nodes[0].node.funding_transaction_generated(channel_id, node_b_id, tx.clone()).unwrap(); + check_added_monitors(&nodes[0], 0); + let funding_created = get_event_msg!(nodes[0], MessageSendEvent::SendFundingCreated, node_b_id); + + nodes[1].node.handle_funding_created(node_a_id, &funding_created); + check_added_monitors(&nodes[1], 1); + expect_channel_pending_event(&nodes[1], &node_a_id); + + let funding_signed = get_event_msg!(nodes[1], MessageSendEvent::SendFundingSigned, node_a_id); + + nodes[0].node.handle_funding_signed(node_b_id, &funding_signed); + check_added_monitors(&nodes[0], 1); + + expect_channel_pending_event(&nodes[0], &node_b_id); + assert!(nodes[0].node.get_and_clear_pending_events().is_empty()); + + // Starting at height 0, we "confirm" the coinbase at height 1. + confirm_transaction_at(&nodes[0], &tx, 1); + // We connect 98 more blocks to have 99 confirmations for the coinbase transaction. + connect_blocks(&nodes[0], COINBASE_MATURITY - 2); + // Check that we have no pending message events (we have not queued a `channel_ready` yet). + assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty()); + // Now connect one more block which results in 100 confirmations of the coinbase transaction. + connect_blocks(&nodes[0], 1); + // There should now be a `channel_ready` which can be handled. + let _ = &nodes[1].node.handle_channel_ready( + node_a_id, + &get_event_msg!(&nodes[0], MessageSendEvent::SendChannelReady, node_b_id), + ); + + confirm_transaction_at(&nodes[1], &tx, 1); + connect_blocks(&nodes[1], COINBASE_MATURITY - 2); + assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty()); + connect_blocks(&nodes[1], 1); + expect_channel_ready_event(&nodes[1], &node_a_id); + create_chan_between_nodes_with_value_confirm_second(&nodes[0], &nodes[1]); +} + +#[xtest(feature = "_externalize_tests")] +pub fn test_non_final_funding_tx() { + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let node_a_id = nodes[0].node.get_our_node_id(); + let node_b_id = nodes[1].node.get_our_node_id(); + + let temp_channel_id = + nodes[0].node.create_channel(node_b_id, 100_000, 0, 42, None, None).unwrap(); + let open_channel_message = + get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); + nodes[1].node.handle_open_channel(node_a_id, &open_channel_message); + let accept_channel_message = + get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, node_a_id); + nodes[0].node.handle_accept_channel(node_b_id, &accept_channel_message); + + let best_height = nodes[0].node.best_block.read().unwrap().height; + + let chan_id = *nodes[0].network_chan_count.borrow(); + let events = nodes[0].node.get_and_clear_pending_events(); + let input = TxIn { + previous_output: BitcoinOutPoint::null(), + script_sig: bitcoin::ScriptBuf::new(), + sequence: Sequence(1), + witness: Witness::from_slice(&[&[1]]), + }; + assert_eq!(events.len(), 1); + let mut tx = match events[0] { + Event::FundingGenerationReady { ref channel_value_satoshis, ref output_script, .. } => { + // Timelock the transaction _beyond_ the best client height + 1. + Transaction { + version: Version(chan_id as i32), + lock_time: LockTime::from_height(best_height + 2).unwrap(), + input: vec![input], + output: vec![TxOut { + value: Amount::from_sat(*channel_value_satoshis), + script_pubkey: output_script.clone(), + }], + } + }, + _ => panic!("Unexpected event"), + }; + // Transaction should fail as it's evaluated as non-final for propagation. + match nodes[0].node.funding_transaction_generated(temp_channel_id, node_b_id, tx.clone()) { + Err(APIError::APIMisuseError { err }) => { + assert_eq!(format!("Funding transaction absolute timelock is non-final"), err); + }, + _ => panic!(), + } + let err = "Error in transaction funding: Misuse error: Funding transaction absolute timelock is non-final"; + let reason = ClosureReason::ProcessingError { err: err.to_owned() }; + let event = ExpectedCloseEvent::from_id_reason(temp_channel_id, false, reason); + check_closed_events(&nodes[0], &[event]); + assert_eq!(get_err_msg(&nodes[0], &node_b_id).data, err); +} + +#[xtest(feature = "_externalize_tests")] +pub fn test_non_final_funding_tx_within_headroom() { + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let node_a_id = nodes[0].node.get_our_node_id(); + let node_b_id = nodes[1].node.get_our_node_id(); + + let temp_channel_id = + nodes[0].node.create_channel(node_b_id, 100_000, 0, 42, None, None).unwrap(); + let open_channel_message = + get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); + nodes[1].node.handle_open_channel(node_a_id, &open_channel_message); + let accept_channel_message = + get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, node_a_id); + nodes[0].node.handle_accept_channel(node_b_id, &accept_channel_message); + + let best_height = nodes[0].node.best_block.read().unwrap().height; + + let chan_id = *nodes[0].network_chan_count.borrow(); + let events = nodes[0].node.get_and_clear_pending_events(); + let input = TxIn { + previous_output: BitcoinOutPoint::null(), + script_sig: bitcoin::ScriptBuf::new(), + sequence: Sequence(1), + witness: Witness::from_slice(&[[1]]), + }; + assert_eq!(events.len(), 1); + let mut tx = match events[0] { + Event::FundingGenerationReady { ref channel_value_satoshis, ref output_script, .. } => { + // Timelock the transaction within a +1 headroom from the best block. + Transaction { + version: Version(chan_id as i32), + lock_time: LockTime::from_consensus(best_height + 1), + input: vec![input], + output: vec![TxOut { + value: Amount::from_sat(*channel_value_satoshis), + script_pubkey: output_script.clone(), + }], + } + }, + _ => panic!("Unexpected event"), + }; + + // Transaction should be accepted if it's in a +1 headroom from best block. + nodes[0].node.funding_transaction_generated(temp_channel_id, node_b_id, tx.clone()).unwrap(); + get_event_msg!(nodes[0], MessageSendEvent::SendFundingCreated, node_b_id); +} + +#[xtest(feature = "_externalize_tests")] +pub fn test_channel_close_when_not_timely_accepted() { + // Create network of two nodes + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let node_a_id = nodes[0].node.get_our_node_id(); + let node_b_id = nodes[1].node.get_our_node_id(); + + // Simulate peer-disconnects mid-handshake + // The channel is initiated from the node 0 side, + // but the nodes disconnect before node 1 could send accept channel + let create_chan_id = + nodes[0].node.create_channel(node_b_id, 100000, 10001, 42, None, None).unwrap(); + let open_channel_msg = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); + assert_eq!(open_channel_msg.common_fields.temporary_channel_id, create_chan_id); + + nodes[0].node.peer_disconnected(node_b_id); + nodes[1].node.peer_disconnected(node_a_id); + + // Make sure that we have not removed the OutboundV1Channel from node[0] immediately. + assert_eq!(nodes[0].node.list_channels().len(), 1); + + // Since channel was inbound from node[1] perspective, it should have been dropped immediately. + assert_eq!(nodes[1].node.list_channels().len(), 0); + + // In the meantime, some time passes. + for _ in 0..UNFUNDED_CHANNEL_AGE_LIMIT_TICKS { + nodes[0].node.timer_tick_occurred(); + } + + // Since we disconnected from peer and did not connect back within time, + // we should have forced-closed the channel by now. + let reason = ClosureReason::FundingTimedOut; + check_closed_event!(nodes[0], 1, reason, [node_b_id], 100000); + assert_eq!(nodes[0].node.list_channels().len(), 0); + + { + // Since accept channel message was never received + // The channel should be forced close by now from node 0 side + // and the peer removed from per_peer_state + let node_0_per_peer_state = nodes[0].node.per_peer_state.read().unwrap(); + assert_eq!(node_0_per_peer_state.len(), 0); + } +} + +#[xtest(feature = "_externalize_tests")] +pub fn test_rebroadcast_open_channel_when_reconnect_mid_handshake() { + // Create network of two nodes + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let node_a_id = nodes[0].node.get_our_node_id(); + let node_b_id = nodes[1].node.get_our_node_id(); + + // Simulate peer-disconnects mid-handshake + // The channel is initiated from the node 0 side, + // but the nodes disconnect before node 1 could send accept channel + let create_chan_id = + nodes[0].node.create_channel(node_b_id, 100000, 10001, 42, None, None).unwrap(); + let open_channel_msg = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); + assert_eq!(open_channel_msg.common_fields.temporary_channel_id, create_chan_id); + + nodes[0].node.peer_disconnected(node_b_id); + nodes[1].node.peer_disconnected(node_a_id); + + // Make sure that we have not removed the OutboundV1Channel from node[0] immediately. + assert_eq!(nodes[0].node.list_channels().len(), 1); + + // Since channel was inbound from node[1] perspective, it should have been immediately dropped. + assert_eq!(nodes[1].node.list_channels().len(), 0); + + // The peers now reconnect + let init_msg = msgs::Init { + features: nodes[0].node.init_features(), + networks: None, + remote_network_address: None, + }; + nodes[0].node.peer_connected(node_b_id, &init_msg, true).unwrap(); + nodes[1].node.peer_connected(node_a_id, &init_msg, false).unwrap(); + + // Make sure the SendOpenChannel message is added to node_0 pending message events + let msg_events = nodes[0].node.get_and_clear_pending_msg_events(); + assert_eq!(msg_events.len(), 1); + match &msg_events[0] { + MessageSendEvent::SendOpenChannel { msg, .. } => assert_eq!(msg, &open_channel_msg), + _ => panic!("Unexpected message."), + } +} + +#[xtest(feature = "_externalize_tests")] +pub fn test_batch_channel_open() { + let chanmon_cfgs = create_chanmon_cfgs(3); + let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]); + let nodes = create_network(3, &node_cfgs, &node_chanmgrs); + + let node_a_id = nodes[0].node.get_our_node_id(); + let node_b_id = nodes[1].node.get_our_node_id(); + let node_c_id = nodes[2].node.get_our_node_id(); + + // Initiate channel opening and create the batch channel funding transaction. + let (tx, funding_created_msgs) = create_batch_channel_funding( + &nodes[0], + &[(&nodes[1], 100_000, 0, 42, None), (&nodes[2], 200_000, 0, 43, None)], + ); + + // Go through the funding_created and funding_signed flow with node 1. + nodes[1].node.handle_funding_created(node_a_id, &funding_created_msgs[0]); + check_added_monitors(&nodes[1], 1); + expect_channel_pending_event(&nodes[1], &node_a_id); + + let funding_signed_msg = + get_event_msg!(nodes[1], MessageSendEvent::SendFundingSigned, node_a_id); + nodes[0].node.handle_funding_signed(node_b_id, &funding_signed_msg); + check_added_monitors(&nodes[0], 1); + + // The transaction should not have been broadcast before all channels are ready. + assert_eq!(nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().len(), 0); + + // Go through the funding_created and funding_signed flow with node 2. + nodes[2].node.handle_funding_created(node_a_id, &funding_created_msgs[1]); + check_added_monitors(&nodes[2], 1); + expect_channel_pending_event(&nodes[2], &node_a_id); + + let funding_signed_msg = + get_event_msg!(nodes[2], MessageSendEvent::SendFundingSigned, node_a_id); + chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::InProgress); + nodes[0].node.handle_funding_signed(node_c_id, &funding_signed_msg); + check_added_monitors(&nodes[0], 1); + + // The transaction should not have been broadcast before persisting all monitors has been + // completed. + assert_eq!(nodes[0].tx_broadcaster.txn_broadcast().len(), 0); + assert_eq!(nodes[0].node.get_and_clear_pending_events().len(), 0); + + // Complete the persistence of the monitor. + nodes[0].chain_monitor.complete_sole_pending_chan_update(&ChannelId::v1_from_funding_outpoint( + OutPoint { txid: tx.compute_txid(), index: 1 }, + )); + let events = nodes[0].node.get_and_clear_pending_events(); + + // The transaction should only have been broadcast now. + let broadcasted_txs = nodes[0].tx_broadcaster.txn_broadcast(); + assert_eq!(broadcasted_txs.len(), 1); + assert_eq!(broadcasted_txs[0], tx); + + assert_eq!(events.len(), 2); + assert!(events.iter().any(|e| matches!( + *e, + crate::events::Event::ChannelPending { + ref counterparty_node_id, + .. + } if counterparty_node_id == &node_b_id, + ))); + assert!(events.iter().any(|e| matches!( + *e, + crate::events::Event::ChannelPending { + ref counterparty_node_id, + .. + } if counterparty_node_id == &node_c_id, + ))); +} + +#[xtest(feature = "_externalize_tests")] +pub fn test_close_in_funding_batch() { + // This test ensures that if one of the channels + // in the batch closes, the complete batch will close. + let chanmon_cfgs = create_chanmon_cfgs(3); + let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]); + let nodes = create_network(3, &node_cfgs, &node_chanmgrs); + + let node_a_id = nodes[0].node.get_our_node_id(); + let node_b_id = nodes[1].node.get_our_node_id(); + + // Initiate channel opening and create the batch channel funding transaction. + let (tx, funding_created_msgs) = create_batch_channel_funding( + &nodes[0], + &[(&nodes[1], 100_000, 0, 42, None), (&nodes[2], 200_000, 0, 43, None)], + ); + + // Go through the funding_created and funding_signed flow with node 1. + nodes[1].node.handle_funding_created(node_a_id, &funding_created_msgs[0]); + check_added_monitors(&nodes[1], 1); + expect_channel_pending_event(&nodes[1], &node_a_id); + + let funding_signed_msg = + get_event_msg!(nodes[1], MessageSendEvent::SendFundingSigned, node_a_id); + nodes[0].node.handle_funding_signed(node_b_id, &funding_signed_msg); + check_added_monitors(&nodes[0], 1); + + // The transaction should not have been broadcast before all channels are ready. + assert_eq!(nodes[0].tx_broadcaster.txn_broadcast().len(), 0); + + // Force-close the channel for which we've completed the initial monitor. + let funding_txo_1 = OutPoint { txid: tx.compute_txid(), index: 0 }; + let funding_txo_2 = OutPoint { txid: tx.compute_txid(), index: 1 }; + let channel_id_1 = ChannelId::v1_from_funding_outpoint(funding_txo_1); + let channel_id_2 = ChannelId::v1_from_funding_outpoint(funding_txo_2); + let err = "Channel force-closed".to_string(); + nodes[0].node.force_close_broadcasting_latest_txn(&channel_id_1, &node_b_id, err).unwrap(); + + // The monitor should become closed. + check_added_monitors(&nodes[0], 1); + { + let mut monitor_updates = nodes[0].chain_monitor.monitor_updates.lock().unwrap(); + let monitor_updates_1 = monitor_updates.get(&channel_id_1).unwrap(); + assert_eq!(monitor_updates_1.len(), 1); + assert_eq!(monitor_updates_1[0].updates.len(), 1); + assert!(matches!( + monitor_updates_1[0].updates[0], + ChannelMonitorUpdateStep::ChannelForceClosed { .. } + )); + } + + let msg_events = nodes[0].node.get_and_clear_pending_msg_events(); + match msg_events[0] { + MessageSendEvent::HandleError { .. } => (), + _ => panic!("Unexpected message."), + } + + // Because the funding was never broadcasted, we should never bother to broadcast the + // commitment transactions either. + let broadcasted_txs = nodes[0].tx_broadcaster.txn_broadcast(); + assert_eq!(broadcasted_txs.len(), 0); + + // All channels in the batch should close immediately. + check_closed_events( + &nodes[0], + &[ + ExpectedCloseEvent { + channel_id: Some(channel_id_1), + discard_funding: true, + channel_funding_txo: Some(funding_txo_1), + user_channel_id: Some(42), + ..Default::default() + }, + ExpectedCloseEvent { + channel_id: Some(channel_id_2), + discard_funding: true, + channel_funding_txo: Some(funding_txo_2), + user_channel_id: Some(43), + ..Default::default() + }, + ], + ); + + // Ensure the channels don't exist anymore. + assert!(nodes[0].node.list_channels().is_empty()); +} + +#[xtest(feature = "_externalize_tests")] +pub fn test_batch_funding_close_after_funding_signed() { + let chanmon_cfgs = create_chanmon_cfgs(3); + let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]); + let nodes = create_network(3, &node_cfgs, &node_chanmgrs); + + let node_a_id = nodes[0].node.get_our_node_id(); + let node_b_id = nodes[1].node.get_our_node_id(); + let node_c_id = nodes[2].node.get_our_node_id(); + + // Initiate channel opening and create the batch channel funding transaction. + let (tx, funding_created_msgs) = create_batch_channel_funding( + &nodes[0], + &[(&nodes[1], 100_000, 0, 42, None), (&nodes[2], 200_000, 0, 43, None)], + ); + + // Go through the funding_created and funding_signed flow with node 1. + nodes[1].node.handle_funding_created(node_a_id, &funding_created_msgs[0]); + check_added_monitors(&nodes[1], 1); + expect_channel_pending_event(&nodes[1], &node_a_id); + + let funding_signed_msg = + get_event_msg!(nodes[1], MessageSendEvent::SendFundingSigned, node_a_id); + nodes[0].node.handle_funding_signed(node_b_id, &funding_signed_msg); + check_added_monitors(&nodes[0], 1); + + // Go through the funding_created and funding_signed flow with node 2. + nodes[2].node.handle_funding_created(node_a_id, &funding_created_msgs[1]); + check_added_monitors(&nodes[2], 1); + expect_channel_pending_event(&nodes[2], &node_a_id); + + let funding_signed_msg = + get_event_msg!(nodes[2], MessageSendEvent::SendFundingSigned, node_a_id); + chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::InProgress); + nodes[0].node.handle_funding_signed(node_c_id, &funding_signed_msg); + check_added_monitors(&nodes[0], 1); + + // The transaction should not have been broadcast before all channels are ready. + assert_eq!(nodes[0].tx_broadcaster.txn_broadcast().len(), 0); + + // Force-close the channel for which we've completed the initial monitor. + let funding_txo_1 = OutPoint { txid: tx.compute_txid(), index: 0 }; + let funding_txo_2 = OutPoint { txid: tx.compute_txid(), index: 1 }; + let channel_id_1 = ChannelId::v1_from_funding_outpoint(funding_txo_1); + let channel_id_2 = ChannelId::v1_from_funding_outpoint(funding_txo_2); + let err = "Channel force-closed".to_string(); + nodes[0].node.force_close_broadcasting_latest_txn(&channel_id_1, &node_b_id, err).unwrap(); + check_added_monitors(&nodes[0], 2); + { + let mut monitor_updates = nodes[0].chain_monitor.monitor_updates.lock().unwrap(); + let monitor_updates_1 = monitor_updates.get(&channel_id_1).unwrap(); + assert_eq!(monitor_updates_1.len(), 1); + assert_eq!(monitor_updates_1[0].updates.len(), 1); + assert!(matches!( + monitor_updates_1[0].updates[0], + ChannelMonitorUpdateStep::ChannelForceClosed { .. } + )); + let monitor_updates_2 = monitor_updates.get(&channel_id_2).unwrap(); + assert_eq!(monitor_updates_2.len(), 1); + assert_eq!(monitor_updates_2[0].updates.len(), 1); + assert!(matches!( + monitor_updates_2[0].updates[0], + ChannelMonitorUpdateStep::ChannelForceClosed { .. } + )); + } + let msg_events = nodes[0].node.get_and_clear_pending_msg_events(); + match msg_events[0] { + MessageSendEvent::HandleError { .. } => (), + _ => panic!("Unexpected message."), + } + + // Because the funding was never broadcasted, we should never bother to broadcast the + // commitment transactions either. + let broadcasted_txs = nodes[0].tx_broadcaster.txn_broadcast(); + assert_eq!(broadcasted_txs.len(), 0); + + // All channels in the batch should close immediately. + check_closed_events( + &nodes[0], + &[ + ExpectedCloseEvent { + channel_id: Some(channel_id_1), + discard_funding: true, + channel_funding_txo: Some(funding_txo_1), + user_channel_id: Some(42), + ..Default::default() + }, + ExpectedCloseEvent { + channel_id: Some(channel_id_2), + discard_funding: true, + channel_funding_txo: Some(funding_txo_2), + user_channel_id: Some(43), + ..Default::default() + }, + ], + ); + + // Ensure the channels don't exist anymore. + assert!(nodes[0].node.list_channels().is_empty()); +} + +#[xtest(feature = "_externalize_tests")] +pub fn test_funding_and_commitment_tx_confirm_same_block() { + // Tests that a node will forget the channel (when it only requires 1 confirmation) if the + // funding and commitment transaction confirm in the same block. + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let mut min_depth_1_block_cfg = test_default_channel_config(); + min_depth_1_block_cfg.channel_handshake_config.minimum_depth = 1; + let node_chanmgrs = create_node_chanmgrs( + 2, + &node_cfgs, + &[Some(min_depth_1_block_cfg.clone()), Some(min_depth_1_block_cfg)], + ); + let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let node_a_id = nodes[0].node.get_our_node_id(); + let node_b_id = nodes[1].node.get_our_node_id(); + + let funding_tx = create_chan_between_nodes_with_value_init(&nodes[0], &nodes[1], 1_000_000, 0); + let chan_id = ChannelId::v1_from_funding_outpoint(chain::transaction::OutPoint { + txid: funding_tx.compute_txid(), + index: 0, + }); + + assert_eq!(nodes[0].node.list_channels().len(), 1); + assert_eq!(nodes[1].node.list_channels().len(), 1); + + let commitment_tx = { + let mon = get_monitor!(nodes[0], chan_id); + let mut txn = mon.unsafe_get_latest_holder_commitment_txn(&nodes[0].logger); + assert_eq!(txn.len(), 1); + txn.pop().unwrap() + }; + + mine_transactions(&nodes[0], &[&funding_tx, &commitment_tx]); + mine_transactions(&nodes[1], &[&funding_tx, &commitment_tx]); + + let check_msg_events = |node: &Node| { + let mut msg_events = node.node.get_and_clear_pending_msg_events(); + assert_eq!(msg_events.len(), 3, "{msg_events:?}"); + if let MessageSendEvent::SendChannelReady { .. } = msg_events.remove(0) { + } else { + panic!(); + } + if let MessageSendEvent::HandleError { + action: msgs::ErrorAction::SendErrorMessage { .. }, + node_id: _, + } = msg_events.remove(0) + { + } else { + panic!(); + } + if let MessageSendEvent::BroadcastChannelUpdate { ref msg } = msg_events.remove(0) { + assert_eq!(msg.contents.channel_flags & 2, 2); + } else { + panic!(); + } + }; + check_msg_events(&nodes[0]); + check_added_monitors(&nodes[0], 1); + let reason = ClosureReason::CommitmentTxConfirmed; + check_closed_event(&nodes[0], 1, reason, false, &[node_b_id], 1_000_000); + + check_msg_events(&nodes[1]); + check_added_monitors(&nodes[1], 1); + let reason = ClosureReason::CommitmentTxConfirmed; + check_closed_event(&nodes[1], 1, reason, false, &[node_a_id], 1_000_000); + + assert!(nodes[0].node.list_channels().is_empty()); + assert!(nodes[1].node.list_channels().is_empty()); +} + +#[xtest(feature = "_externalize_tests")] +pub fn test_accept_inbound_channel_errors_queued() { + // For manually accepted inbound channels, tests that a close error is correctly handled + // and the channel fails for the initiator. + let mut config0 = test_default_channel_config(); + let mut config1 = config0.clone(); + config1.channel_handshake_limits.their_to_self_delay = 1000; + config1.manually_accept_inbound_channels = true; + config0.channel_handshake_config.our_to_self_delay = 2000; + + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config0), Some(config1)]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let node_a_id = nodes[0].node.get_our_node_id(); + let node_b_id = nodes[1].node.get_our_node_id(); + + nodes[0].node.create_channel(node_b_id, 100_000, 0, 42, None, None).unwrap(); + let open_channel_msg = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); + + nodes[1].node.handle_open_channel(node_a_id, &open_channel_msg); + let events = nodes[1].node.get_and_clear_pending_events(); + match events[0] { + Event::OpenChannelRequest { temporary_channel_id, .. } => { + match nodes[1].node.accept_inbound_channel(&temporary_channel_id, &node_a_id, 23, None) + { + Err(APIError::ChannelUnavailable { err: _ }) => (), + _ => panic!(), + } + }, + _ => panic!("Unexpected event"), + } + assert_eq!( + get_err_msg(&nodes[1], &node_a_id).channel_id, + open_channel_msg.common_fields.temporary_channel_id + ); +} + +#[xtest(feature = "_externalize_tests")] +pub fn test_manual_funding_abandon() { + let mut cfg = UserConfig::default(); + cfg.channel_handshake_config.minimum_depth = 1; + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(cfg.clone()), Some(cfg)]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let node_a_id = nodes[0].node.get_our_node_id(); + let node_b_id = nodes[1].node.get_our_node_id(); + + assert!(nodes[0].node.create_channel(node_b_id, 100_000, 0, 42, None, None).is_ok()); + let open_channel = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); + + nodes[1].node.handle_open_channel(node_a_id, &open_channel); + let accept_channel = get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, node_a_id); + + nodes[0].node.handle_accept_channel(node_b_id, &accept_channel); + let (temp_channel_id, _tx, funding_outpoint) = + create_funding_transaction(&nodes[0], &node_b_id, 100_000, 42); + nodes[0] + .node + .unsafe_manual_funding_transaction_generated(temp_channel_id, node_b_id, funding_outpoint) + .unwrap(); + check_added_monitors(&nodes[0], 0); + + let funding_created = get_event_msg!(nodes[0], MessageSendEvent::SendFundingCreated, node_b_id); + nodes[1].node.handle_funding_created(node_a_id, &funding_created); + check_added_monitors(&nodes[1], 1); + expect_channel_pending_event(&nodes[1], &node_a_id); + + let funding_signed = get_event_msg!(nodes[1], MessageSendEvent::SendFundingSigned, node_a_id); + let err = msgs::ErrorMessage { channel_id: funding_signed.channel_id, data: "".to_string() }; + nodes[0].node.handle_error(node_b_id, &err); + + let close_events = nodes[0].node.get_and_clear_pending_events(); + assert_eq!(close_events.len(), 2); + assert!(close_events.iter().any(|ev| matches!(ev, Event::ChannelClosed { .. }))); + assert!(close_events.iter().any(|ev| match ev { + Event::DiscardFunding { channel_id, funding_info: FundingInfo::OutPoint { outpoint } } => { + assert_eq!(*channel_id, err.channel_id); + assert_eq!(*outpoint, funding_outpoint); + true + }, + _ => false, + })); +} + +#[xtest(feature = "_externalize_tests")] +pub fn test_funding_signed_event() { + let mut cfg = UserConfig::default(); + cfg.channel_handshake_config.minimum_depth = 1; + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(cfg.clone()), Some(cfg)]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let node_a_id = nodes[0].node.get_our_node_id(); + let node_b_id = nodes[1].node.get_our_node_id(); + + assert!(nodes[0].node.create_channel(node_b_id, 100_000, 0, 42, None, None).is_ok()); + let open_channel = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); + + nodes[1].node.handle_open_channel(node_a_id, &open_channel); + let accept_channel = get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, node_a_id); + + nodes[0].node.handle_accept_channel(node_b_id, &accept_channel); + let (temp_channel_id, tx, funding_outpoint) = + create_funding_transaction(&nodes[0], &node_b_id, 100_000, 42); + nodes[0] + .node + .unsafe_manual_funding_transaction_generated(temp_channel_id, node_b_id, funding_outpoint) + .unwrap(); + check_added_monitors(&nodes[0], 0); + + let funding_created = get_event_msg!(nodes[0], MessageSendEvent::SendFundingCreated, node_b_id); + nodes[1].node.handle_funding_created(node_a_id, &funding_created); + check_added_monitors(&nodes[1], 1); + expect_channel_pending_event(&nodes[1], &node_a_id); + + let funding_signed = get_event_msg!(nodes[1], MessageSendEvent::SendFundingSigned, node_a_id); + nodes[0].node.handle_funding_signed(node_b_id, &funding_signed); + check_added_monitors(&nodes[0], 1); + let events = &nodes[0].node.get_and_clear_pending_events(); + assert_eq!(events.len(), 2); + match &events[0] { + crate::events::Event::FundingTxBroadcastSafe { funding_txo, .. } => { + assert_eq!(funding_txo.txid, funding_outpoint.txid); + assert_eq!(funding_txo.vout, funding_outpoint.index.into()); + }, + _ => panic!("Unexpected event"), + }; + match &events[1] { + crate::events::Event::ChannelPending { counterparty_node_id, .. } => { + assert_eq!(node_b_id, *counterparty_node_id); + }, + _ => panic!("Unexpected event"), + }; + + mine_transaction(&nodes[0], &tx); + mine_transaction(&nodes[1], &tx); + + let as_channel_ready = get_event_msg!(nodes[1], MessageSendEvent::SendChannelReady, node_a_id); + nodes[1].node.handle_channel_ready(node_a_id, &as_channel_ready); + let as_channel_ready = get_event_msg!(nodes[0], MessageSendEvent::SendChannelReady, node_b_id); + nodes[0].node.handle_channel_ready(node_b_id, &as_channel_ready); + + expect_channel_ready_event(&nodes[0], &node_b_id); + expect_channel_ready_event(&nodes[1], &node_a_id); + nodes[0].node.get_and_clear_pending_msg_events(); + nodes[1].node.get_and_clear_pending_msg_events(); +} + +#[xtest(feature = "_externalize_tests")] +fn test_fund_pending_channel() { + // Previously, we would panic if a user called `batch_funding_transaction_generated` for a + // channel which wasn't ready to receive funding. While this isn't a major bug - users + // shouldn't do that - we shouldn't panic and should instead return an error. + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let node_b_id = nodes[1].node.get_our_node_id(); + + nodes[0].node.create_channel(node_b_id, 100_000, 0, 42, None, None).unwrap(); + let open_msg = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); + + let tx = Transaction { + version: Version(2), + lock_time: LockTime::ZERO, + input: vec![], + output: vec![], + }; + let pending_chan = [(&open_msg.common_fields.temporary_channel_id, &node_b_id)]; + let res = nodes[0].node.batch_funding_transaction_generated(&pending_chan, tx); + if let Err(APIError::APIMisuseError { err }) = res { + assert_eq!(err, "Channel f7fee84016d554015f5166c0a0df6479942ef55fd70713883b0493493a38e13a with counterparty 0355f8d2238a322d16b602bd0ceaad5b01019fb055971eaadcc9b29226a4da6c23 is not an unfunded, outbound channel ready to fund"); + } else { + panic!("Unexpected result {res:?}"); + } + get_err_msg(&nodes[0], &node_b_id); + let reason = ClosureReason::ProcessingError { + err: "Error in transaction funding: Misuse error: Channel f7fee84016d554015f5166c0a0df6479942ef55fd70713883b0493493a38e13a with counterparty 0355f8d2238a322d16b602bd0ceaad5b01019fb055971eaadcc9b29226a4da6c23 is not an unfunded, outbound channel ready to fund".to_owned(), + }; + check_closed_event!(nodes[0], 1, reason, [node_b_id], 100_000); +} diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 6f411273aab..c953e39d6d6 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -30,12 +30,11 @@ use bitcoin::hashes::{Hash, HashEngine, HmacEngine}; use bitcoin::secp256k1::Secp256k1; use bitcoin::secp256k1::{PublicKey, SecretKey}; -use bitcoin::{secp256k1, Sequence}; -#[cfg(splicing)] -use bitcoin::{ScriptBuf, TxIn, Weight}; +use bitcoin::{secp256k1, Sequence, SignedAmount}; -use crate::blinded_path::message::MessageForwardNode; -use crate::blinded_path::message::{AsyncPaymentsContext, OffersContext}; +use crate::blinded_path::message::{ + AsyncPaymentsContext, BlindedMessagePath, MessageForwardNode, OffersContext, +}; use crate::blinded_path::payment::{ AsyncBolt12OfferContext, Bolt12OfferContext, PaymentContext, UnauthenticatedReceiveTlvs, }; @@ -57,14 +56,16 @@ use crate::events::{ }; use crate::events::{FundingInfo, PaidBolt12Invoice}; use crate::ln::chan_utils::selected_commitment_sat_per_1000_weight; -// Since this struct is returned in `list_channels` methods, expose it here in case users want to -// construct one themselves. +#[cfg(any(test, fuzzing))] +use crate::ln::channel::QuiescentAction; use crate::ln::channel::{ self, hold_time_since, Channel, ChannelError, ChannelUpdateStatus, FundedChannel, InboundV1Channel, OutboundV1Channel, PendingV2Channel, ReconnectionMsg, ShutdownResult, UpdateFulfillCommitFetch, WithChannelContext, }; use crate::ln::channel_state::ChannelDetails; +#[cfg(splicing)] +use crate::ln::funding::SpliceContribution; use crate::ln::inbound_payment; use crate::ln::interactivetxs::{HandleTxCompleteResult, InteractiveTxMessageSendResult}; use crate::ln::msgs; @@ -82,7 +83,7 @@ use crate::ln::onion_utils::{ decode_fulfill_attribution_data, HTLCFailReason, LocalHTLCFailureReason, }; use crate::ln::onion_utils::{process_fulfill_attribution_data, AttributionData}; -use crate::ln::our_peer_storage::EncryptedOurPeerStorage; +use crate::ln::our_peer_storage::{EncryptedOurPeerStorage, PeerStorageMonitorHolder}; #[cfg(test)] use crate::ln::outbound_payment; use crate::ln::outbound_payment::{ @@ -102,6 +103,7 @@ use crate::offers::offer::Offer; use crate::offers::parse::Bolt12SemanticError; use crate::offers::refund::Refund; use crate::offers::signer; +use crate::offers::static_invoice::StaticInvoice; use crate::onion_message::async_payments::{ AsyncPaymentsMessage, AsyncPaymentsMessageHandler, HeldHtlcAvailable, OfferPaths, OfferPathsRequest, ReleaseHeldHtlc, ServeStaticInvoice, StaticInvoicePersisted, @@ -130,21 +132,12 @@ use crate::util::logger::{Level, Logger, WithContext}; use crate::util::scid_utils::fake_scid; use crate::util::ser::{ BigSize, FixedLengthReader, LengthReadable, MaybeReadable, Readable, ReadableArgs, VecWriter, - Writeable, Writer, + WithoutLength, Writeable, Writer, }; use crate::util::wakers::{Future, Notifier}; -#[cfg(all(test, async_payments))] +#[cfg(test)] use crate::blinded_path::payment::BlindedPaymentPath; -#[cfg(async_payments)] -use { - crate::blinded_path::message::BlindedMessagePath, - crate::offers::offer::Amount, - crate::offers::static_invoice::{ - StaticInvoice, StaticInvoiceBuilder, - DEFAULT_RELATIVE_EXPIRY as STATIC_INVOICE_DEFAULT_RELATIVE_EXPIRY, - }, -}; #[cfg(feature = "dnssec")] use { @@ -450,7 +443,7 @@ pub(super) struct PendingAddHTLCInfo { // Note that this may be an outbound SCID alias for the associated channel. prev_short_channel_id: u64, prev_htlc_id: u64, - prev_counterparty_node_id: Option, + prev_counterparty_node_id: PublicKey, prev_channel_id: ChannelId, prev_funding_outpoint: OutPoint, prev_user_channel_id: u128, @@ -512,6 +505,7 @@ struct ClaimableHTLC { impl From<&ClaimableHTLC> for events::ClaimedHTLC { fn from(val: &ClaimableHTLC) -> Self { events::ClaimedHTLC { + counterparty_node_id: val.prev_hop.counterparty_node_id, channel_id: val.prev_hop.channel_id, user_channel_id: val.prev_hop.user_channel_id.unwrap_or(0), cltv_expiry: val.cltv_expiry, @@ -670,7 +664,7 @@ impl_writeable_tlv_based_enum!(SentHTLCId, // (src_channel_id, src_counterparty_node_id, src_funding_outpoint, src_chan_id, src_user_chan_id) type PerSourcePendingForward = - (u64, Option, OutPoint, ChannelId, u128, Vec<(PendingHTLCInfo, u64)>); + (u64, PublicKey, OutPoint, ChannelId, u128, Vec<(PendingHTLCInfo, u64)>); type FailedHTLCForward = (HTLCSource, PaymentHash, HTLCFailReason, HTLCHandlingFailureType); @@ -933,9 +927,19 @@ struct ClaimingPayment { sender_intended_value: Option, onion_fields: Option, payment_id: Option, + /// When we claim and generate a [`Event::PaymentClaimed`], we want to block any + /// payment-preimage-removing RAA [`ChannelMonitorUpdate`]s until the [`Event::PaymentClaimed`] + /// is handled, ensuring we can regenerate the event on restart. We pick a random channel to + /// block and store it here. + /// + /// Note that once we disallow downgrades to 0.1 we should be able to simply use + /// [`Self::htlcs`] to generate this rather than storing it here (as we won't need the funding + /// outpoint), allowing us to remove this field. + durable_preimage_channel: Option<(OutPoint, PublicKey, ChannelId)>, } impl_writeable_tlv_based!(ClaimingPayment, { (0, amount_msat, required), + (1, durable_preimage_channel, option), (2, payment_purpose, required), (4, receiver_node_id, required), (5, htlcs, optional_vec), @@ -1082,6 +1086,16 @@ impl ClaimablePayments { .or_insert_with(|| { let htlcs = payment.htlcs.iter().map(events::ClaimedHTLC::from).collect(); let sender_intended_value = payment.htlcs.first().map(|htlc| htlc.total_msat); + // Pick an "arbitrary" channel to block RAAs on until the `PaymentSent` + // event is processed, specifically the last channel to get claimed. + let durable_preimage_channel = payment.htlcs.last().map_or(None, |htlc| { + if let Some(node_id) = htlc.prev_hop.counterparty_node_id { + Some((htlc.prev_hop.outpoint, node_id, htlc.prev_hop.channel_id)) + } else { + None + } + }); + debug_assert!(durable_preimage_channel.is_some()); ClaimingPayment { amount_msat: payment.htlcs.iter().map(|source| source.value).sum(), payment_purpose: payment.purpose, @@ -1090,6 +1104,7 @@ impl ClaimablePayments { sender_intended_value, onion_fields: payment.onion_fields, payment_id: Some(payment_id), + durable_preimage_channel, } }).clone(); @@ -1201,7 +1216,6 @@ pub(crate) enum MonitorUpdateCompletionAction { /// stored for later processing. FreeOtherChannelImmediately { downstream_counterparty_node_id: PublicKey, - downstream_funding_outpoint: OutPoint, blocking_action: RAAMonitorUpdateBlockingAction, downstream_channel_id: ChannelId, }, @@ -1216,11 +1230,8 @@ impl_writeable_tlv_based_enum_upgradable!(MonitorUpdateCompletionAction, // *immediately*. However, for simplicity we implement read/write here. (1, FreeOtherChannelImmediately) => { (0, downstream_counterparty_node_id, required), - (2, downstream_funding_outpoint, required), (4, blocking_action, upgradable_required), - // Note that by the time we get past the required read above, downstream_funding_outpoint will be - // filled in, so we can safely unwrap it here. - (5, downstream_channel_id, (default_value, ChannelId::v1_from_funding_outpoint(downstream_funding_outpoint.0.unwrap()))), + (5, downstream_channel_id, required), }, (2, EmitEventAndFreeOtherChannel) => { (0, event, upgradable_required), @@ -1237,17 +1248,21 @@ impl_writeable_tlv_based_enum_upgradable!(MonitorUpdateCompletionAction, pub(crate) enum EventCompletionAction { ReleaseRAAChannelMonitorUpdate { counterparty_node_id: PublicKey, - channel_funding_outpoint: OutPoint, + // Was required until LDK 0.2. Always filled in as `Some`. + channel_funding_outpoint: Option, channel_id: ChannelId, }, } impl_writeable_tlv_based_enum!(EventCompletionAction, (0, ReleaseRAAChannelMonitorUpdate) => { - (0, channel_funding_outpoint, required), + (0, channel_funding_outpoint, option), (2, counterparty_node_id, required), - // Note that by the time we get past the required read above, channel_funding_outpoint will be - // filled in, so we can safely unwrap it here. - (3, channel_id, (default_value, ChannelId::v1_from_funding_outpoint(channel_funding_outpoint.0.unwrap()))), + (3, channel_id, (default_value, { + if channel_funding_outpoint.is_none() { + Err(DecodeError::InvalidValue)? + } + ChannelId::v1_from_funding_outpoint(channel_funding_outpoint.unwrap()) + })), } ); @@ -1258,7 +1273,7 @@ impl_writeable_tlv_based_enum!(EventCompletionAction, /// drop this and merge the two, however doing so may break upgrades for nodes which have pending /// forwarded payments. struct HTLCClaimSource { - counterparty_node_id: Option, + counterparty_node_id: PublicKey, funding_txo: OutPoint, channel_id: ChannelId, htlc_id: u64, @@ -1267,7 +1282,7 @@ struct HTLCClaimSource { impl From<&MPPClaimHTLCSource> for HTLCClaimSource { fn from(o: &MPPClaimHTLCSource) -> HTLCClaimSource { HTLCClaimSource { - counterparty_node_id: Some(o.counterparty_node_id), + counterparty_node_id: o.counterparty_node_id, funding_txo: o.funding_txo, channel_id: o.channel_id, htlc_id: o.htlc_id, @@ -1277,8 +1292,8 @@ impl From<&MPPClaimHTLCSource> for HTLCClaimSource { #[derive(Debug)] pub(crate) struct PendingMPPClaim { - channels_without_preimage: Vec<(PublicKey, OutPoint, ChannelId)>, - channels_with_preimage: Vec<(PublicKey, OutPoint, ChannelId)>, + channels_without_preimage: Vec<(PublicKey, ChannelId)>, + channels_with_preimage: Vec<(PublicKey, ChannelId)>, } #[derive(Clone, Debug, Hash, PartialEq, Eq)] @@ -2959,6 +2974,7 @@ pub(super) const MAX_UNFUNDED_CHANNEL_PEERS: usize = 50; /// This constant defines the upper limit for the size of data /// that can be stored for a peer. It is set to 1024 bytes (1 kilobyte) /// to prevent excessive resource consumption. +#[cfg(not(test))] const MAX_PEER_STORAGE_SIZE: usize = 1024; /// The maximum number of peers which we do not have a (funded) channel with. Once we reach this @@ -4436,14 +4452,13 @@ where #[cfg(splicing)] #[rustfmt::skip] pub fn splice_channel( - &self, channel_id: &ChannelId, counterparty_node_id: &PublicKey, our_funding_contribution_satoshis: i64, - our_funding_inputs: Vec<(TxIn, Transaction, Weight)>, change_script: Option, - funding_feerate_per_kw: u32, locktime: Option, + &self, channel_id: &ChannelId, counterparty_node_id: &PublicKey, + contribution: SpliceContribution, funding_feerate_per_kw: u32, locktime: Option, ) -> Result<(), APIError> { let mut res = Ok(()); PersistenceNotifierGuard::optionally_notify(self, || { let result = self.internal_splice_channel( - channel_id, counterparty_node_id, our_funding_contribution_satoshis, our_funding_inputs, change_script, funding_feerate_per_kw, locktime + channel_id, counterparty_node_id, contribution, funding_feerate_per_kw, locktime ); res = result; match res { @@ -4458,9 +4473,7 @@ where #[cfg(splicing)] fn internal_splice_channel( &self, channel_id: &ChannelId, counterparty_node_id: &PublicKey, - our_funding_contribution_satoshis: i64, - our_funding_inputs: Vec<(TxIn, Transaction, Weight)>, change_script: Option, - funding_feerate_per_kw: u32, locktime: Option, + contribution: SpliceContribution, funding_feerate_per_kw: u32, locktime: Option, ) -> Result<(), APIError> { let per_peer_state = self.per_peer_state.read().unwrap(); @@ -4481,13 +4494,8 @@ where hash_map::Entry::Occupied(mut chan_phase_entry) => { let locktime = locktime.unwrap_or_else(|| self.current_best_block().height); if let Some(chan) = chan_phase_entry.get_mut().as_funded_mut() { - let msg = chan.splice_channel( - our_funding_contribution_satoshis, - our_funding_inputs, - change_script, - funding_feerate_per_kw, - locktime, - )?; + let msg = + chan.splice_channel(contribution, funding_feerate_per_kw, locktime)?; peer_state.pending_msg_events.push(MessageSendEvent::SendSpliceInit { node_id: *counterparty_node_id, msg, @@ -5099,7 +5107,7 @@ where ) } - #[cfg(all(test, async_payments))] + #[cfg(test)] pub(crate) fn test_modify_pending_payment(&self, payment_id: &PaymentId, mut callback: Fn) where Fn: FnMut(&mut PendingOutboundPayment), @@ -5229,7 +5237,6 @@ where ) } - #[cfg(async_payments)] fn check_refresh_async_receive_offer_cache(&self, timer_tick_occurred: bool) { let peers = self.get_peers_for_blinded_path(); let channels = self.list_usable_channels(); @@ -5253,27 +5260,24 @@ where } } - #[cfg(all(test, async_payments))] + #[cfg(test)] pub(crate) fn test_check_refresh_async_receive_offers(&self) { self.check_refresh_async_receive_offer_cache(false); } /// Should be called after handling an [`Event::PersistStaticInvoice`], where the `Responder` /// comes from [`Event::PersistStaticInvoice::invoice_persisted_path`]. - #[cfg(async_payments)] pub fn static_invoice_persisted(&self, invoice_persisted_path: Responder) { self.flow.static_invoice_persisted(invoice_persisted_path); } /// Forwards a [`StaticInvoice`] in response to an [`Event::StaticInvoiceRequested`]. - #[cfg(async_payments)] pub fn send_static_invoice( &self, invoice: StaticInvoice, responder: Responder, ) -> Result<(), Bolt12SemanticError> { self.flow.enqueue_static_invoice(invoice, responder) } - #[cfg(async_payments)] fn initiate_async_payment( &self, invoice: &StaticInvoice, payment_id: PaymentId, ) -> Result<(), Bolt12PaymentError> { @@ -5323,7 +5327,6 @@ where res } - #[cfg(async_payments)] fn send_payment_for_static_invoice( &self, payment_id: PaymentId, ) -> Result<(), Bolt12PaymentError> { @@ -5598,42 +5601,48 @@ where Err($api_err) } } } - let funding_txo; - let (mut chan, msg_opt) = match peer_state.channel_by_id.remove(&temporary_channel_id) - .map(Channel::into_unfunded_outbound_v1) - { - Some(Ok(mut chan)) => { - match find_funding_output(&chan) { - Ok(found_funding_txo) => funding_txo = found_funding_txo, - Err(err) => { - let chan_err = ChannelError::close(err.to_owned()); - let api_err = APIError::APIMisuseError { err: err.to_owned() }; - return abandon_chan!(chan_err, api_err, chan); - }, + let mut chan = match peer_state.channel_by_id.entry(temporary_channel_id) { + hash_map::Entry::Occupied(chan) => { + if !chan.get().ready_to_fund() { + return Err(APIError::APIMisuseError { + err: format!("Channel {temporary_channel_id} with counterparty {counterparty_node_id} is not an unfunded, outbound channel ready to fund"), + }); } - - let logger = WithChannelContext::from(&self.logger, &chan.context, None); - let funding_res = chan.get_funding_created(funding_transaction, funding_txo, is_batch_funding, &&logger); - match funding_res { - Ok(funding_msg) => (chan, funding_msg), - Err((mut chan, chan_err)) => { - let api_err = APIError::ChannelUnavailable { err: "Signer refused to sign the initial commitment transaction".to_owned() }; - return abandon_chan!(chan_err, api_err, chan); - } + match chan.remove().into_unfunded_outbound_v1() { + Ok(chan) => chan, + Err(chan) => { + debug_assert!(false, "ready_to_fund guarantees into_unfunded_outbound_v1 will succeed"); + peer_state.channel_by_id.insert(temporary_channel_id, chan); + return Err(APIError::APIMisuseError { + err: "Invalid state, please report this bug".to_owned(), + }); + }, } }, - Some(Err(chan)) => { - peer_state.channel_by_id.insert(temporary_channel_id, chan); - return Err(APIError::APIMisuseError { - err: format!( - "Channel with id {} for the passed counterparty node_id {} is not an unfunded, outbound V1 channel", - temporary_channel_id, counterparty_node_id), - }) + hash_map::Entry::Vacant(_) => { + return Err(APIError::ChannelUnavailable { + err: format!("Channel {temporary_channel_id} with counterparty {counterparty_node_id} not found"), + }); }, - None => return Err(APIError::ChannelUnavailable {err: format!( - "Channel with id {} not found for the passed counterparty node_id {}", - temporary_channel_id, counterparty_node_id), - }), + }; + + let funding_txo = match find_funding_output(&chan) { + Ok(found_funding_txo) => found_funding_txo, + Err(err) => { + let chan_err = ChannelError::close(err.to_owned()); + let api_err = APIError::APIMisuseError { err: err.to_owned() }; + return abandon_chan!(chan_err, api_err, chan); + }, + }; + + let logger = WithChannelContext::from(&self.logger, &chan.context, None); + let funding_res = chan.get_funding_created(funding_transaction, funding_txo, is_batch_funding, &&logger); + let (mut chan, msg_opt) = match funding_res { + Ok(funding_msg) => (chan, funding_msg), + Err((mut chan, chan_err)) => { + let api_err = APIError::ChannelUnavailable { err: "Signer refused to sign the initial commitment transaction".to_owned() }; + return abandon_chan!(chan_err, api_err, chan); + } }; match peer_state.channel_by_id.entry(chan.context.channel_id()) { @@ -5901,6 +5910,126 @@ where result } + /// Handles a signed funding transaction generated by interactive transaction construction and + /// provided by the client. Should only be called in response to a [`FundingTransactionReadyForSigning`] + /// event. + /// + /// Do NOT broadcast the funding transaction yourself. When we have safely received our + /// counterparty's signature(s) the funding transaction will automatically be broadcast via the + /// [`BroadcasterInterface`] provided when this `ChannelManager` was constructed. + /// + /// `SIGHASH_ALL` MUST be used for all signatures when providing signatures, otherwise your + /// funds can be held hostage! + /// + /// LDK checks the following: + /// * Each input spends an output that is one of P2WPKH, P2WSH, or P2TR. + /// These were already checked by LDK when the inputs to be contributed were provided. + /// * All signatures use the `SIGHASH_ALL` sighash type. + /// * P2WPKH and P2TR key path spends are valid (verifies signatures) + /// + /// NOTE: + /// * When checking P2WSH spends, LDK tries to decode 70-72 byte witness elements as ECDSA + /// signatures with a sighash flag. If the internal DER-decoding fails, then LDK just + /// assumes it wasn't a signature and carries with checks. If the element can be decoded + /// as an ECDSA signature, the the sighash flag must be `SIGHASH_ALL`. + /// * When checking P2TR script-path spends, LDK assumes all elements of exactly 65 bytes + /// with the last byte matching any valid sighash flag byte are schnorr signatures and checks + /// that the sighash type is `SIGHASH_ALL`. If the last byte is not any valid sighash flag, the + /// element is assumed not to be a signature and is ignored. Elements of 64 bytes are not + /// checked because if they were schnorr signatures then they would implicitly be `SIGHASH_DEFAULT` + /// which is an alias of `SIGHASH_ALL`. + /// + /// Returns [`ChannelUnavailable`] when a channel is not found or an incorrect + /// `counterparty_node_id` is provided. + /// + /// Returns [`APIMisuseError`] when a channel is not in a state where it is expecting funding + /// signatures or if any of the checks described above fail. + /// + /// [`FundingTransactionReadyForSigning`]: events::Event::FundingTransactionReadyForSigning + /// [`ChannelUnavailable`]: APIError::ChannelUnavailable + /// [`APIMisuseError`]: APIError::APIMisuseError + pub fn funding_transaction_signed( + &self, channel_id: &ChannelId, counterparty_node_id: &PublicKey, transaction: Transaction, + ) -> Result<(), APIError> { + let mut result = Ok(()); + PersistenceNotifierGuard::optionally_notify(self, || { + let per_peer_state = self.per_peer_state.read().unwrap(); + let peer_state_mutex_opt = per_peer_state.get(counterparty_node_id); + if peer_state_mutex_opt.is_none() { + result = Err(APIError::ChannelUnavailable { + err: format!("Can't find a peer matching the passed counterparty node_id {counterparty_node_id}") + }); + return NotifyOption::SkipPersistNoEvents; + } + + let mut peer_state = peer_state_mutex_opt.unwrap().lock().unwrap(); + + match peer_state.channel_by_id.get_mut(channel_id) { + Some(channel) => match channel.as_funded_mut() { + Some(chan) => { + let witnesses: Vec<_> = transaction + .input + .into_iter() + .map(|input| input.witness) + .filter(|witness| witness.is_empty()) + .collect(); + match chan.funding_transaction_signed(witnesses) { + Ok((Some(tx_signatures), funding_tx_opt)) => { + if let Some(funding_tx) = funding_tx_opt { + self.broadcast_interactive_funding(chan, &funding_tx); + } + peer_state.pending_msg_events.push( + MessageSendEvent::SendTxSignatures { + node_id: *counterparty_node_id, + msg: tx_signatures, + }, + ); + return NotifyOption::DoPersist; + }, + Err(err) => { + result = Err(err); + return NotifyOption::SkipPersistNoEvents; + }, + _ => { + return NotifyOption::SkipPersistNoEvents; + }, + } + }, + None => { + result = Err(APIError::APIMisuseError { + err: format!( + "Channel with id {} not expecting funding signatures", + channel_id + ), + }); + return NotifyOption::SkipPersistNoEvents; + }, + }, + None => { + result = Err(APIError::ChannelUnavailable { + err: format!( + "Channel with id {} not found for the passed counterparty node_id {}", + channel_id, counterparty_node_id + ), + }); + return NotifyOption::SkipPersistNoEvents; + }, + } + }); + + result + } + + fn broadcast_interactive_funding( + &self, channel: &mut FundedChannel, funding_tx: &Transaction, + ) { + self.tx_broadcaster.broadcast_transactions(&[funding_tx]); + { + let mut pending_events = self.pending_events.lock().unwrap(); + emit_channel_pending_event!(pending_events, channel); + } + } + /// Atomically applies partial updates to the [`ChannelConfig`] of the given channels. /// /// Once the updates are applied, each eligible channel (advertised with a known short channel @@ -6149,7 +6278,7 @@ where user_channel_id: Some(payment.prev_user_channel_id), outpoint: payment.prev_funding_outpoint, channel_id: payment.prev_channel_id, - counterparty_node_id: payment.prev_counterparty_node_id, + counterparty_node_id: Some(payment.prev_counterparty_node_id), htlc_id: payment.prev_htlc_id, incoming_packet_shared_secret: payment.forward_info.incoming_shared_secret, phantom_shared_secret: None, @@ -6255,7 +6384,7 @@ where &chan.context, Some(update_add_htlc.payment_hash), ); - chan.can_accept_incoming_htlc(update_add_htlc, &self.fee_estimator, &logger) + chan.can_accept_incoming_htlc(&self.fee_estimator, &logger) }, ) { Some(Ok(_)) => {}, @@ -6322,7 +6451,7 @@ where // proposed to as a batch. let pending_forwards = ( incoming_scid, - Some(incoming_counterparty_node_id), + incoming_counterparty_node_id, incoming_funding_txo, incoming_channel_id, incoming_user_channel_id, @@ -6511,7 +6640,7 @@ where user_channel_id: Some(prev_user_channel_id), channel_id: prev_channel_id, outpoint: prev_funding_outpoint, - counterparty_node_id: prev_counterparty_node_id, + counterparty_node_id: Some(prev_counterparty_node_id), htlc_id: prev_htlc_id, incoming_packet_shared_secret: incoming_shared_secret, phantom_shared_secret: phantom_ss, @@ -6724,7 +6853,7 @@ where let htlc_source = HTLCSource::PreviousHopData(HTLCPreviousHopData { short_channel_id: prev_short_channel_id, user_channel_id: Some(prev_user_channel_id), - counterparty_node_id: prev_counterparty_node_id, + counterparty_node_id: Some(prev_counterparty_node_id), channel_id: prev_channel_id, outpoint: prev_funding_outpoint, htlc_id: prev_htlc_id, @@ -7035,7 +7164,7 @@ where prev_hop: HTLCPreviousHopData { short_channel_id: prev_short_channel_id, user_channel_id: Some(prev_user_channel_id), - counterparty_node_id: prev_counterparty_node_id, + counterparty_node_id: Some(prev_counterparty_node_id), channel_id: prev_channel_id, outpoint: prev_funding_outpoint, htlc_id: prev_htlc_id, @@ -7676,7 +7805,6 @@ where self.pending_outbound_payments .remove_stale_payments(duration_since_epoch, &self.pending_events); - #[cfg(async_payments)] self.check_refresh_async_receive_offer_cache(true); // Technically we don't need to do this here, but if we have holding cell entries in a @@ -8065,7 +8193,7 @@ where let pending_mpp_claim_ptr_opt = if sources.len() > 1 { let mut channels_without_preimage = Vec::with_capacity(mpp_parts.len()); for part in mpp_parts.iter() { - let chan = (part.counterparty_node_id, part.funding_txo, part.channel_id); + let chan = (part.counterparty_node_id, part.channel_id); if !channels_without_preimage.contains(&chan) { channels_without_preimage.push(chan); } @@ -8080,13 +8208,12 @@ where let payment_info = Some(PaymentClaimDetails { mpp_parts, claiming_payment }); for htlc in sources { let this_mpp_claim = - pending_mpp_claim_ptr_opt.as_ref().and_then(|pending_mpp_claim| { - if let Some(cp_id) = htlc.prev_hop.counterparty_node_id { - let claim_ptr = PendingMPPClaimPointer(Arc::clone(pending_mpp_claim)); - Some((cp_id, htlc.prev_hop.channel_id, claim_ptr)) - } else { - None - } + pending_mpp_claim_ptr_opt.as_ref().map(|pending_mpp_claim| { + let counterparty_id = htlc.prev_hop.counterparty_node_id; + let counterparty_id = counterparty_id + .expect("Prior to upgrading to LDK 0.1, all pending HTLCs forwarded by LDK 0.0.123 or before must be resolved. It appears at least one claimable payment was not resolved. Please downgrade to LDK 0.0.125 and resolve the HTLC by claiming the payment prior to upgrading."); + let claim_ptr = PendingMPPClaimPointer(Arc::clone(pending_mpp_claim)); + (counterparty_id, htlc.prev_hop.channel_id, claim_ptr) }); let raa_blocker = pending_mpp_claim_ptr_opt.as_ref().map(|pending_claim| { RAAMonitorUpdateBlockingAction::ClaimedMPPPayment { @@ -8168,6 +8295,14 @@ where let short_to_chan_info = self.short_to_chan_info.read().unwrap(); short_to_chan_info.get(&prev_hop.short_channel_id).map(|(cp_id, _)| *cp_id) }); + let counterparty_node_id = if let Some(node_id) = counterparty_node_id { + node_id + } else { + let payment_hash: PaymentHash = payment_preimage.into(); + panic!( + "Prior to upgrading to LDK 0.1, all pending HTLCs forwarded by LDK 0.0.123 or before must be resolved. It appears at least the HTLC with payment_hash {payment_hash} (preimage {payment_preimage}) was not resolved. Please downgrade to LDK 0.0.125 and resolve the HTLC prior to upgrading.", + ); + }; let htlc_source = HTLCClaimSource { counterparty_node_id, @@ -8212,18 +8347,13 @@ where const MISSING_MON_ERROR: &'static str = "If we're going to claim an HTLC against a channel, we should always have *some* state for the channel, even if just the latest ChannelMonitor update_id. This failure indicates we need to claim an HTLC from a channel for which we did not have a ChannelMonitor at startup and didn't create one while running."; - // Note here that `peer_state_opt` is always `Some` if `prev_hop.counterparty_node_id` is - // `Some`. This is relied on in the closed-channel case below. - let mut peer_state_opt = - prev_hop.counterparty_node_id.as_ref().map(|counterparty_node_id| { - per_peer_state - .get(counterparty_node_id) - .map(|peer_mutex| peer_mutex.lock().unwrap()) - .expect(MISSING_MON_ERROR) - }); + let mut peer_state_lock = per_peer_state + .get(&prev_hop.counterparty_node_id) + .map(|peer_mutex| peer_mutex.lock().unwrap()) + .expect(MISSING_MON_ERROR); - if let Some(peer_state_lock) = peer_state_opt.as_mut() { - let peer_state = &mut **peer_state_lock; + { + let peer_state = &mut *peer_state_lock; if let hash_map::Entry::Occupied(mut chan_entry) = peer_state.channel_by_id.entry(chan_id) { @@ -8261,7 +8391,7 @@ where self, prev_hop.funding_txo, monitor_update, - peer_state_opt, + peer_state_lock, peer_state, per_peer_state, chan @@ -8274,18 +8404,21 @@ where // payment claim from a `ChannelMonitor`. In some cases (MPP or // if the HTLC was only recently removed) we make such claims // after an HTLC has been removed from a channel entirely, and - // thus the RAA blocker has long since completed. + // thus the RAA blocker may have long since completed. // - // In any other case, the RAA blocker must still be present and - // blocking RAAs. - debug_assert!( - during_init - || peer_state - .actions_blocking_raa_monitor_updates - .get(&chan_id) - .unwrap() - .contains(&raa_blocker) - ); + // However, its possible that the `ChannelMonitorUpdate` containing + // the preimage never completed and is still pending. In that case, + // we need to re-add the RAA blocker, which we do here. Handling + // the post-update action, below, will remove it again. + // + // In any other case (i.e. not during startup), the RAA blocker + // must still be present and blocking RAAs. + let actions = &mut peer_state.actions_blocking_raa_monitor_updates; + let actions_list = actions.entry(chan_id).or_insert_with(Vec::new); + if !actions_list.contains(&raa_blocker) { + debug_assert!(during_init); + actions_list.push(raa_blocker); + } } let action = if let Some(action) = action_opt { action @@ -8293,13 +8426,28 @@ where return; }; - mem::drop(peer_state_opt); + // If there are monitor updates in flight, we may be in the case + // described above, replaying a claim on startup which needs an RAA + // blocker to remain blocked. Thus, in such a case we simply push the + // post-update action to the blocked list and move on. + // In any case, we should err on the side of caution and not process + // the post-update action no matter the situation. + let in_flight_mons = peer_state.in_flight_monitor_updates.get(&chan_id); + if in_flight_mons.map(|(_, mons)| !mons.is_empty()).unwrap_or(false) { + peer_state + .monitor_update_blocked_actions + .entry(chan_id) + .or_insert_with(Vec::new) + .push(action); + return; + } + + mem::drop(peer_state_lock); log_trace!(logger, "Completing monitor update completion action for channel {} as claim was redundant: {:?}", chan_id, action); if let MonitorUpdateCompletionAction::FreeOtherChannelImmediately { downstream_counterparty_node_id: node_id, - downstream_funding_outpoint: _, blocking_action: blocker, downstream_channel_id: channel_id, } = action @@ -8346,18 +8494,7 @@ where } } - if prev_hop.counterparty_node_id.is_none() { - let payment_hash: PaymentHash = payment_preimage.into(); - panic!( - "Prior to upgrading to LDK 0.1, all pending HTLCs forwarded by LDK 0.0.123 or before must be resolved. It appears at least the HTLC with payment_hash {} (preimage {}) was not resolved. Please downgrade to LDK 0.0.125 and resolve the HTLC prior to upgrading.", - payment_hash, - payment_preimage, - ); - } - let counterparty_node_id = - prev_hop.counterparty_node_id.expect("Checked immediately above"); - let mut peer_state = peer_state_opt - .expect("peer_state_opt is always Some when the counterparty_node_id is Some"); + let peer_state = &mut *peer_state_lock; let update_id = if let Some(latest_update_id) = peer_state.closed_channel_monitor_update_ids.get_mut(&chan_id) @@ -8381,11 +8518,11 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ channel_id: Some(prev_hop.channel_id), }; - // Note that we do process the completion action here. This totally could be a - // duplicate claim, but we have no way of knowing without interrogating the - // `ChannelMonitor` we've provided the above update to. Instead, note that `Event`s are - // generally always allowed to be duplicative (and it's specifically noted in - // `PaymentForwarded`). + // We don't have any idea if this is a duplicate claim without interrogating the + // `ChannelMonitor`, so we just always queue up the completion action after the + // `ChannelMonitorUpdate` we're about to generate. This may result in a duplicate `Event`, + // but note that `Event`s are generally always allowed to be duplicative (and it's + // specifically noted in `PaymentForwarded`). let (action_opt, raa_blocker_opt) = completion_action(None, false); if let Some(raa_blocker) = raa_blocker_opt { @@ -8401,7 +8538,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ let payment_hash = payment_preimage.into(); let logger = WithContext::from( &self.logger, - Some(counterparty_node_id), + Some(prev_hop.counterparty_node_id), Some(chan_id), Some(payment_hash), ); @@ -8424,10 +8561,10 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ self, prev_hop.funding_txo, preimage_update, - peer_state, + peer_state_lock, peer_state, per_peer_state, - counterparty_node_id, + prev_hop.counterparty_node_id, chan_id, POST_CHANNEL_CLOSE ); @@ -8483,7 +8620,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ "We don't support claim_htlc claims during startup - monitors may not be available yet"); debug_assert_eq!(next_channel_counterparty_node_id, path.hops[0].pubkey); let ev_completion_action = EventCompletionAction::ReleaseRAAChannelMonitorUpdate { - channel_funding_outpoint: next_channel_outpoint, + channel_funding_outpoint: Some(next_channel_outpoint), channel_id: next_channel_id, counterparty_node_id: path.hops[0].pubkey, }; @@ -8587,7 +8724,6 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ if let Some(other_chan) = chan_to_release { (Some(MonitorUpdateCompletionAction::FreeOtherChannelImmediately { downstream_counterparty_node_id: other_chan.counterparty_node_id, - downstream_funding_outpoint: other_chan.funding_txo, downstream_channel_id: other_chan.channel_id, blocking_action: other_chan.blocking_action, }), None) @@ -8661,17 +8797,17 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ if *pending_claim == claim_ptr { let mut pending_claim_state_lock = pending_claim.0.lock().unwrap(); let pending_claim_state = &mut *pending_claim_state_lock; - pending_claim_state.channels_without_preimage.retain(|(cp, op, cid)| { + pending_claim_state.channels_without_preimage.retain(|(cp, cid)| { let this_claim = *cp == counterparty_node_id && *cid == chan_id; if this_claim { - pending_claim_state.channels_with_preimage.push((*cp, *op, *cid)); + pending_claim_state.channels_with_preimage.push((*cp, *cid)); false } else { true } }); if pending_claim_state.channels_without_preimage.is_empty() { - for (cp, op, cid) in pending_claim_state.channels_with_preimage.iter() { - let freed_chan = (*cp, *op, *cid, blocker.clone()); + for (cp, cid) in pending_claim_state.channels_with_preimage.iter() { + let freed_chan = (*cp, *cid, blocker.clone()); freed_channels.push(freed_chan); } } @@ -8695,6 +8831,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ sender_intended_value: sender_intended_total_msat, onion_fields, payment_id, + durable_preimage_channel, }) = payment { let event = events::Event::PaymentClaimed { payment_hash, @@ -8706,7 +8843,18 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ onion_fields, payment_id, }; - let event_action = (event, None); + let action = if let Some((outpoint, counterparty_node_id, channel_id)) + = durable_preimage_channel + { + Some(EventCompletionAction::ReleaseRAAChannelMonitorUpdate { + channel_funding_outpoint: Some(outpoint), + counterparty_node_id, + channel_id, + }) + } else { + None + }; + let event_action = (event, action); let mut pending_events = self.pending_events.lock().unwrap(); // If we're replaying a claim on startup we may end up duplicating an event // that's already in our queue, so check before we push another one. The @@ -8723,17 +8871,17 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ self.pending_events.lock().unwrap().push_back((event, None)); if let Some(unblocked) = downstream_counterparty_and_funding_outpoint { self.handle_monitor_update_release( - unblocked.counterparty_node_id, unblocked.funding_txo, - unblocked.channel_id, Some(unblocked.blocking_action), + unblocked.counterparty_node_id, + unblocked.channel_id, + Some(unblocked.blocking_action), ); } }, MonitorUpdateCompletionAction::FreeOtherChannelImmediately { - downstream_counterparty_node_id, downstream_funding_outpoint, downstream_channel_id, blocking_action, + downstream_counterparty_node_id, downstream_channel_id, blocking_action, } => { self.handle_monitor_update_release( downstream_counterparty_node_id, - downstream_funding_outpoint, downstream_channel_id, Some(blocking_action), ); @@ -8741,8 +8889,8 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ } } - for (node_id, funding_outpoint, channel_id, blocker) in freed_channels { - self.handle_monitor_update_release(node_id, funding_outpoint, channel_id, Some(blocker)); + for (node_id, channel_id, blocker) in freed_channels { + self.handle_monitor_update_release(node_id, channel_id, Some(blocker)); } } @@ -8756,7 +8904,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ funding_broadcastable: Option, channel_ready: Option, announcement_sigs: Option, tx_signatures: Option, tx_abort: Option, - ) -> (Option<(u64, Option, OutPoint, ChannelId, u128, Vec<(PendingHTLCInfo, u64)>)>, Option<(u64, Vec)>) { + ) -> (Option<(u64, PublicKey, OutPoint, ChannelId, u128, Vec<(PendingHTLCInfo, u64)>)>, Option<(u64, Vec)>) { let logger = WithChannelContext::from(&self.logger, &channel.context, None); log_trace!(logger, "Handling channel resumption for channel {} with {} RAA, {} commitment update, {} pending forwards, {} pending update_add_htlcs, {}broadcasting funding, {} channel ready, {} announcement, {} tx_signatures, {} tx_abort", &channel.context.channel_id(), @@ -8776,7 +8924,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ let mut htlc_forwards = None; if !pending_forwards.is_empty() { htlc_forwards = Some(( - short_channel_id, Some(channel.context.get_counterparty_node_id()), + short_channel_id, channel.context.get_counterparty_node_id(), channel.funding.get_funding_txo().unwrap(), channel.context.channel_id(), channel.context.get_user_id(), pending_forwards )); @@ -8795,6 +8943,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ msg, }); } + // TODO(dual_funding): For async signing support we need to hold back `tx_signatures` until the `commitment_signed` is ready. if let Some(msg) = tx_signatures { pending_msg_events.push(MessageSendEvent::SendTxSignatures { node_id: counterparty_node_id, @@ -8855,6 +9004,46 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ } } + if let Some(signing_session) = &mut channel.interactive_tx_signing_session { + if signing_session.local_inputs_count() > 0 + && signing_session.holder_tx_signatures().is_none() + { + let mut pending_events = self.pending_events.lock().unwrap(); + let unsigned_transaction = signing_session.unsigned_tx().build_unsigned_tx(); + let event_action = ( + Event::FundingTransactionReadyForSigning { + unsigned_transaction, + counterparty_node_id, + channel_id: channel.context.channel_id(), + user_channel_id: channel.context.get_user_id(), + }, + None, + ); + + if pending_events.contains(&event_action) { + debug_assert!(false, "FundingTransactionReadyForSigning should not have been queued already"); + } else { + pending_events.push_back(event_action); + } + } else if signing_session.local_inputs_count() == 0 && signing_session.holder_tx_signatures().is_none() { + match channel.funding_transaction_signed(vec![]) { + Ok((Some(tx_signatures), funding_tx_opt)) => { + if let Some(funding_tx) = funding_tx_opt { + self.broadcast_interactive_funding(channel, &funding_tx); + } + pending_msg_events.push(MessageSendEvent::SendTxSignatures { + node_id: counterparty_node_id, + msg: tx_signatures, + }); + }, + Ok((None, _)) => { + debug_assert!(false, "If our tx_signatures is empty, then we should send it first!"); + }, + Err(err) => debug_assert!(false, "We should not error here but we got: {:?}", err), + } + } + } + { let mut pending_events = self.pending_events.lock().unwrap(); emit_channel_pending_event!(pending_events, channel); @@ -9198,7 +9387,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ // Inbound V2 channels with contributed inputs are not considered unfunded. if let Some(unfunded_chan) = chan.as_unfunded_v2() { - if unfunded_chan.funding_negotiation_context.our_funding_contribution_satoshis > 0 { + if unfunded_chan.funding_negotiation_context.our_funding_contribution > SignedAmount::ZERO { continue; } } @@ -9516,7 +9705,54 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ }; log_trace!(logger, "Got valid {}-byte peer backup from {}", decrypted.len(), peer_node_id); + let per_peer_state = self.per_peer_state.read().unwrap(); + + let mut cursor = io::Cursor::new(decrypted); + let mon_list = as Readable>::read(&mut cursor) + .unwrap_or_else(|e| { + // This should NEVER happen. + debug_assert!(false); + log_debug!(self.logger, "Unable to unpack the retrieved peer storage {:?}", e); + Vec::new() + }); + + for mon_holder in mon_list.iter() { + let peer_state_mutex = match per_peer_state.get(&mon_holder.counterparty_node_id) { + Some(mutex) => mutex, + None => { + log_debug!( + logger, + "Not able to find peer_state for the counterparty {}, channel_id {}", + log_pubkey!(mon_holder.counterparty_node_id), + mon_holder.channel_id + ); + continue; + }, + }; + + let peer_state_lock = peer_state_mutex.lock().unwrap(); + let peer_state = &*peer_state_lock; + match peer_state.channel_by_id.get(&mon_holder.channel_id) { + Some(chan) => { + if let Some(funded_chan) = chan.as_funded() { + if funded_chan.get_revoked_counterparty_commitment_transaction_number() + > mon_holder.min_seen_secret + { + panic!( + "Lost channel state for channel {}.\n\ + Received peer storage with a more recent state than what our node had.\n\ + Use the FundRecoverer to initiate a force close and sweep the funds.", + &mon_holder.channel_id + ); + } + } + }, + None => { + log_debug!(logger, "Found an unknown channel {}", &mon_holder.channel_id); + }, + } + } Ok(()) } @@ -9542,6 +9778,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ ), ChannelId([0; 32]))); } + #[cfg(not(test))] if msg.data.len() > MAX_PEER_STORAGE_SIZE { log_debug!(logger, "Sending warning to peer and ignoring peer storage request from {} as its over 1KiB", log_pubkey!(counterparty_node_id)); @@ -9730,11 +9967,11 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ peer_state.pending_msg_events.push(msg_send_event); }; if negotiation_complete { - let (commitment_signed, funding_ready_for_sig_event_opt) = match chan_entry + let commitment_signed = match chan_entry .get_mut() .funding_tx_constructed(&self.logger) { - Ok((commitment_signed, event)) => (commitment_signed, event), + Ok(commitment_signed) => commitment_signed, Err(tx_abort) => { if chan_entry.get().is_funded() { peer_state.pending_msg_events.push(MessageSendEvent::SendTxAbort { @@ -9751,10 +9988,6 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ } }, }; - if let Some(funding_ready_for_sig_event) = funding_ready_for_sig_event_opt { - let mut pending_events = self.pending_events.lock().unwrap(); - pending_events.push_back((funding_ready_for_sig_event, None)); - } peer_state.pending_msg_events.push(MessageSendEvent::UpdateHTLCs { node_id: counterparty_node_id, channel_id: msg.channel_id, @@ -9793,8 +10026,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ hash_map::Entry::Occupied(mut chan_entry) => { match chan_entry.get_mut().as_funded_mut() { Some(chan) => { - let logger = WithChannelContext::from(&self.logger, &chan.context, None); - let (funding_tx_opt, tx_signatures_opt) = try_channel_entry!(self, peer_state, chan.tx_signatures(msg, &&logger), chan_entry); + let (funding_tx_opt, tx_signatures_opt) = try_channel_entry!(self, peer_state, chan.tx_signatures(msg), chan_entry); if let Some(tx_signatures) = tx_signatures_opt { peer_state.pending_msg_events.push(MessageSendEvent::SendTxSignatures { node_id: *counterparty_node_id, @@ -10449,23 +10681,22 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ "Failed to forward incoming HTLC: detected duplicate intercepted payment over short channel id {}", scid ); + let routing = &forward_info.routing; let htlc_source = HTLCSource::PreviousHopData(HTLCPreviousHopData { short_channel_id: prev_short_channel_id, user_channel_id: Some(prev_user_channel_id), - counterparty_node_id: prev_counterparty_node_id, + counterparty_node_id: Some( + prev_counterparty_node_id, + ), outpoint: prev_funding_outpoint, channel_id: prev_channel_id, htlc_id: prev_htlc_id, incoming_packet_shared_secret: forward_info .incoming_shared_secret, phantom_shared_secret: None, - blinded_failure: forward_info - .routing - .blinded_failure(), - cltv_expiry: forward_info - .routing - .incoming_cltv_expiry(), + blinded_failure: routing.blinded_failure(), + cltv_expiry: routing.incoming_cltv_expiry(), }); let payment_hash = forward_info.payment_hash; @@ -10525,16 +10756,20 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ #[rustfmt::skip] fn raa_monitor_updates_held(&self, actions_blocking_raa_monitor_updates: &BTreeMap>, - channel_funding_outpoint: OutPoint, channel_id: ChannelId, counterparty_node_id: PublicKey + channel_id: ChannelId, counterparty_node_id: PublicKey, ) -> bool { actions_blocking_raa_monitor_updates .get(&channel_id).map(|v| !v.is_empty()).unwrap_or(false) || self.pending_events.lock().unwrap().iter().any(|(_, action)| { - action == &Some(EventCompletionAction::ReleaseRAAChannelMonitorUpdate { - channel_funding_outpoint, - channel_id, - counterparty_node_id, - }) + if let Some(EventCompletionAction::ReleaseRAAChannelMonitorUpdate { + channel_funding_outpoint: _, + channel_id: ev_channel_id, + counterparty_node_id: ev_counterparty_node_id + }) = action { + *ev_channel_id == channel_id && *ev_counterparty_node_id == counterparty_node_id + } else { + false + } }) } @@ -10547,14 +10782,12 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ let mut peer_state_lck = peer_state_mtx.lock().unwrap(); let peer_state = &mut *peer_state_lck; - if let Some(chan) = peer_state.channel_by_id.get(&channel_id) { - return self.raa_monitor_updates_held( - &peer_state.actions_blocking_raa_monitor_updates, - chan.funding().get_funding_txo().unwrap(), - channel_id, - counterparty_node_id, - ); - } + assert!(peer_state.channel_by_id.contains_key(&channel_id)); + return self.raa_monitor_updates_held( + &peer_state.actions_blocking_raa_monitor_updates, + channel_id, + counterparty_node_id, + ); } false } @@ -10574,11 +10807,9 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ if let Some(chan) = chan_entry.get_mut().as_funded_mut() { let logger = WithChannelContext::from(&self.logger, &chan.context, None); let funding_txo_opt = chan.funding.get_funding_txo(); - let mon_update_blocked = if let Some(funding_txo) = funding_txo_opt { - self.raa_monitor_updates_held( - &peer_state.actions_blocking_raa_monitor_updates, funding_txo, msg.channel_id, - *counterparty_node_id) - } else { false }; + let mon_update_blocked = self.raa_monitor_updates_held( + &peer_state.actions_blocking_raa_monitor_updates, msg.channel_id, + *counterparty_node_id); let (htlcs_to_fail, monitor_update_opt) = try_channel_entry!(self, peer_state, chan.revoke_and_ack(&msg, &self.fee_estimator, &&logger, mon_update_blocked), chan_entry); if let Some(monitor_update) = monitor_update_opt { @@ -11132,6 +11363,22 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ } } }, + MonitorEvent::CommitmentTxConfirmed(_) => { + let per_peer_state = self.per_peer_state.read().unwrap(); + if let Some(peer_state_mutex) = per_peer_state.get(&counterparty_node_id) { + let mut peer_state_lock = peer_state_mutex.lock().unwrap(); + let peer_state = &mut *peer_state_lock; + if let hash_map::Entry::Occupied(chan_entry) = + peer_state.channel_by_id.entry(channel_id) + { + let reason = ClosureReason::CommitmentTxConfirmed; + let err = ChannelError::Close((reason.to_string(), reason)); + let mut chan = chan_entry.remove(); + let (_, e) = convert_channel_err!(self, peer_state, err, &mut chan); + failed_channels.push((Err(e), counterparty_node_id)); + } + } + }, MonitorEvent::Completed { channel_id, monitor_update_id, .. } => { self.channel_monitor_updated( &channel_id, @@ -11463,7 +11710,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ &self.logger, Some(*counterparty_node_id), Some(*channel_id), None ); - match chan.propose_quiescence(&&logger) { + match chan.propose_quiescence(&&logger, QuiescentAction::DoNothing) { Ok(None) => {}, Ok(Some(stfu)) => { peer_state.pending_msg_events.push(MessageSendEvent::SendStfu { @@ -11856,9 +12103,9 @@ where L::Target: Logger, { #[cfg(not(c_bindings))] - create_offer_builder!(self, OfferBuilder); + create_offer_builder!(self, OfferBuilder<'_, DerivedMetadata, secp256k1::All>); #[cfg(not(c_bindings))] - create_refund_builder!(self, RefundBuilder); + create_refund_builder!(self, RefundBuilder<'_, secp256k1::All>); #[cfg(c_bindings)] create_offer_builder!(self, OfferWithDerivedMetadataBuilder); @@ -11870,7 +12117,6 @@ where /// interactively building a [`StaticInvoice`] with the static invoice server. /// /// Useful for posting offers to receive payments later, such as posting an offer on a website. - #[cfg(async_payments)] pub fn get_async_receive_offer(&self) -> Result { let (offer, needs_persist) = self.flow.get_async_receive_offer()?; if needs_persist { @@ -11881,79 +12127,17 @@ where Ok(offer) } - /// Create an offer for receiving async payments as an often-offline recipient. - /// - /// Instead of using this method, it is preferable to call - /// [`Self::set_paths_to_static_invoice_server`] and retrieve the automatically built offer via - /// [`Self::get_async_receive_offer`]. - /// - /// If you want to build the [`StaticInvoice`] manually using this method instead, you MUST: - /// 1. Provide at least 1 [`BlindedMessagePath`] terminating at an always-online node that will - /// serve the [`StaticInvoice`] created from this offer on our behalf. - /// 2. Use [`Self::create_static_invoice_builder`] to create a [`StaticInvoice`] from this - /// [`Offer`] plus the returned [`Nonce`], and provide the static invoice to the - /// aforementioned always-online node. - #[cfg(async_payments)] - pub fn create_async_receive_offer_builder( - &self, message_paths_to_always_online_node: Vec, - ) -> Result<(OfferBuilder, Nonce), Bolt12SemanticError> { - let entropy = &*self.entropy_source; - self.flow.create_async_receive_offer_builder(entropy, message_paths_to_always_online_node) - } - - /// Creates a [`StaticInvoiceBuilder`] from the corresponding [`Offer`] and [`Nonce`] that were - /// created via [`Self::create_async_receive_offer_builder`]. If `relative_expiry` is unset, the - /// invoice's expiry will default to [`STATIC_INVOICE_DEFAULT_RELATIVE_EXPIRY`]. - /// - /// Instead of using this method to manually build the invoice, it is preferable to set - /// [`Self::set_paths_to_static_invoice_server`] and retrieve the automatically built offer via - /// [`Self::get_async_receive_offer`]. - #[cfg(async_payments)] - pub fn create_static_invoice_builder<'a>( - &self, offer: &'a Offer, offer_nonce: Nonce, relative_expiry: Option, - ) -> Result, Bolt12SemanticError> { - let entropy = &*self.entropy_source; - let amount_msat = offer.amount().and_then(|amount| match amount { - Amount::Bitcoin { amount_msats } => Some(amount_msats), - Amount::Currency { .. } => None, - }); - - let relative_expiry = relative_expiry.unwrap_or(STATIC_INVOICE_DEFAULT_RELATIVE_EXPIRY); - let relative_expiry_secs: u32 = relative_expiry.as_secs().try_into().unwrap_or(u32::MAX); - - let created_at = self.duration_since_epoch(); - let payment_secret = inbound_payment::create_for_spontaneous_payment( - &self.inbound_payment_key, - amount_msat, - relative_expiry_secs, - created_at.as_secs(), - None, - ) - .map_err(|()| Bolt12SemanticError::InvalidAmount)?; - - self.flow.create_static_invoice_builder( - &self.router, - entropy, - offer, - offer_nonce, - payment_secret, - relative_expiry_secs, - self.list_usable_channels(), - self.get_peers_for_blinded_path(), - ) - } - /// Sets the [`BlindedMessagePath`]s that we will use as an async recipient to interactively build /// [`Offer`]s with a static invoice server, so the server can serve [`StaticInvoice`]s to payers /// on our behalf when we're offline. /// /// This method only needs to be called once when the server first takes on the recipient as a /// client, or when the paths change, e.g. if the paths are set to expire at a particular time. - #[cfg(async_payments)] pub fn set_paths_to_static_invoice_server( &self, paths_to_static_invoice_server: Vec, ) -> Result<(), ()> { - self.flow.set_paths_to_static_invoice_server(paths_to_static_invoice_server)?; + let peers = self.get_peers_for_blinded_path(); + self.flow.set_paths_to_static_invoice_server(paths_to_static_invoice_server, peers)?; let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); Ok(()) @@ -12354,7 +12538,6 @@ where /// The provided `recipient_id` must uniquely identify the recipient, and will be surfaced later /// when the recipient provides us with a static invoice to persist and serve to payers on their /// behalf. - #[cfg(async_payments)] pub fn blinded_paths_for_async_recipient( &self, recipient_id: Vec, relative_expiry: Option, ) -> Result, ()> { @@ -12362,7 +12545,6 @@ where self.flow.blinded_paths_for_async_recipient(recipient_id, relative_expiry, peers) } - #[cfg(any(test, async_payments))] pub(super) fn duration_since_epoch(&self) -> Duration { #[cfg(not(feature = "std"))] let now = Duration::from_secs(self.highest_seen_timestamp.load(Ordering::Acquire) as u64); @@ -12394,7 +12576,12 @@ where .collect::>() } - #[cfg(all(test, async_payments))] + #[cfg(test)] + pub(super) fn test_get_peers_for_blinded_path(&self) -> Vec { + self.get_peers_for_blinded_path() + } + + #[cfg(test)] /// Creates multi-hop blinded payment paths for the given `amount_msats` by delegating to /// [`Router::create_blinded_payment_paths`]. pub(super) fn test_create_blinded_payment_paths( @@ -12563,10 +12750,10 @@ where /// operation. It will double-check that nothing *else* is also blocking the same channel from /// making progress and then let any blocked [`ChannelMonitorUpdate`]s fly. #[rustfmt::skip] - fn handle_monitor_update_release(&self, counterparty_node_id: PublicKey, - channel_funding_outpoint: OutPoint, channel_id: ChannelId, - mut completed_blocker: Option) { - + fn handle_monitor_update_release( + &self, counterparty_node_id: PublicKey, channel_id: ChannelId, + mut completed_blocker: Option, + ) { let logger = WithContext::from( &self.logger, Some(counterparty_node_id), Some(channel_id), None ); @@ -12585,7 +12772,7 @@ where } if self.raa_monitor_updates_held(&peer_state.actions_blocking_raa_monitor_updates, - channel_funding_outpoint, channel_id, counterparty_node_id) { + channel_id, counterparty_node_id) { // Check that, while holding the peer lock, we don't have anything else // blocking monitor updates for this channel. If we do, release the monitor // update(s) when those blockers complete. @@ -12597,7 +12784,7 @@ where if let hash_map::Entry::Occupied(mut chan_entry) = peer_state.channel_by_id.entry( channel_id) { if let Some(chan) = chan_entry.get_mut().as_funded_mut() { - debug_assert_eq!(chan.funding.get_funding_txo().unwrap(), channel_funding_outpoint); + let channel_funding_outpoint = chan.funding_outpoint(); if let Some((monitor_update, further_update_exists)) = chan.unblock_next_blocked_monitor_update() { log_debug!(logger, "Unlocking monitor updating for channel {} and updating monitor", channel_id); @@ -12627,16 +12814,11 @@ where for action in actions { match action { EventCompletionAction::ReleaseRAAChannelMonitorUpdate { - channel_funding_outpoint, + channel_funding_outpoint: _, channel_id, counterparty_node_id, } => { - self.handle_monitor_update_release( - counterparty_node_id, - channel_funding_outpoint, - channel_id, - None, - ); + self.handle_monitor_update_release(counterparty_node_id, channel_id, None); }, } } @@ -12896,9 +13078,8 @@ where // While we usually refresh the AsyncReceiveOfferCache on a timer, we also want to start // interactively building offers as soon as we can after startup. We can't start building offers - // until we have some peer connection(s) to send onion messages over, so as a minor optimization + // until we have some peer connection(s) to receive onion messages over, so as a minor optimization // refresh the cache when a peer connects. - #[cfg(async_payments)] self.check_refresh_async_receive_offer_cache(false); res } @@ -13451,7 +13632,7 @@ where htlc_id: htlc.prev_htlc_id, incoming_packet_shared_secret: htlc.forward_info.incoming_shared_secret, phantom_shared_secret: None, - counterparty_node_id: htlc.prev_counterparty_node_id, + counterparty_node_id: Some(htlc.prev_counterparty_node_id), outpoint: htlc.prev_funding_outpoint, channel_id: htlc.prev_channel_id, blinded_failure: htlc.forward_info.routing.blinded_failure(), @@ -14170,7 +14351,6 @@ where log_trace!($logger, "Failed paying invoice: {:?}", e); InvoiceError::from_string(format!("{:?}", e)) }, - #[cfg(async_payments)] Err(Bolt12PaymentError::BlindedPathCreationFailed) => { let err_msg = "Failed to create a blinded path back to ourselves"; log_trace!($logger, "{}", err_msg); @@ -14200,12 +14380,9 @@ where let invoice_request = match self.flow.verify_invoice_request(invoice_request, context) { Ok(InvreqResponseInstructions::SendInvoice(invoice_request)) => invoice_request, - Ok(InvreqResponseInstructions::SendStaticInvoice { - recipient_id: _recipient_id, invoice_id: _invoice_id - }) => { - #[cfg(async_payments)] + Ok(InvreqResponseInstructions::SendStaticInvoice { recipient_id, invoice_slot }) => { self.pending_events.lock().unwrap().push_back((Event::StaticInvoiceRequested { - recipient_id: _recipient_id, invoice_id: _invoice_id, reply_path: responder + recipient_id, invoice_slot, reply_path: responder }, None)); return None @@ -14268,7 +14445,6 @@ where let res = self.send_payment_for_verified_bolt12_invoice(&invoice, payment_id); handle_pay_invoice_res!(res, invoice, logger); }, - #[cfg(async_payments)] OffersMessage::StaticInvoice(invoice) => { let payment_id = match context { Some(OffersContext::OutboundPayment { payment_id, .. }) => payment_id, @@ -14328,130 +14504,102 @@ where L::Target: Logger, { fn handle_offer_paths_request( - &self, _message: OfferPathsRequest, _context: AsyncPaymentsContext, - _responder: Option, + &self, message: OfferPathsRequest, context: AsyncPaymentsContext, + responder: Option, ) -> Option<(OfferPaths, ResponseInstruction)> { - #[cfg(async_payments)] - { - let peers = self.get_peers_for_blinded_path(); - let entropy = &*self.entropy_source; - let (message, reply_path_context) = - match self.flow.handle_offer_paths_request(_context, peers, entropy) { - Some(msg) => msg, - None => return None, - }; - _responder.map(|resp| (message, resp.respond_with_reply_path(reply_path_context))) - } - - #[cfg(not(async_payments))] - None + let peers = self.get_peers_for_blinded_path(); + let (message, reply_path_context) = + match self.flow.handle_offer_paths_request(&message, context, peers) { + Some(msg) => msg, + None => return None, + }; + responder.map(|resp| (message, resp.respond_with_reply_path(reply_path_context))) } fn handle_offer_paths( - &self, _message: OfferPaths, _context: AsyncPaymentsContext, _responder: Option, + &self, message: OfferPaths, context: AsyncPaymentsContext, responder: Option, ) -> Option<(ServeStaticInvoice, ResponseInstruction)> { - #[cfg(async_payments)] - { - let responder = match _responder { - Some(responder) => responder, - None => return None, - }; - let (serve_static_invoice, reply_context) = match self.flow.handle_offer_paths( - _message, - _context, - responder.clone(), - self.get_peers_for_blinded_path(), - self.list_usable_channels(), - &*self.entropy_source, - &*self.router, - ) { - Some((msg, ctx)) => (msg, ctx), - None => return None, - }; - - // We cached a new pending offer, so persist the cache. - let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); + let responder = match responder { + Some(responder) => responder, + None => return None, + }; + let (serve_static_invoice, reply_context) = match self.flow.handle_offer_paths( + message, + context, + responder.clone(), + self.get_peers_for_blinded_path(), + self.list_usable_channels(), + &*self.entropy_source, + &*self.router, + ) { + Some((msg, ctx)) => (msg, ctx), + None => return None, + }; - let response_instructions = responder.respond_with_reply_path(reply_context); - return Some((serve_static_invoice, response_instructions)); - } + // We cached a new pending offer, so persist the cache. + let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); - #[cfg(not(async_payments))] - return None; + let response_instructions = responder.respond_with_reply_path(reply_context); + return Some((serve_static_invoice, response_instructions)); } fn handle_serve_static_invoice( - &self, _message: ServeStaticInvoice, _context: AsyncPaymentsContext, - _responder: Option, + &self, message: ServeStaticInvoice, context: AsyncPaymentsContext, + responder: Option, ) { - #[cfg(async_payments)] - { - let responder = match _responder { - Some(resp) => resp, - None => return, - }; + let responder = match responder { + Some(resp) => resp, + None => return, + }; - let (recipient_id, invoice_id) = - match self.flow.verify_serve_static_invoice_message(&_message, _context) { - Ok((recipient_id, inv_id)) => (recipient_id, inv_id), - Err(()) => return, - }; + let (recipient_id, invoice_slot) = + match self.flow.verify_serve_static_invoice_message(&message, context) { + Ok((recipient_id, inv_slot)) => (recipient_id, inv_slot), + Err(()) => return, + }; - let mut pending_events = self.pending_events.lock().unwrap(); - pending_events.push_back(( - Event::PersistStaticInvoice { - invoice: _message.invoice, - invoice_slot: _message.invoice_slot, - recipient_id, - invoice_id, - invoice_persisted_path: responder, - }, - None, - )); - } + let mut pending_events = self.pending_events.lock().unwrap(); + pending_events.push_back(( + Event::PersistStaticInvoice { + invoice: message.invoice, + invoice_slot, + recipient_id, + invoice_persisted_path: responder, + }, + None, + )); } fn handle_static_invoice_persisted( - &self, _message: StaticInvoicePersisted, _context: AsyncPaymentsContext, + &self, _message: StaticInvoicePersisted, context: AsyncPaymentsContext, ) { - #[cfg(async_payments)] - { - let should_persist = self.flow.handle_static_invoice_persisted(_context); - if should_persist { - let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); - } + let should_persist = self.flow.handle_static_invoice_persisted(context); + if should_persist { + let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); } } fn handle_held_htlc_available( - &self, _message: HeldHtlcAvailable, _context: AsyncPaymentsContext, - _responder: Option, + &self, _message: HeldHtlcAvailable, context: AsyncPaymentsContext, + responder: Option, ) -> Option<(ReleaseHeldHtlc, ResponseInstruction)> { - #[cfg(async_payments)] - { - self.flow.verify_inbound_async_payment_context(_context).ok()?; - return _responder.map(|responder| (ReleaseHeldHtlc {}, responder.respond())); - } - #[cfg(not(async_payments))] - return None; + self.flow.verify_inbound_async_payment_context(context).ok()?; + return responder.map(|responder| (ReleaseHeldHtlc {}, responder.respond())); } - fn handle_release_held_htlc(&self, _message: ReleaseHeldHtlc, _context: AsyncPaymentsContext) { - #[cfg(async_payments)] - { - let payment_id = match _context { - AsyncPaymentsContext::OutboundPayment { payment_id } => payment_id, - _ => return, - }; + fn handle_release_held_htlc(&self, _message: ReleaseHeldHtlc, context: AsyncPaymentsContext) { + let payment_id = match context { + AsyncPaymentsContext::OutboundPayment { payment_id } => payment_id, + _ => return, + }; - if let Err(e) = self.send_payment_for_static_invoice(payment_id) { - log_trace!( - self.logger, - "Failed to release held HTLC with payment id {}: {:?}", - payment_id, - e - ); - } + if let Err(e) = self.send_payment_for_static_invoice(payment_id) { + log_trace!( + self.logger, + "Failed to release held HTLC with payment id {}: {:?}", + payment_id, + e + ); } } @@ -14962,7 +15110,7 @@ impl_writeable_tlv_based!(PendingAddHTLCInfo, { // Note that by the time we get past the required read for type 6 above, prev_funding_outpoint will be // filled in, so we can safely unwrap it here. (7, prev_channel_id, (default_value, ChannelId::v1_from_funding_outpoint(prev_funding_outpoint.0.unwrap()))), - (9, prev_counterparty_node_id, option), + (9, prev_counterparty_node_id, required), }); impl Writeable for HTLCForwardInfo { @@ -15291,7 +15439,7 @@ where (15, self.inbound_payment_id_secret, required), (17, in_flight_monitor_updates, option), (19, peer_storage_dir, optional_vec), - (21, self.flow.writeable_async_receive_offer_cache(), required), + (21, WithoutLength(&self.flow.writeable_async_receive_offer_cache()), required), }); Ok(()) @@ -16410,7 +16558,9 @@ where // `ChannelMonitor` is removed. let compl_action = EventCompletionAction::ReleaseRAAChannelMonitorUpdate { - channel_funding_outpoint: monitor.get_funding_txo(), + channel_funding_outpoint: Some( + monitor.get_funding_txo(), + ), channel_id: monitor.channel_id(), counterparty_node_id: path.hops[0].pubkey, }; @@ -16913,13 +17063,7 @@ where let mut channels_without_preimage = payment_claim .mpp_parts .iter() - .map(|htlc_info| { - ( - htlc_info.counterparty_node_id, - htlc_info.funding_txo, - htlc_info.channel_id, - ) - }) + .map(|htlc_info| (htlc_info.counterparty_node_id, htlc_info.channel_id)) .collect::>(); // If we have multiple MPP parts which were received over the same channel, // we only track it once as once we get a preimage durably in the @@ -17105,6 +17249,10 @@ where onion_fields: payment.onion_fields, payment_id: Some(payment_id), }, + // Note that we don't bother adding a EventCompletionAction here to + // ensure the `PaymentClaimed` event is durable processed as this + // should only be hit for particularly old channels and we don't have + // enough information to generate such an action. None, )); } @@ -17388,108 +17536,6 @@ mod tests { } } - #[test] - #[rustfmt::skip] - fn test_peer_storage() { - let chanmon_cfgs = create_chanmon_cfgs(2); - let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); - let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); - let nodes = create_network(2, &node_cfgs, &node_chanmgrs); - - create_announced_chan_between_nodes(&nodes, 0, 1); - - let peer_storage_msg_events_node0 = nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_msg_events(); - let peer_storage_msg_events_node1 = nodes[1].chain_monitor.chain_monitor.get_and_clear_pending_msg_events(); - assert_ne!(peer_storage_msg_events_node0.len(), 0); - assert_ne!(peer_storage_msg_events_node1.len(), 0); - - match peer_storage_msg_events_node0[0] { - MessageSendEvent::SendPeerStorage { ref node_id, ref msg } => { - assert_eq!(*node_id, nodes[1].node.get_our_node_id()); - nodes[1].node.handle_peer_storage(nodes[0].node.get_our_node_id(), msg.clone()); - } - _ => panic!("Unexpected event"), - } - - match peer_storage_msg_events_node1[0] { - MessageSendEvent::SendPeerStorage { ref node_id, ref msg } => { - assert_eq!(*node_id, nodes[0].node.get_our_node_id()); - nodes[0].node.handle_peer_storage(nodes[1].node.get_our_node_id(), msg.clone()); - } - _ => panic!("Unexpected event"), - } - - nodes[0].node.peer_disconnected(nodes[1].node.get_our_node_id()); - nodes[1].node.peer_disconnected(nodes[0].node.get_our_node_id()); - - nodes[0].node.peer_connected(nodes[1].node.get_our_node_id(), &msgs::Init { - features: nodes[1].node.init_features(), networks: None, remote_network_address: None - }, true).unwrap(); - nodes[1].node.peer_connected(nodes[0].node.get_our_node_id(), &msgs::Init { - features: nodes[0].node.init_features(), networks: None, remote_network_address: None - }, false).unwrap(); - - let node_1_events = nodes[1].node.get_and_clear_pending_msg_events(); - assert_eq!(node_1_events.len(), 2); - - let node_0_events = nodes[0].node.get_and_clear_pending_msg_events(); - assert_eq!(node_0_events.len(), 2); - - for msg in node_1_events{ - if let MessageSendEvent::SendChannelReestablish { ref node_id, ref msg } = msg { - nodes[0].node.handle_channel_reestablish(nodes[1].node.get_our_node_id(), msg); - assert_eq!(*node_id, nodes[0].node.get_our_node_id()); - } else if let MessageSendEvent::SendPeerStorageRetrieval { ref node_id, ref msg } = msg { - nodes[0].node.handle_peer_storage_retrieval(nodes[1].node.get_our_node_id(), msg.clone()); - assert_eq!(*node_id, nodes[0].node.get_our_node_id()); - } else { - panic!("Unexpected event") - } - } - - for msg in node_0_events{ - if let MessageSendEvent::SendChannelReestablish { ref node_id, ref msg } = msg { - nodes[1].node.handle_channel_reestablish(nodes[0].node.get_our_node_id(), msg); - assert_eq!(*node_id, nodes[1].node.get_our_node_id()); - } else if let MessageSendEvent::SendPeerStorageRetrieval { ref node_id, ref msg } = msg { - nodes[1].node.handle_peer_storage_retrieval(nodes[0].node.get_our_node_id(), msg.clone()); - assert_eq!(*node_id, nodes[1].node.get_our_node_id()); - } else { - panic!("Unexpected event") - } - } - - let node_1_msg_events = nodes[1].node.get_and_clear_pending_msg_events(); - let node_0_msg_events = nodes[0].node.get_and_clear_pending_msg_events(); - - assert_eq!(node_1_msg_events.len(), 3); - assert_eq!(node_0_msg_events.len(), 3); - - for msg in node_1_msg_events { - if let MessageSendEvent::SendChannelReady { ref node_id, .. } = msg { - assert_eq!(*node_id, nodes[0].node.get_our_node_id()); - } else if let MessageSendEvent::SendAnnouncementSignatures { ref node_id, .. } = msg { - assert_eq!(*node_id, nodes[0].node.get_our_node_id()); - } else if let MessageSendEvent::SendChannelUpdate { ref node_id, .. } = msg { - assert_eq!(*node_id, nodes[0].node.get_our_node_id()); - } else { - panic!("Unexpected event") - } - } - - for msg in node_0_msg_events { - if let MessageSendEvent::SendChannelReady { ref node_id, .. } = msg { - assert_eq!(*node_id, nodes[1].node.get_our_node_id()); - } else if let MessageSendEvent::SendAnnouncementSignatures { ref node_id, .. } = msg { - assert_eq!(*node_id, nodes[1].node.get_our_node_id()); - } else if let MessageSendEvent::SendChannelUpdate { ref node_id, .. } = msg { - assert_eq!(*node_id, nodes[1].node.get_our_node_id()); - } else { - panic!("Unexpected event") - } - } - } - #[test] #[rustfmt::skip] fn test_keysend_dup_payment_hash() { diff --git a/lightning/src/ln/dual_funding_tests.rs b/lightning/src/ln/dual_funding_tests.rs index 39cf6200765..a91e04bbd82 100644 --- a/lightning/src/ln/dual_funding_tests.rs +++ b/lightning/src/ln/dual_funding_tests.rs @@ -19,11 +19,11 @@ use { crate::ln::channel::PendingV2Channel, crate::ln::channel_keys::{DelayedPaymentBasepoint, HtlcBasepoint, RevocationBasepoint}, crate::ln::functional_test_utils::*, + crate::ln::funding::FundingTxInput, crate::ln::msgs::{BaseMessageHandler, ChannelMessageHandler, MessageSendEvent}, crate::ln::msgs::{CommitmentSigned, TxAddInput, TxAddOutput, TxComplete, TxSignatures}, crate::ln::types::ChannelId, crate::prelude::*, - crate::util::ser::TransactionU16LenLimited, crate::util::test_utils, bitcoin::Witness, }; @@ -49,10 +49,7 @@ fn do_test_v2_channel_establishment(session: V2ChannelEstablishmentTestSession) let initiator_funding_inputs: Vec<_> = create_dual_funding_utxos_with_prev_txs( &nodes[0], &[session.initiator_input_value_satoshis], - ) - .into_iter() - .map(|(txin, tx, _)| (txin, TransactionU16LenLimited::new(tx).unwrap())) - .collect(); + ); // Alice creates a dual-funded channel as initiator. let funding_satoshis = session.funding_input_sats; @@ -86,15 +83,16 @@ fn do_test_v2_channel_establishment(session: V2ChannelEstablishmentTestSession) &RevocationBasepoint::from(open_channel_v2_msg.common_fields.revocation_basepoint), ); + let FundingTxInput { sequence, prevtx, .. } = &initiator_funding_inputs[0]; let tx_add_input_msg = TxAddInput { channel_id, serial_id: 2, // Even serial_id from initiator. - prevtx: Some(initiator_funding_inputs[0].1.clone()), + prevtx: Some(prevtx.clone()), prevtx_out: 0, - sequence: initiator_funding_inputs[0].0.sequence.0, + sequence: sequence.0, shared_input_txid: None, }; - let input_value = tx_add_input_msg.prevtx.as_ref().unwrap().as_transaction().output + let input_value = tx_add_input_msg.prevtx.as_ref().unwrap().output [tx_add_input_msg.prevtx_out as usize] .value; assert_eq!(input_value.to_sat(), session.initiator_input_value_satoshis); diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index 00e883e8561..2c839c4d252 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -26,6 +26,7 @@ use crate::ln::channelmanager::{ AChannelManager, ChainParameters, ChannelManager, ChannelManagerReadArgs, PaymentId, RAACommitmentOrder, RecipientOnionFields, MIN_CLTV_EXPIRY_DELTA, }; +use crate::ln::funding::FundingTxInput; use crate::ln::msgs; use crate::ln::msgs::{ BaseMessageHandler, ChannelMessageHandler, MessageSendEvent, RoutingMessageHandler, @@ -61,13 +62,11 @@ use bitcoin::pow::CompactTarget; use bitcoin::script::ScriptBuf; use bitcoin::secp256k1::{PublicKey, SecretKey}; use bitcoin::transaction::{self, Version as TxVersion}; -use bitcoin::transaction::{Sequence, Transaction, TxIn, TxOut}; -use bitcoin::witness::Witness; -use bitcoin::{WPubkeyHash, Weight}; +use bitcoin::transaction::{Transaction, TxIn, TxOut}; +use bitcoin::WPubkeyHash; use crate::io; use crate::prelude::*; -use crate::sign::P2WPKH_WITNESS_WEIGHT; use crate::sync::{Arc, LockTestExt, Mutex, RwLock}; use alloc::rc::Rc; use core::cell::RefCell; @@ -718,7 +717,7 @@ pub trait NodeHolder { ::MR, ::L, >; - fn chain_monitor(&self) -> Option<&test_utils::TestChainMonitor>; + fn chain_monitor(&self) -> Option<&test_utils::TestChainMonitor<'_>>; } impl NodeHolder for &H { type CM = H::CM; @@ -737,7 +736,7 @@ impl NodeHolder for &H { > { (*self).node() } - fn chain_monitor(&self) -> Option<&test_utils::TestChainMonitor> { + fn chain_monitor(&self) -> Option<&test_utils::TestChainMonitor<'_>> { (*self).chain_monitor() } } @@ -746,7 +745,7 @@ impl<'a, 'b: 'a, 'c: 'b> NodeHolder for Node<'a, 'b, 'c> { fn node(&self) -> &TestChannelManager<'b, 'c> { &self.node } - fn chain_monitor(&self) -> Option<&test_utils::TestChainMonitor> { + fn chain_monitor(&self) -> Option<&test_utils::TestChainMonitor<'_>> { Some(self.chain_monitor) } } @@ -1440,7 +1439,7 @@ fn internal_create_funding_transaction<'a, 'b, 'c>( /// Return the inputs (with prev tx), and the total witness weight for these inputs pub fn create_dual_funding_utxos_with_prev_txs( node: &Node<'_, '_, '_>, utxo_values_in_satoshis: &[u64], -) -> Vec<(TxIn, Transaction, Weight)> { +) -> Vec { // Ensure we have unique transactions per node by using the locktime. let tx = Transaction { version: TxVersion::TWO, @@ -1460,22 +1459,12 @@ pub fn create_dual_funding_utxos_with_prev_txs( .collect(), }; - let mut inputs = vec![]; - for i in 0..utxo_values_in_satoshis.len() { - inputs.push(( - TxIn { - previous_output: OutPoint { txid: tx.compute_txid(), index: i as u16 } - .into_bitcoin_outpoint(), - script_sig: ScriptBuf::new(), - sequence: Sequence::ZERO, - witness: Witness::new(), - }, - tx.clone(), - Weight::from_wu(P2WPKH_WITNESS_WEIGHT), - )); - } - - inputs + tx.output + .iter() + .enumerate() + .map(|(index, _)| index as u32) + .map(|vout| FundingTxInput::new_p2wpkh(tx.clone(), vout).unwrap()) + .collect() } pub fn sign_funding_transaction<'a, 'b, 'c>( @@ -2211,21 +2200,37 @@ macro_rules! check_closed_event { }; } -pub fn handle_bump_htlc_event(node: &Node, count: usize) { +pub fn handle_bump_events(node: &Node, expected_close: bool, expected_htlc_count: usize) { let events = node.chain_monitor.chain_monitor.get_and_clear_pending_events(); - assert_eq!(events.len(), count); - for event in events { + let mut close = false; + let mut htlc_count = 0; + for event in &events { match event { - Event::BumpTransaction(bump_event) => { - if let BumpTransactionEvent::HTLCResolution { .. } = &bump_event { - } else { - panic!(); - } - node.bump_tx_handler.handle_event(&bump_event); + Event::BumpTransaction(bump @ BumpTransactionEvent::ChannelClose { .. }) => { + close = true; + node.bump_tx_handler.handle_event(&bump); }, - _ => panic!(), + Event::BumpTransaction(bump @ BumpTransactionEvent::HTLCResolution { .. }) => { + htlc_count += 1; + node.bump_tx_handler.handle_event(&bump); + }, + _ => panic!("Unexpected non-bump event: {:?}.", event), } } + assert_eq!(close, expected_close, "Expected a bump close event, found {:?}.", events); + assert_eq!( + htlc_count, expected_htlc_count, + "Expected {} bump HTLC events, found {:?}", + expected_htlc_count, events + ); +} + +pub fn handle_bump_close_event(node: &Node) { + handle_bump_events(node, true, 0); +} + +pub fn handle_bump_htlc_event(node: &Node, count: usize) { + handle_bump_events(node, false, count); } pub fn close_channel<'a, 'b, 'c>( diff --git a/lightning/src/ln/functional_tests.rs b/lightning/src/ln/functional_tests.rs index 388db6d174f..5e78049664c 100644 --- a/lightning/src/ln/functional_tests.rs +++ b/lightning/src/ln/functional_tests.rs @@ -15,13 +15,12 @@ use crate::chain; use crate::chain::chaininterface::LowerBoundedFeeEstimator; use crate::chain::channelmonitor; use crate::chain::channelmonitor::{ - Balance, ChannelMonitorUpdateStep, ANTI_REORG_DELAY, CLTV_CLAIM_BUFFER, - COUNTERPARTY_CLAIMABLE_WITHIN_BLOCKS_PINNABLE, LATENCY_GRACE_PERIOD_BLOCKS, + Balance, ANTI_REORG_DELAY, CLTV_CLAIM_BUFFER, COUNTERPARTY_CLAIMABLE_WITHIN_BLOCKS_PINNABLE, + LATENCY_GRACE_PERIOD_BLOCKS, }; -use crate::chain::transaction::OutPoint; use crate::chain::{ChannelMonitorUpdateStatus, Confirm, Listen, Watch}; use crate::events::{ - ClosureReason, Event, FundingInfo, HTLCHandlingFailureType, PathFailure, PaymentFailureReason, + ClosureReason, Event, HTLCHandlingFailureType, PathFailure, PaymentFailureReason, PaymentPurpose, }; use crate::ln::chan_utils::{ @@ -29,18 +28,16 @@ use crate::ln::chan_utils::{ OFFERED_HTLC_SCRIPT_WEIGHT, }; use crate::ln::channel::{ - get_holder_selected_channel_reserve_satoshis, Channel, ChannelError, InboundV1Channel, - OutboundV1Channel, COINBASE_MATURITY, DISCONNECT_PEER_AWAITING_RESPONSE_TICKS, - MIN_CHAN_DUST_LIMIT_SATOSHIS, + get_holder_selected_channel_reserve_satoshis, Channel, DISCONNECT_PEER_AWAITING_RESPONSE_TICKS, + MIN_CHAN_DUST_LIMIT_SATOSHIS, UNFUNDED_CHANNEL_AGE_LIMIT_TICKS, }; use crate::ln::channelmanager::{ - self, PaymentId, RAACommitmentOrder, RecipientOnionFields, BREAKDOWN_TIMEOUT, - DISABLE_GOSSIP_TICKS, ENABLE_GOSSIP_TICKS, MIN_CLTV_EXPIRY_DELTA, + PaymentId, RAACommitmentOrder, RecipientOnionFields, BREAKDOWN_TIMEOUT, DISABLE_GOSSIP_TICKS, + ENABLE_GOSSIP_TICKS, MIN_CLTV_EXPIRY_DELTA, }; use crate::ln::msgs; use crate::ln::msgs::{ - AcceptChannel, BaseMessageHandler, ChannelMessageHandler, ErrorAction, MessageSendEvent, - RoutingMessageHandler, + BaseMessageHandler, ChannelMessageHandler, ErrorAction, MessageSendEvent, RoutingMessageHandler, }; use crate::ln::onion_utils::LocalHTLCFailureReason; use crate::ln::types::ChannelId; @@ -53,24 +50,19 @@ use crate::sign::{EntropySource, OutputSpender, SignerProvider}; use crate::types::features::{ChannelFeatures, ChannelTypeFeatures, NodeFeatures}; use crate::types::payment::{PaymentHash, PaymentSecret}; use crate::types::string::UntrustedString; -use crate::util::config::{ - ChannelConfigOverrides, ChannelConfigUpdate, ChannelHandshakeConfigUpdate, MaxDustHTLCExposure, - UserConfig, -}; +use crate::util::config::{ChannelConfigUpdate, MaxDustHTLCExposure, UserConfig}; use crate::util::errors::APIError; use crate::util::ser::{ReadableArgs, Writeable}; use crate::util::test_channel_signer::TestChannelSigner; -use crate::util::test_utils::{self, TestLogger, WatchtowerPersister}; +use crate::util::test_utils::{self, WatchtowerPersister}; -use bitcoin::constants::ChainHash; use bitcoin::hash_types::BlockHash; use bitcoin::locktime::absolute::LockTime; use bitcoin::network::Network; use bitcoin::opcodes; -use bitcoin::script::{Builder, ScriptBuf}; +use bitcoin::script::Builder; use bitcoin::transaction::Version; -use bitcoin::OutPoint as BitcoinOutPoint; -use bitcoin::{Amount, Sequence, Transaction, TxIn, TxOut, Witness}; +use bitcoin::{Amount, Transaction, TxIn, TxOut}; use bitcoin::secp256k1::Secp256k1; use bitcoin::secp256k1::{PublicKey, SecretKey}; @@ -85,391 +77,6 @@ use lightning_macros::xtest; use crate::ln::functional_test_utils::*; -use super::channel::UNFUNDED_CHANNEL_AGE_LIMIT_TICKS; - -#[xtest(feature = "_externalize_tests")] -fn test_channel_resumption_fail_post_funding() { - // If we fail to exchange funding with a peer prior to it disconnecting we'll resume the - // channel open on reconnect, however if we do exchange funding we do not currently support - // replaying it and here test that the channel closes. - let chanmon_cfgs = create_chanmon_cfgs(2); - let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); - let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); - let nodes = create_network(2, &node_cfgs, &node_chanmgrs); - - let node_a_id = nodes[0].node.get_our_node_id(); - let node_b_id = nodes[1].node.get_our_node_id(); - - nodes[0].node.create_channel(node_b_id, 1_000_000, 0, 42, None, None).unwrap(); - let open_chan = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); - nodes[1].node.handle_open_channel(node_a_id, &open_chan); - let accept_chan = get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, node_a_id); - nodes[0].node.handle_accept_channel(node_b_id, &accept_chan); - - let (temp_chan_id, tx, funding_output) = - create_funding_transaction(&nodes[0], &node_b_id, 1_000_000, 42); - let new_chan_id = ChannelId::v1_from_funding_outpoint(funding_output); - nodes[0].node.funding_transaction_generated(temp_chan_id, node_b_id, tx).unwrap(); - - nodes[0].node.peer_disconnected(node_b_id); - check_closed_events( - &nodes[0], - &[ExpectedCloseEvent::from_id_reason(new_chan_id, true, ClosureReason::DisconnectedPeer)], - ); - - // After ddf75afd16 we'd panic on reconnection if we exchanged funding info, so test that - // explicitly here. - let init_msg = msgs::Init { - features: nodes[1].node.init_features(), - networks: None, - remote_network_address: None, - }; - nodes[0].node.peer_connected(node_b_id, &init_msg, true).unwrap(); - assert_eq!(nodes[0].node.get_and_clear_pending_msg_events(), Vec::new()); -} - -#[xtest(feature = "_externalize_tests")] -pub fn test_insane_channel_opens() { - // Stand up a network of 2 nodes - use crate::ln::channel::TOTAL_BITCOIN_SUPPLY_SATOSHIS; - let mut cfg = UserConfig::default(); - cfg.channel_handshake_limits.max_funding_satoshis = TOTAL_BITCOIN_SUPPLY_SATOSHIS + 1; - let chanmon_cfgs = create_chanmon_cfgs(2); - let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); - let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, Some(cfg.clone())]); - let nodes = create_network(2, &node_cfgs, &node_chanmgrs); - - let node_a_id = nodes[0].node.get_our_node_id(); - let node_b_id = nodes[1].node.get_our_node_id(); - - // Instantiate channel parameters where we push the maximum msats given our - // funding satoshis - let channel_value_sat = 31337; // same as funding satoshis - let channel_reserve_satoshis = - get_holder_selected_channel_reserve_satoshis(channel_value_sat, &cfg); - let push_msat = (channel_value_sat - channel_reserve_satoshis) * 1000; - - // Have node0 initiate a channel to node1 with aforementioned parameters - nodes[0].node.create_channel(node_b_id, channel_value_sat, push_msat, 42, None, None).unwrap(); - - // Extract the channel open message from node0 to node1 - let open_channel_message = - get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); - - // Test helper that asserts we get the correct error string given a mutator - // that supposedly makes the channel open message insane - let insane_open_helper = - |expected_error_str: &str, message_mutator: fn(msgs::OpenChannel) -> msgs::OpenChannel| { - let open_channel_mutated = message_mutator(open_channel_message.clone()); - nodes[1].node.handle_open_channel(node_a_id, &open_channel_mutated); - let msg_events = nodes[1].node.get_and_clear_pending_msg_events(); - assert_eq!(msg_events.len(), 1); - let expected_regex = regex::Regex::new(expected_error_str).unwrap(); - if let MessageSendEvent::HandleError { ref action, .. } = msg_events[0] { - match action { - &ErrorAction::SendErrorMessage { .. } => { - nodes[1].logger.assert_log_regex( - "lightning::ln::channelmanager", - expected_regex, - 1, - ); - }, - _ => panic!("unexpected event!"), - } - } else { - assert!(false); - } - }; - - use crate::ln::channelmanager::MAX_LOCAL_BREAKDOWN_TIMEOUT; - - // Test all mutations that would make the channel open message insane - insane_open_helper( - format!( - "Per our config, funding must be at most {}. It was {}", - TOTAL_BITCOIN_SUPPLY_SATOSHIS + 1, - TOTAL_BITCOIN_SUPPLY_SATOSHIS + 2 - ) - .as_str(), - |mut msg| { - msg.common_fields.funding_satoshis = TOTAL_BITCOIN_SUPPLY_SATOSHIS + 2; - msg - }, - ); - insane_open_helper( - format!( - "Funding must be smaller than the total bitcoin supply. It was {}", - TOTAL_BITCOIN_SUPPLY_SATOSHIS - ) - .as_str(), - |mut msg| { - msg.common_fields.funding_satoshis = TOTAL_BITCOIN_SUPPLY_SATOSHIS; - msg - }, - ); - - insane_open_helper("Bogus channel_reserve_satoshis", |mut msg| { - msg.channel_reserve_satoshis = msg.common_fields.funding_satoshis + 1; - msg - }); - - insane_open_helper( - r"push_msat \d+ was larger than channel amount minus reserve \(\d+\)", - |mut msg| { - msg.push_msat = - (msg.common_fields.funding_satoshis - msg.channel_reserve_satoshis) * 1000 + 1; - msg - }, - ); - - insane_open_helper("Peer never wants payout outputs?", |mut msg| { - msg.common_fields.dust_limit_satoshis = msg.common_fields.funding_satoshis + 1; - msg - }); - - insane_open_helper( - r"Minimum htlc value \(\d+\) was larger than full channel value \(\d+\)", - |mut msg| { - msg.common_fields.htlc_minimum_msat = - (msg.common_fields.funding_satoshis - msg.channel_reserve_satoshis) * 1000; - msg - }, - ); - - insane_open_helper( - "They wanted our payments to be delayed by a needlessly long period", - |mut msg| { - msg.common_fields.to_self_delay = MAX_LOCAL_BREAKDOWN_TIMEOUT + 1; - msg - }, - ); - - insane_open_helper("0 max_accepted_htlcs makes for a useless channel", |mut msg| { - msg.common_fields.max_accepted_htlcs = 0; - msg - }); - - insane_open_helper("max_accepted_htlcs was 484. It must not be larger than 483", |mut msg| { - msg.common_fields.max_accepted_htlcs = 484; - msg - }); -} - -#[test] -fn test_insane_zero_fee_channel_open() { - let mut cfg = UserConfig::default(); - cfg.manually_accept_inbound_channels = true; - cfg.channel_handshake_config.negotiate_anchor_zero_fee_commitments = true; - - let chanmon_cfgs = create_chanmon_cfgs(2); - let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); - let node_chanmgrs = - create_node_chanmgrs(2, &node_cfgs, &[Some(cfg.clone()), Some(cfg.clone())]); - let nodes = create_network(2, &node_cfgs, &node_chanmgrs); - - let node_a_id = nodes[0].node.get_our_node_id(); - let node_b_id = nodes[1].node.get_our_node_id(); - - nodes[0].node.create_channel(node_b_id, 100_000, 0, 42, None, None).unwrap(); - - let open_channel_message = - get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); - - let insane_open_helper = - |expected_error_str: &str, message_mutator: fn(msgs::OpenChannel) -> msgs::OpenChannel| { - let open_channel_mutated = message_mutator(open_channel_message.clone()); - nodes[1].node.handle_open_channel(node_a_id, &open_channel_mutated); - - let events = nodes[1].node.get_and_clear_pending_events(); - match events[0] { - Event::OpenChannelRequest { temporary_channel_id, .. } => { - match nodes[1].node.accept_inbound_channel( - &temporary_channel_id, - &nodes[0].node.get_our_node_id(), - 23, - None, - ) { - Ok(_) => panic!("Unexpected successful channel accept"), - Err(e) => assert!(format!("{:?}", e).contains(expected_error_str)), - } - }, - _ => panic!("Unexpected event"), - } - - let events = nodes[1].node.get_and_clear_pending_msg_events(); - assert_eq!(events.len(), 1); - assert!(matches!(events[0], MessageSendEvent::HandleError { .. })); - }; - - insane_open_helper( - "max_accepted_htlcs was 115. It must not be larger than 114".into(), - |mut msg| { - msg.common_fields.max_accepted_htlcs = 115; - msg - }, - ); - - insane_open_helper("Zero Fee Channels must never attempt to use a fee".into(), |mut msg| { - msg.common_fields.commitment_feerate_sat_per_1000_weight = 123; - msg - }); -} - -#[xtest(feature = "_externalize_tests")] -pub fn test_funding_exceeds_no_wumbo_limit() { - // Test that if a peer does not support wumbo channels, we'll refuse to open a wumbo channel to - // them. - use crate::ln::channel::MAX_FUNDING_SATOSHIS_NO_WUMBO; - let chanmon_cfgs = create_chanmon_cfgs(2); - let mut node_cfgs = create_node_cfgs(2, &chanmon_cfgs); - let mut features = channelmanager::provided_init_features(&test_default_channel_config()); - features.clear_wumbo(); - *node_cfgs[1].override_init_features.borrow_mut() = Some(features); - let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); - let nodes = create_network(2, &node_cfgs, &node_chanmgrs); - - let node_b_id = nodes[1].node.get_our_node_id(); - - match nodes[0].node.create_channel( - node_b_id, - MAX_FUNDING_SATOSHIS_NO_WUMBO + 1, - 0, - 42, - None, - None, - ) { - Err(APIError::APIMisuseError { err }) => { - let exp_err = format!( - "funding_value must not exceed {}, it was {}", - MAX_FUNDING_SATOSHIS_NO_WUMBO, - MAX_FUNDING_SATOSHIS_NO_WUMBO + 1 - ); - assert_eq!(err, exp_err); - }, - _ => panic!(), - } -} - -fn do_test_sanity_on_in_flight_opens(steps: u8) { - // Previously, we had issues deserializing channels when we hadn't connected the first block - // after creation. To catch that and similar issues, we lean on the Node::drop impl to test - // serialization round-trips and simply do steps towards opening a channel and then drop the - // Node objects. - - let chanmon_cfgs = create_chanmon_cfgs(2); - let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); - let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); - let nodes = create_network(2, &node_cfgs, &node_chanmgrs); - - let node_a_id = nodes[0].node.get_our_node_id(); - let node_b_id = nodes[1].node.get_our_node_id(); - - if steps & 0b1000_0000 != 0 { - let block = create_dummy_block(nodes[0].best_block_hash(), 42, Vec::new()); - connect_block(&nodes[0], &block); - connect_block(&nodes[1], &block); - } - - if steps & 0x0f == 0 { - return; - } - nodes[0].node.create_channel(node_b_id, 100000, 10001, 42, None, None).unwrap(); - let open_channel = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); - - if steps & 0x0f == 1 { - return; - } - nodes[1].node.handle_open_channel(node_a_id, &open_channel); - let accept_channel = get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, node_a_id); - - if steps & 0x0f == 2 { - return; - } - nodes[0].node.handle_accept_channel(node_b_id, &accept_channel); - - let (temporary_channel_id, tx, _) = - create_funding_transaction(&nodes[0], &node_b_id, 100000, 42); - - if steps & 0x0f == 3 { - return; - } - nodes[0] - .node - .funding_transaction_generated(temporary_channel_id, node_b_id, tx.clone()) - .unwrap(); - check_added_monitors(&nodes[0], 0); - let funding_created = get_event_msg!(nodes[0], MessageSendEvent::SendFundingCreated, node_b_id); - - let channel_id = ChannelId::v1_from_funding_txid( - funding_created.funding_txid.as_byte_array(), - funding_created.funding_output_index, - ); - - if steps & 0x0f == 4 { - return; - } - nodes[1].node.handle_funding_created(node_a_id, &funding_created); - { - let mut added_monitors = nodes[1].chain_monitor.added_monitors.lock().unwrap(); - assert_eq!(added_monitors.len(), 1); - assert_eq!(added_monitors[0].0, channel_id); - added_monitors.clear(); - } - expect_channel_pending_event(&nodes[1], &node_a_id); - - let funding_signed = get_event_msg!(nodes[1], MessageSendEvent::SendFundingSigned, node_a_id); - - if steps & 0x0f == 5 { - return; - } - nodes[0].node.handle_funding_signed(node_b_id, &funding_signed); - { - let mut added_monitors = nodes[0].chain_monitor.added_monitors.lock().unwrap(); - assert_eq!(added_monitors.len(), 1); - assert_eq!(added_monitors[0].0, channel_id); - added_monitors.clear(); - } - - expect_channel_pending_event(&nodes[0], &node_b_id); - let events_4 = nodes[0].node.get_and_clear_pending_events(); - assert_eq!(events_4.len(), 0); - - if steps & 0x0f == 6 { - return; - } - create_chan_between_nodes_with_value_confirm_first(&nodes[0], &nodes[1], &tx, 2); - - if steps & 0x0f == 7 { - return; - } - confirm_transaction_at(&nodes[0], &tx, 2); - connect_blocks(&nodes[0], CHAN_CONFIRM_DEPTH); - create_chan_between_nodes_with_value_confirm_second(&nodes[1], &nodes[0]); - expect_channel_ready_event(&nodes[0], &node_b_id); -} - -#[xtest(feature = "_externalize_tests")] -pub fn test_sanity_on_in_flight_opens() { - do_test_sanity_on_in_flight_opens(0); - do_test_sanity_on_in_flight_opens(0 | 0b1000_0000); - do_test_sanity_on_in_flight_opens(1); - do_test_sanity_on_in_flight_opens(1 | 0b1000_0000); - do_test_sanity_on_in_flight_opens(2); - do_test_sanity_on_in_flight_opens(2 | 0b1000_0000); - do_test_sanity_on_in_flight_opens(3); - do_test_sanity_on_in_flight_opens(3 | 0b1000_0000); - do_test_sanity_on_in_flight_opens(4); - do_test_sanity_on_in_flight_opens(4 | 0b1000_0000); - do_test_sanity_on_in_flight_opens(5); - do_test_sanity_on_in_flight_opens(5 | 0b1000_0000); - do_test_sanity_on_in_flight_opens(6); - do_test_sanity_on_in_flight_opens(6 | 0b1000_0000); - do_test_sanity_on_in_flight_opens(7); - do_test_sanity_on_in_flight_opens(7 | 0b1000_0000); - do_test_sanity_on_in_flight_opens(8); - do_test_sanity_on_in_flight_opens(8 | 0b1000_0000); -} - #[xtest(feature = "_externalize_tests")] pub fn fake_network_test() { // Simple test which builds a network of ChannelManagers, connects them to each other, and @@ -713,6 +320,39 @@ pub fn test_duplicate_htlc_different_direction_onchain() { assert_eq!(has_both_htlcs, 2); mine_transaction(&nodes[0], &remote_txn[0]); + let events = nodes[0].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 3); + for e in events { + match e { + MessageSendEvent::BroadcastChannelUpdate { .. } => {}, + MessageSendEvent::HandleError { + node_id, + action: msgs::ErrorAction::SendErrorMessage { ref msg }, + } => { + assert_eq!(node_id, node_b_id); + assert_eq!(msg.data, "Channel closed because commitment or closing transaction was confirmed on chain."); + }, + MessageSendEvent::UpdateHTLCs { + ref node_id, + updates: + msgs::CommitmentUpdate { + ref update_add_htlcs, + ref update_fulfill_htlcs, + ref update_fail_htlcs, + ref update_fail_malformed_htlcs, + .. + }, + .. + } => { + assert!(update_add_htlcs.is_empty()); + assert!(update_fail_htlcs.is_empty()); + assert_eq!(update_fulfill_htlcs.len(), 1); + assert!(update_fail_malformed_htlcs.is_empty()); + assert_eq!(node_b_id, *node_id); + }, + _ => panic!("Unexpected event"), + } + } check_added_monitors(&nodes[0], 1); check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed, [node_b_id], 100000); connect_blocks(&nodes[0], TEST_FINAL_CLTV); // Confirm blocks until the HTLC expires @@ -752,40 +392,6 @@ pub fn test_duplicate_htlc_different_direction_onchain() { remote_txn[0].output[timeout_tx.input[0].previous_output.vout as usize].value.to_sat(), 900 ); - - let events = nodes[0].node.get_and_clear_pending_msg_events(); - assert_eq!(events.len(), 3); - for e in events { - match e { - MessageSendEvent::BroadcastChannelUpdate { .. } => {}, - MessageSendEvent::HandleError { - node_id, - action: msgs::ErrorAction::SendErrorMessage { ref msg }, - } => { - assert_eq!(node_id, node_b_id); - assert_eq!(msg.data, "Channel closed because commitment or closing transaction was confirmed on chain."); - }, - MessageSendEvent::UpdateHTLCs { - ref node_id, - updates: - msgs::CommitmentUpdate { - ref update_add_htlcs, - ref update_fulfill_htlcs, - ref update_fail_htlcs, - ref update_fail_malformed_htlcs, - .. - }, - .. - } => { - assert!(update_add_htlcs.is_empty()); - assert!(update_fail_htlcs.is_empty()); - assert_eq!(update_fulfill_htlcs.len(), 1); - assert!(update_fail_malformed_htlcs.is_empty()); - assert_eq!(node_b_id, *node_id); - }, - _ => panic!("Unexpected event"), - } - } } #[xtest(feature = "_externalize_tests")] @@ -856,7 +462,8 @@ fn do_test_fail_back_before_backwards_timeout(post_fail_back_action: PostFailBac let timeout_blocks = TEST_FINAL_CLTV + LATENCY_GRACE_PERIOD_BLOCKS + 1; connect_blocks(&nodes[1], timeout_blocks); let node_1_txn = test_txn_broadcast(&nodes[1], &chan_2, None, HTLCType::TIMEOUT); - check_closed_event(&nodes[1], 1, ClosureReason::HTLCsTimedOut, false, &[node_c_id], 100_000); + let reason = ClosureReason::HTLCsTimedOut { payment_hash: Some(payment_hash) }; + check_closed_event(&nodes[1], 1, reason, false, &[node_c_id], 100_000); check_closed_broadcast(&nodes[1], 1, true); check_added_monitors(&nodes[1], 1); @@ -910,7 +517,7 @@ fn do_test_fail_back_before_backwards_timeout(post_fail_back_action: PostFailBac connect_blocks(&nodes[2], TEST_FINAL_CLTV - CLTV_CLAIM_BUFFER + 2); let node_2_txn = test_txn_broadcast(&nodes[2], &chan_2, None, HTLCType::SUCCESS); check_closed_broadcast!(nodes[2], true); - let reason = ClosureReason::HTLCsTimedOut; + let reason = ClosureReason::HTLCsTimedOut { payment_hash: Some(payment_hash) }; check_closed_event(&nodes[2], 1, reason, false, &[node_b_id], 100_000); check_added_monitors(&nodes[2], 1); @@ -1002,10 +609,10 @@ pub fn channel_monitor_network_test() { } mine_transaction(&nodes[0], &node_txn[0]); + check_closed_broadcast(&nodes[0], 1, true); check_added_monitors(&nodes[0], 1); test_txn_broadcast(&nodes[0], &chan_1, Some(node_txn[0].clone()), HTLCType::NONE); } - check_closed_broadcast!(nodes[0], true); assert_eq!(nodes[0].node.list_channels().len(), 0); assert_eq!(nodes[1].node.list_channels().len(), 1); check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed, [node_b_id], 100000); @@ -1031,10 +638,10 @@ pub fn channel_monitor_network_test() { ); test_txn_broadcast(&nodes[1], &chan_2, None, HTLCType::TIMEOUT); mine_transaction(&nodes[2], &node_txn[0]); + check_closed_broadcast(&nodes[2], 1, true); check_added_monitors(&nodes[2], 1); test_txn_broadcast(&nodes[2], &chan_2, Some(node_txn[0].clone()), HTLCType::NONE); } - check_closed_broadcast!(nodes[2], true); assert_eq!(nodes[1].node.list_channels().len(), 0); assert_eq!(nodes[2].node.list_channels().len(), 1); let node_b_reason = @@ -1085,10 +692,10 @@ pub fn channel_monitor_network_test() { // Claim the payment on nodes[3], giving it knowledge of the preimage claim_funds!(nodes[3], nodes[2], payment_preimage_1, payment_hash_1); mine_transaction(&nodes[3], &node_txn[0]); + check_closed_broadcast(&nodes[3], 1, true); check_added_monitors(&nodes[3], 1); check_preimage_claim(&nodes[3], &node_txn); } - check_closed_broadcast!(nodes[3], true); assert_eq!(nodes[2].node.list_channels().len(), 0); assert_eq!(nodes[3].node.list_channels().len(), 1); let node_c_reason = @@ -1160,7 +767,8 @@ pub fn channel_monitor_network_test() { } check_added_monitors(&nodes[4], 1); test_txn_broadcast(&nodes[4], &chan_4, None, HTLCType::SUCCESS); - check_closed_event!(nodes[4], 1, ClosureReason::HTLCsTimedOut, [node_d_id], 100000); + let reason = ClosureReason::HTLCsTimedOut { payment_hash: Some(payment_hash_2) }; + check_closed_event!(nodes[4], 1, reason, [node_d_id], 100000); mine_transaction(&nodes[4], &node_txn[0]); check_preimage_claim(&nodes[4], &node_txn); @@ -1177,7 +785,8 @@ pub fn channel_monitor_network_test() { nodes[3].chain_monitor.chain_monitor.watch_channel(chan_3.2, chan_3_mon), Ok(ChannelMonitorUpdateStatus::Completed) ); - check_closed_event!(nodes[3], 1, ClosureReason::HTLCsTimedOut, [node_id_4], 100000); + let reason = ClosureReason::HTLCsTimedOut { payment_hash: Some(payment_hash_2) }; + check_closed_event!(nodes[3], 1, reason, [node_id_4], 100000); } #[xtest(feature = "_externalize_tests")] @@ -1238,8 +847,8 @@ pub fn test_justice_tx_htlc_timeout() { assert_ne!(node_txn[0].input[0].previous_output, node_txn[0].input[1].previous_output); node_txn.clear(); } - check_added_monitors(&nodes[1], 1); check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [node_a_id], 100000); + check_added_monitors(&nodes[1], 1); test_txn_broadcast(&nodes[1], &chan_5, Some(revoked_local_txn[0].clone()), HTLCType::NONE); mine_transaction(&nodes[0], &revoked_local_txn[0]); @@ -1252,8 +861,8 @@ pub fn test_justice_tx_htlc_timeout() { Some(revoked_local_txn[0].clone()), HTLCType::TIMEOUT, ); - check_added_monitors(&nodes[0], 1); check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed, [node_b_id], 100000); + check_added_monitors(&nodes[0], 1); // Broadcast revoked HTLC-timeout on node 1 mine_transaction(&nodes[1], &node_txn[1]); test_revoked_htlc_claim_txn_broadcast( @@ -1314,10 +923,12 @@ pub fn test_justice_tx_htlc_success() { check_spends!(node_txn[0], revoked_local_txn[0]); node_txn.swap_remove(0); } + check_closed_broadcast(&nodes[0], 1, true); check_added_monitors(&nodes[0], 1); test_txn_broadcast(&nodes[0], &chan_6, Some(revoked_local_txn[0].clone()), HTLCType::NONE); mine_transaction(&nodes[1], &revoked_local_txn[0]); + check_closed_broadcast(&nodes[1], 1, true); check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [node_a_id], 100000); let node_txn = test_txn_broadcast( &nodes[1], @@ -1334,7 +945,6 @@ pub fn test_justice_tx_htlc_success() { revoked_local_txn[0].clone(), ); } - get_announce_close_broadcast_events(&nodes, 0, 1); assert_eq!(nodes[0].node.list_channels().len(), 0); assert_eq!(nodes[1].node.list_channels().len(), 0); } @@ -1362,8 +972,8 @@ pub fn revoked_output_claim() { // Inform nodes[1] that nodes[0] broadcast a stale tx mine_transaction(&nodes[1], &revoked_local_txn[0]); - check_added_monitors(&nodes[1], 1); check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [node_a_id], 100000); + check_added_monitors(&nodes[1], 1); let node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().clone(); assert_eq!(node_txn.len(), 1); // ChannelMonitor: justice tx against revoked to_local output @@ -1424,10 +1034,10 @@ fn do_test_forming_justice_tx_from_monitor_updates(broadcast_initial_commitment: mine_transactions(&nodes[1], &[revoked_commitment_tx, &justice_tx]); mine_transactions(&nodes[0], &[revoked_commitment_tx, &justice_tx]); + get_announce_close_broadcast_events(&nodes, 1, 0); check_added_monitors(&nodes[1], 1); let reason = ClosureReason::CommitmentTxConfirmed; check_closed_event(&nodes[1], 1, reason, false, &[node_a_id], 100_000); - get_announce_close_broadcast_events(&nodes, 1, 0); check_added_monitors(&nodes[0], 1); let reason = ClosureReason::CommitmentTxConfirmed; @@ -1495,9 +1105,11 @@ pub fn claim_htlc_outputs() { { mine_transaction(&nodes[0], &revoked_local_txn[0]); + check_closed_broadcast(&nodes[0], 1, true); check_added_monitors(&nodes[0], 1); check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed, [node_b_id], 100000); mine_transaction(&nodes[1], &revoked_local_txn[0]); + check_closed_broadcast(&nodes[1], 1, true); check_added_monitors(&nodes[1], 1); check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [node_a_id], 100000); connect_blocks(&nodes[1], ANTI_REORG_DELAY - 1); @@ -1530,7 +1142,6 @@ pub fn claim_htlc_outputs() { connect_blocks(&nodes[1], ANTI_REORG_DELAY - 1); expect_payment_failed!(nodes[1], payment_hash_2, false); } - get_announce_close_broadcast_events(&nodes, 0, 1); assert_eq!(nodes[0].node.list_channels().len(), 0); assert_eq!(nodes[1].node.list_channels().len(), 0); } @@ -1876,20 +1487,10 @@ pub fn test_htlc_on_chain_success() { let txn = vec![commitment_tx[0].clone(), node_txn[0].clone(), node_txn[1].clone()]; connect_block(&nodes[1], &create_dummy_block(nodes[1].best_block_hash(), 42, txn)); connect_blocks(&nodes[1], TEST_FINAL_CLTV); // Confirm blocks until the HTLC expires - { - let mut added_monitors = nodes[1].chain_monitor.added_monitors.lock().unwrap(); - assert_eq!(added_monitors.len(), 1); - assert_eq!(added_monitors[0].0, chan_2.2); - added_monitors.clear(); - } let forwarded_events = nodes[1].node.get_and_clear_pending_events(); assert_eq!(forwarded_events.len(), 3); - match forwarded_events[0] { - Event::ChannelClosed { reason: ClosureReason::CommitmentTxConfirmed, .. } => {}, - _ => panic!("Unexpected event"), - } let chan_id = Some(chan_1.2); - match forwarded_events[1] { + match forwarded_events[0] { Event::PaymentForwarded { total_fee_earned_msat, prev_channel_id, @@ -1906,7 +1507,7 @@ pub fn test_htlc_on_chain_success() { }, _ => panic!(), } - match forwarded_events[2] { + match forwarded_events[1] { Event::PaymentForwarded { total_fee_earned_msat, prev_channel_id, @@ -1923,12 +1524,17 @@ pub fn test_htlc_on_chain_success() { }, _ => panic!(), } + match forwarded_events[2] { + Event::ChannelClosed { reason: ClosureReason::CommitmentTxConfirmed, .. } => {}, + _ => panic!("Unexpected event"), + } let mut events = nodes[1].node.get_and_clear_pending_msg_events(); { let mut added_monitors = nodes[1].chain_monitor.added_monitors.lock().unwrap(); - assert_eq!(added_monitors.len(), 2); - assert_eq!(added_monitors[0].0, chan_1.2); + assert_eq!(added_monitors.len(), 3); + assert_eq!(added_monitors[0].0, chan_2.2); assert_eq!(added_monitors[1].0, chan_1.2); + assert_eq!(added_monitors[2].0, chan_1.2); added_monitors.clear(); } assert_eq!(events.len(), 3); @@ -2430,7 +2036,6 @@ fn do_test_commitment_revoked_fail_backward_exhaustive( assert!(nodes[1].node.get_and_clear_pending_events().is_empty()); mine_transaction(&nodes[1], &revoked_local_txn[0]); - check_added_monitors(&nodes[1], 1); connect_blocks(&nodes[1], ANTI_REORG_DELAY - 1); let events = nodes[1].node.get_and_clear_pending_events(); @@ -2449,7 +2054,7 @@ fn do_test_commitment_revoked_fail_backward_exhaustive( ))); nodes[1].node.process_pending_htlc_forwards(); - check_added_monitors(&nodes[1], 1); + check_added_monitors(&nodes[1], 2); let mut events = nodes[1].node.get_and_clear_pending_msg_events(); assert_eq!(events.len(), if deliver_bs_raa { 4 } else { 3 }); @@ -3970,7 +3575,6 @@ pub fn test_static_spendable_outputs_preimage_tx() { expect_payment_claimed!(nodes[1], payment_hash, 3_000_000); check_added_monitors(&nodes[1], 1); mine_transaction(&nodes[1], &commitment_tx[0]); - check_added_monitors(&nodes[1], 1); let events = nodes[1].node.get_and_clear_pending_msg_events(); match events[0] { MessageSendEvent::UpdateHTLCs { .. } => {}, @@ -3980,6 +3584,7 @@ pub fn test_static_spendable_outputs_preimage_tx() { MessageSendEvent::BroadcastChannelUpdate { .. } => {}, _ => panic!("Unexepected event"), } + check_added_monitors(&nodes[1], 1); // Check B's monitor was able to send back output descriptor event for preimage tx on A's commitment tx let node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().clone(); // ChannelMonitor: preimage tx @@ -4019,12 +3624,12 @@ pub fn test_static_spendable_outputs_timeout_tx() { // Settle A's commitment tx on B' chain mine_transaction(&nodes[1], &commitment_tx[0]); - check_added_monitors(&nodes[1], 1); let events = nodes[1].node.get_and_clear_pending_msg_events(); match events[1] { MessageSendEvent::BroadcastChannelUpdate { .. } => {}, _ => panic!("Unexpected event"), } + check_added_monitors(&nodes[1], 1); connect_blocks(&nodes[1], TEST_FINAL_CLTV); // Confirm blocks until the HTLC expires // Check B's monitor was able to send back output descriptor event for timeout tx on A's commitment tx @@ -4323,14 +3928,9 @@ pub fn test_onchain_to_onchain_claim() { // So we broadcast C's commitment tx and HTLC-Success on B's chain, we should successfully be able to extract preimage and update downstream monitor let txn = vec![commitment_tx[0].clone(), c_txn[0].clone()]; connect_block(&nodes[1], &create_dummy_block(nodes[1].best_block_hash(), 42, txn)); - check_added_monitors(&nodes[1], 1); let events = nodes[1].node.get_and_clear_pending_events(); assert_eq!(events.len(), 2); match events[0] { - Event::ChannelClosed { reason: ClosureReason::CommitmentTxConfirmed, .. } => {}, - _ => panic!("Unexpected event"), - } - match events[1] { Event::PaymentForwarded { total_fee_earned_msat, prev_channel_id, @@ -4347,7 +3947,11 @@ pub fn test_onchain_to_onchain_claim() { }, _ => panic!("Unexpected event"), } - check_added_monitors(&nodes[1], 1); + match events[1] { + Event::ChannelClosed { reason: ClosureReason::CommitmentTxConfirmed, .. } => {}, + _ => panic!("Unexpected event"), + } + check_added_monitors(&nodes[1], 2); let mut msg_events = nodes[1].node.get_and_clear_pending_msg_events(); assert_eq!(msg_events.len(), 3); let nodes_2_event = remove_first_msg_event_to_node(&node_c_id, &mut msg_events); @@ -4515,9 +4119,9 @@ pub fn test_duplicate_payment_hash_one_failure_one_success() { // generate (note that the ChannelMonitor doesn't differentiate between HTLCs once it has the // preimage). mine_transaction(&nodes[2], &commitment_txn[0]); + check_closed_broadcast(&nodes[2], 1, true); check_added_monitors(&nodes[2], 1); check_closed_event!(nodes[2], 1, ClosureReason::CommitmentTxConfirmed, [node_b_id], 100000); - check_closed_broadcast(&nodes[2], 1, true); let htlc_success_txn: Vec<_> = nodes[2].tx_broadcaster.txn_broadcasted.lock().unwrap().clone(); assert_eq!(htlc_success_txn.len(), 2); // ChannelMonitor: HTLC-Success txn (*2 due to 2-HTLC outputs) @@ -4614,8 +4218,6 @@ pub fn test_dynamic_spendable_outputs_local_htlc_success_tx() { check_added_monitors(&nodes[1], 1); mine_transaction(&nodes[1], &local_txn[0]); - check_added_monitors(&nodes[1], 1); - check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [node_a_id], 100000); let events = nodes[1].node.get_and_clear_pending_msg_events(); match events[0] { MessageSendEvent::UpdateHTLCs { .. } => {}, @@ -4625,6 +4227,8 @@ pub fn test_dynamic_spendable_outputs_local_htlc_success_tx() { MessageSendEvent::BroadcastChannelUpdate { .. } => {}, _ => panic!("Unexepected event"), } + check_added_monitors(&nodes[1], 1); + check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [node_a_id], 100000); let node_tx = { let node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap(); assert_eq!(node_txn.len(), 1); @@ -5321,7 +4925,8 @@ fn do_htlc_claim_local_commitment_only(use_dust: bool) { test_txn_broadcast(&nodes[1], &chan, None, htlc_type); check_closed_broadcast!(nodes[1], true); check_added_monitors(&nodes[1], 1); - check_closed_event!(nodes[1], 1, ClosureReason::HTLCsTimedOut, [node_a_id], 100000); + let reason = ClosureReason::HTLCsTimedOut { payment_hash: Some(payment_hash) }; + check_closed_event!(nodes[1], 1, reason, [node_a_id], 100000); } fn do_htlc_claim_current_remote_commitment_only(use_dust: bool) { @@ -5359,7 +4964,8 @@ fn do_htlc_claim_current_remote_commitment_only(use_dust: bool) { test_txn_broadcast(&nodes[0], &chan, None, HTLCType::NONE); check_closed_broadcast!(nodes[0], true); check_added_monitors(&nodes[0], 1); - check_closed_event!(nodes[0], 1, ClosureReason::HTLCsTimedOut, [node_b_id], 100000); + let reason = ClosureReason::HTLCsTimedOut { payment_hash: Some(payment_hash) }; + check_closed_event!(nodes[0], 1, reason, [node_b_id], 100000); } fn do_htlc_claim_previous_remote_commitment_only(use_dust: bool, check_revoke_no_close: bool) { @@ -5414,7 +5020,8 @@ fn do_htlc_claim_previous_remote_commitment_only(use_dust: bool, check_revoke_no test_txn_broadcast(&nodes[0], &chan, None, HTLCType::NONE); check_closed_broadcast!(nodes[0], true); check_added_monitors(&nodes[0], 1); - check_closed_event!(nodes[0], 1, ClosureReason::HTLCsTimedOut, [node_b_id], 100000); + let reason = ClosureReason::HTLCsTimedOut { payment_hash: Some(our_payment_hash) }; + check_closed_event!(nodes[0], 1, reason, [node_b_id], 100000); } else { expect_payment_failed!(nodes[0], our_payment_hash, true); } @@ -5448,107 +5055,6 @@ pub fn htlc_claim_single_commitment_only_b() { do_htlc_claim_previous_remote_commitment_only(false, true); } -#[xtest(feature = "_externalize_tests")] -#[should_panic] -pub fn bolt2_open_channel_sending_node_checks_part1() { - //This test needs to be on its own as we are catching a panic - let chanmon_cfgs = create_chanmon_cfgs(2); - let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); - let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); - let nodes = create_network(2, &node_cfgs, &node_chanmgrs); - - let node_a_id = nodes[0].node.get_our_node_id(); - let node_b_id = nodes[1].node.get_our_node_id(); - - // Force duplicate randomness for every get-random call - for node in nodes.iter() { - *node.keys_manager.override_random_bytes.lock().unwrap() = Some([0; 32]); - } - - // BOLT #2 spec: Sending node must ensure temporary_channel_id is unique from any other channel ID with the same peer. - let channel_value_satoshis = 10000; - let push_msat = 10001; - nodes[0] - .node - .create_channel(node_b_id, channel_value_satoshis, push_msat, 42, None, None) - .unwrap(); - let node0_to_1_send_open_channel = - get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); - nodes[1].node.handle_open_channel(node_a_id, &node0_to_1_send_open_channel); - get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, node_a_id); - - // Create a second channel with the same random values. This used to panic due to a colliding - // channel_id, but now panics due to a colliding outbound SCID alias. - assert!(nodes[0] - .node - .create_channel(node_b_id, channel_value_satoshis, push_msat, 42, None, None) - .is_err()); -} - -#[xtest(feature = "_externalize_tests")] -pub fn bolt2_open_channel_sending_node_checks_part2() { - let chanmon_cfgs = create_chanmon_cfgs(2); - let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); - let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); - let nodes = create_network(2, &node_cfgs, &node_chanmgrs); - - let node_b_id = nodes[1].node.get_our_node_id(); - - // BOLT #2 spec: Sending node must set push_msat to equal or less than 1000 * funding_satoshis - let channel_value_satoshis = 10000; - // Test when push_msat is equal to 1000 * funding_satoshis. - let push_msat = 1000 * channel_value_satoshis + 1; - assert!(nodes[0] - .node - .create_channel(node_b_id, channel_value_satoshis, push_msat, 42, None, None) - .is_err()); - - nodes[0].node.create_channel(node_b_id, 100_000, 0, 42, None, None).unwrap(); - - let node0_to_1_send_open_channel = - get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); - - // BOLT #2 spec: Sending node should set to_self_delay sufficient to ensure the sender can irreversibly spend a commitment transaction output, in case of misbehaviour by the receiver. - assert!(BREAKDOWN_TIMEOUT > 0); - assert!(node0_to_1_send_open_channel.common_fields.to_self_delay == BREAKDOWN_TIMEOUT); - - // BOLT #2 spec: Sending node must ensure the chain_hash value identifies the chain it wishes to open the channel within. - let chain_hash = ChainHash::using_genesis_block(Network::Testnet); - assert_eq!(node0_to_1_send_open_channel.common_fields.chain_hash, chain_hash); -} - -#[xtest(feature = "_externalize_tests")] -pub fn bolt2_open_channel_sane_dust_limit() { - let chanmon_cfgs = create_chanmon_cfgs(2); - let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); - let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); - let nodes = create_network(2, &node_cfgs, &node_chanmgrs); - - let node_a_id = nodes[0].node.get_our_node_id(); - let node_b_id = nodes[1].node.get_our_node_id(); - - let value_sats = 1000000; - let push_msat = 10001; - nodes[0].node.create_channel(node_b_id, value_sats, push_msat, 42, None, None).unwrap(); - let mut node0_to_1_send_open_channel = - get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); - node0_to_1_send_open_channel.common_fields.dust_limit_satoshis = 547; - node0_to_1_send_open_channel.channel_reserve_satoshis = 100001; - - nodes[1].node.handle_open_channel(node_a_id, &node0_to_1_send_open_channel); - let events = nodes[1].node.get_and_clear_pending_msg_events(); - let err_msg = match events[0] { - MessageSendEvent::HandleError { - action: ErrorAction::SendErrorMessage { ref msg }, .. - } => msg.clone(), - _ => panic!("Unexpected event"), - }; - assert_eq!( - err_msg.data, - "dust_limit_satoshis (547) is greater than the implementation limit (546)" - ); -} - // Test that if we fail to send an HTLC that is being freed from the holding cell, and the HTLC // originated from our node, its failure is surfaced to the user. We trigger this failure to // free the HTLC by increasing our fee while the HTLC is in the holding cell such that the HTLC @@ -6364,145 +5870,6 @@ pub fn test_sweep_outbound_htlc_failure_update() { do_test_sweep_outbound_htlc_failure_update(true, false); } -#[xtest(feature = "_externalize_tests")] -pub fn test_user_configurable_csv_delay() { - // We test our channel constructors yield errors when we pass them absurd csv delay - - let mut low_our_to_self_config = UserConfig::default(); - low_our_to_self_config.channel_handshake_config.our_to_self_delay = 6; - let mut high_their_to_self_config = UserConfig::default(); - high_their_to_self_config.channel_handshake_limits.their_to_self_delay = 100; - let user_cfgs = [Some(high_their_to_self_config.clone()), None]; - let chanmon_cfgs = create_chanmon_cfgs(2); - let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); - let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &user_cfgs); - let nodes = create_network(2, &node_cfgs, &node_chanmgrs); - - let node_a_id = nodes[0].node.get_our_node_id(); - let node_b_id = nodes[1].node.get_our_node_id(); - - let logger = TestLogger::new(); - - // We test config.our_to_self > BREAKDOWN_TIMEOUT is enforced in OutboundV1Channel::new() - if let Err(error) = OutboundV1Channel::new( - &LowerBoundedFeeEstimator::new(&test_utils::TestFeeEstimator::new(253)), - &nodes[0].keys_manager, - &nodes[0].keys_manager, - node_b_id, - &nodes[1].node.init_features(), - 1000000, - 1000000, - 0, - &low_our_to_self_config, - 0, - 42, - None, - &logger, - ) { - match error { - APIError::APIMisuseError { err } => { - assert!(regex::Regex::new( - r"Configured with an unreasonable our_to_self_delay \(\d+\) putting user funds at risks" - ) - .unwrap() - .is_match(err.as_str())); - }, - _ => panic!("Unexpected event"), - } - } else { - assert!(false) - } - - // We test config.our_to_self > BREAKDOWN_TIMEOUT is enforced in InboundV1Channel::new() - nodes[1].node.create_channel(node_a_id, 1000000, 1000000, 42, None, None).unwrap(); - let mut open_channel = get_event_msg!(nodes[1], MessageSendEvent::SendOpenChannel, node_a_id); - open_channel.common_fields.to_self_delay = 200; - if let Err(error) = InboundV1Channel::new( - &LowerBoundedFeeEstimator::new(&test_utils::TestFeeEstimator::new(253)), - &nodes[0].keys_manager, - &nodes[0].keys_manager, - node_b_id, - &nodes[0].node.channel_type_features(), - &nodes[1].node.init_features(), - &open_channel, - 0, - &low_our_to_self_config, - 0, - &nodes[0].logger, - /*is_0conf=*/ false, - ) { - match error { - ChannelError::Close((err, _)) => { - let regex = regex::Regex::new( - r"Configured with an unreasonable our_to_self_delay \(\d+\) putting user funds at risks", - ) - .unwrap(); - assert!(regex.is_match(err.as_str())); - }, - _ => panic!("Unexpected event"), - } - } else { - assert!(false); - } - - // We test msg.to_self_delay <= config.their_to_self_delay is enforced in Chanel::accept_channel() - nodes[0].node.create_channel(node_b_id, 1000000, 1000000, 42, None, None).unwrap(); - let open_channel = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); - nodes[1].node.handle_open_channel(node_a_id, &open_channel); - - let mut accept_channel = - get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, node_a_id); - accept_channel.common_fields.to_self_delay = 200; - nodes[0].node.handle_accept_channel(node_b_id, &accept_channel); - let reason_msg; - if let MessageSendEvent::HandleError { ref action, .. } = - nodes[0].node.get_and_clear_pending_msg_events()[0] - { - match action { - &ErrorAction::SendErrorMessage { ref msg } => { - assert!(regex::Regex::new(r"They wanted our payments to be delayed by a needlessly long period\. Upper limit: \d+\. Actual: \d+").unwrap().is_match(msg.data.as_str())); - reason_msg = msg.data.clone(); - }, - _ => { - panic!(); - }, - } - } else { - panic!(); - } - let reason = ClosureReason::ProcessingError { err: reason_msg }; - check_closed_event!(nodes[0], 1, reason, [node_b_id], 1000000); - - // We test msg.to_self_delay <= config.their_to_self_delay is enforced in InboundV1Channel::new() - nodes[1].node.create_channel(node_a_id, 1000000, 1000000, 42, None, None).unwrap(); - let mut open_channel = get_event_msg!(nodes[1], MessageSendEvent::SendOpenChannel, node_a_id); - open_channel.common_fields.to_self_delay = 200; - if let Err(error) = InboundV1Channel::new( - &LowerBoundedFeeEstimator::new(&test_utils::TestFeeEstimator::new(253)), - &nodes[0].keys_manager, - &nodes[0].keys_manager, - node_b_id, - &nodes[0].node.channel_type_features(), - &nodes[1].node.init_features(), - &open_channel, - 0, - &high_their_to_self_config, - 0, - &nodes[0].logger, - /*is_0conf=*/ false, - ) { - match error { - ChannelError::Close((err, _)) => { - let regex = regex::Regex::new(r"They wanted our payments to be delayed by a needlessly long period\. Upper limit: \d+\. Actual: \d+").unwrap(); - assert!(regex.is_match(err.as_str())); - }, - _ => panic!("Unexpected event"), - } - } else { - assert!(false); - } -} - #[xtest(feature = "_externalize_tests")] pub fn test_check_htlc_underpaying() { // Send payment through A -> B but A is maliciously @@ -6733,6 +6100,7 @@ pub fn test_bump_penalty_txn_on_revoked_commitment() { // Actually revoke tx by claiming a HTLC claim_payment(&nodes[0], &[&nodes[1]], payment_preimage); connect_block(&nodes[1], &create_dummy_block(header_114, 42, vec![revoked_txn[0].clone()])); + check_closed_broadcast(&nodes[1], 1, true); check_added_monitors(&nodes[1], 1); macro_rules! check_broadcasted_txn { @@ -6868,7 +6236,7 @@ pub fn test_bump_penalty_txn_on_revoked_htlcs() { &nodes[1], &create_dummy_block(nodes[1].best_block_hash(), 42, vec![revoked_local_txn[0].clone()]), ); - check_closed_broadcast!(nodes[1], true); + check_closed_broadcast(&nodes[1], 1, true); check_added_monitors(&nodes[1], 1); check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [node_a_id], 1000000); connect_blocks(&nodes[1], 50); // Confirm blocks until the HTLC expires (note CLTV was explicitly 50 above) @@ -6893,6 +6261,8 @@ pub fn test_bump_penalty_txn_on_revoked_htlcs() { let hash_128 = connect_blocks(&nodes[0], 40); let block_11 = create_dummy_block(hash_128, 42, vec![revoked_local_txn[0].clone()]); connect_block(&nodes[0], &block_11); + check_closed_broadcast(&nodes[0], 1, true); + check_added_monitors(&nodes[0], 1); let block_129 = create_dummy_block( block_11.block_hash(), 42, @@ -6999,8 +6369,6 @@ pub fn test_bump_penalty_txn_on_revoked_htlcs() { assert_eq!(node_txn.len(), 0); node_txn.clear(); } - check_closed_broadcast!(nodes[0], true); - check_added_monitors(&nodes[0], 1); } #[xtest(feature = "_externalize_tests")] @@ -7036,7 +6404,9 @@ pub fn test_bump_penalty_txn_on_remote_commitment() { // Claim a HTLC without revocation (provide B monitor with preimage) nodes[1].node.claim_funds(payment_preimage); expect_payment_claimed!(nodes[1], payment_hash, htlc_value_a_msats); + let _ = get_htlc_update_msgs(&nodes[1], &nodes[0].node.get_our_node_id()); mine_transaction(&nodes[1], &remote_txn[0]); + check_closed_broadcast(&nodes[1], 1, true); check_added_monitors(&nodes[1], 2); connect_blocks(&nodes[1], TEST_FINAL_CLTV); // Confirm blocks until the HTLC expires @@ -7406,315 +6776,63 @@ pub fn test_channel_update_has_correct_htlc_maximum_msat() { } #[xtest(feature = "_externalize_tests")] -pub fn test_manually_accept_inbound_channel_request() { - let mut manually_accept_conf = UserConfig::default(); - manually_accept_conf.manually_accept_inbound_channels = true; - manually_accept_conf.channel_handshake_config.minimum_depth = 1; - - let chanmon_cfgs = create_chanmon_cfgs(2); - let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); - let node_chanmgrs = - create_node_chanmgrs(2, &node_cfgs, &[None, Some(manually_accept_conf.clone())]); - let nodes = create_network(2, &node_cfgs, &node_chanmgrs); +pub fn test_onion_value_mpp_set_calculation() { + // Test that we use the onion value `amt_to_forward` when + // calculating whether we've reached the `total_msat` of an MPP + // by having a routing node forward more than `amt_to_forward` + // and checking that the receiving node doesn't generate + // a PaymentClaimable event too early + let node_count = 4; + let chanmon_cfgs = create_chanmon_cfgs(node_count); + let node_cfgs = create_node_cfgs(node_count, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(node_count, &node_cfgs, &vec![None; node_count]); + let mut nodes = create_network(node_count, &node_cfgs, &node_chanmgrs); - let node_a_id = nodes[0].node.get_our_node_id(); let node_b_id = nodes[1].node.get_our_node_id(); + let node_c_id = nodes[2].node.get_our_node_id(); + let node_d_id = nodes[3].node.get_our_node_id(); - nodes[0] - .node - .create_channel(node_b_id, 100000, 10001, 42, None, Some(manually_accept_conf)) - .unwrap(); - let res = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); - - nodes[1].node.handle_open_channel(node_a_id, &res); - - // Assert that `nodes[1]` has no `MessageSendEvent::SendAcceptChannel` in `msg_events` before - // accepting the inbound channel request. - assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty()); - - let config_overrides = ChannelConfigOverrides { - handshake_overrides: Some(ChannelHandshakeConfigUpdate { - max_inbound_htlc_value_in_flight_percent_of_channel: None, - htlc_minimum_msat: None, - minimum_depth: None, - to_self_delay: None, - max_accepted_htlcs: Some(3), - channel_reserve_proportional_millionths: None, - }), - update_overrides: Some(ChannelConfigUpdate { - forwarding_fee_proportional_millionths: None, - forwarding_fee_base_msat: Some(555), - cltv_expiry_delta: None, - max_dust_htlc_exposure_msat: None, - force_close_avoidance_max_fee_satoshis: None, - accept_underpaying_htlcs: None, - }), - }; - let events = nodes[1].node.get_and_clear_pending_events(); - match events[0] { - Event::OpenChannelRequest { temporary_channel_id, .. } => { - let config = Some(config_overrides); - nodes[1] - .node - .accept_inbound_channel(&temporary_channel_id, &node_a_id, 23, config) - .unwrap(); - }, - _ => panic!("Unexpected event"), - } - - let accept_msg_ev = nodes[1].node.get_and_clear_pending_msg_events(); - assert_eq!(accept_msg_ev.len(), 1); + let chan_1_id = create_announced_chan_between_nodes(&nodes, 0, 1).0.contents.short_channel_id; + let chan_2_id = create_announced_chan_between_nodes(&nodes, 0, 2).0.contents.short_channel_id; + let chan_3_id = create_announced_chan_between_nodes(&nodes, 1, 3).0.contents.short_channel_id; + let chan_4_id = create_announced_chan_between_nodes(&nodes, 2, 3).0.contents.short_channel_id; - let ref accept_channel: AcceptChannel; - match accept_msg_ev[0] { - MessageSendEvent::SendAcceptChannel { ref node_id, ref msg } => { - assert_eq!(*node_id, node_a_id); + let total_msat = 100_000; + let expected_paths: &[&[&Node]] = &[&[&nodes[1], &nodes[3]], &[&nodes[2], &nodes[3]]]; + let (mut route, hash, preimage, payment_secret) = + get_route_and_payment_hash!(&nodes[0], nodes[3], total_msat); + let sample_path = route.paths.pop().unwrap(); - // Assert overriden handshake parameter. - assert_eq!(msg.common_fields.max_accepted_htlcs, 3); + let mut path_1 = sample_path.clone(); + path_1.hops[0].pubkey = node_b_id; + path_1.hops[0].short_channel_id = chan_1_id; + path_1.hops[1].pubkey = node_d_id; + path_1.hops[1].short_channel_id = chan_3_id; + path_1.hops[1].fee_msat = 100_000; + route.paths.push(path_1); - accept_channel = msg; - }, - _ => panic!("Unexpected event"), - } + let mut path_2 = sample_path.clone(); + path_2.hops[0].pubkey = node_c_id; + path_2.hops[0].short_channel_id = chan_2_id; + path_2.hops[1].pubkey = node_d_id; + path_2.hops[1].short_channel_id = chan_4_id; + path_2.hops[1].fee_msat = 1_000; + route.paths.push(path_2); - // Continue channel opening process until channel update messages are sent. - nodes[0].node.handle_accept_channel(node_b_id, &accept_channel); - let (temp_channel_id, tx, funding_outpoint) = - create_funding_transaction(&nodes[0], &node_b_id, 100_000, 42); + // Send payment + let id = PaymentId(nodes[0].keys_manager.backing.get_secure_random_bytes()); + let onion = RecipientOnionFields::secret_only(payment_secret); + let onion_session_privs = + nodes[0].node.test_add_new_pending_payment(hash, onion.clone(), id, &route).unwrap(); + let amt = Some(total_msat); nodes[0] .node - .unsafe_manual_funding_transaction_generated(temp_channel_id, node_b_id, funding_outpoint) + .test_send_payment_internal(&route, hash, onion, None, id, amt, onion_session_privs) .unwrap(); - check_added_monitors(&nodes[0], 0); + check_added_monitors(&nodes[0], expected_paths.len()); - let funding_created = get_event_msg!(nodes[0], MessageSendEvent::SendFundingCreated, node_b_id); - nodes[1].node.handle_funding_created(node_a_id, &funding_created); - check_added_monitors(&nodes[1], 1); - expect_channel_pending_event(&nodes[1], &node_a_id); - - let funding_signed = get_event_msg!(nodes[1], MessageSendEvent::SendFundingSigned, node_a_id); - nodes[0].node.handle_funding_signed(node_b_id, &funding_signed); - check_added_monitors(&nodes[0], 1); - let events = &nodes[0].node.get_and_clear_pending_events(); - assert_eq!(events.len(), 2); - match &events[0] { - crate::events::Event::FundingTxBroadcastSafe { funding_txo, .. } => { - assert_eq!(funding_txo.txid, funding_outpoint.txid); - assert_eq!(funding_txo.vout, funding_outpoint.index.into()); - }, - _ => panic!("Unexpected event"), - }; - match &events[1] { - crate::events::Event::ChannelPending { counterparty_node_id, .. } => { - assert_eq!(node_b_id, *counterparty_node_id); - }, - _ => panic!("Unexpected event"), - }; - - mine_transaction(&nodes[0], &tx); - mine_transaction(&nodes[1], &tx); - - let as_channel_ready = get_event_msg!(nodes[1], MessageSendEvent::SendChannelReady, node_a_id); - nodes[1].node.handle_channel_ready(node_a_id, &as_channel_ready); - let as_channel_ready = get_event_msg!(nodes[0], MessageSendEvent::SendChannelReady, node_b_id); - nodes[0].node.handle_channel_ready(node_b_id, &as_channel_ready); - - expect_channel_ready_event(&nodes[0], &node_b_id); - expect_channel_ready_event(&nodes[1], &node_a_id); - - // Assert that the overriden base fee surfaces in the channel update. - let channel_update = get_event_msg!(nodes[1], MessageSendEvent::SendChannelUpdate, node_a_id); - assert_eq!(channel_update.contents.fee_base_msat, 555); - - get_event_msg!(nodes[0], MessageSendEvent::SendChannelUpdate, node_b_id); -} - -#[xtest(feature = "_externalize_tests")] -pub fn test_manually_reject_inbound_channel_request() { - let mut manually_accept_conf = UserConfig::default(); - manually_accept_conf.manually_accept_inbound_channels = true; - let chanmon_cfgs = create_chanmon_cfgs(2); - let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); - let node_chanmgrs = - create_node_chanmgrs(2, &node_cfgs, &[None, Some(manually_accept_conf.clone())]); - let nodes = create_network(2, &node_cfgs, &node_chanmgrs); - - let node_a_id = nodes[0].node.get_our_node_id(); - let node_b_id = nodes[1].node.get_our_node_id(); - - nodes[0] - .node - .create_channel(node_b_id, 100000, 10001, 42, None, Some(manually_accept_conf)) - .unwrap(); - let res = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); - - nodes[1].node.handle_open_channel(node_a_id, &res); - - // Assert that `nodes[1]` has no `MessageSendEvent::SendAcceptChannel` in `msg_events` before - // rejecting the inbound channel request. - assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty()); - let err = "Channel force-closed".to_string(); - let events = nodes[1].node.get_and_clear_pending_events(); - match events[0] { - Event::OpenChannelRequest { temporary_channel_id, .. } => { - nodes[1] - .node - .force_close_broadcasting_latest_txn(&temporary_channel_id, &node_a_id, err) - .unwrap(); - }, - _ => panic!("Unexpected event"), - } - - let close_msg_ev = nodes[1].node.get_and_clear_pending_msg_events(); - assert_eq!(close_msg_ev.len(), 1); - - match close_msg_ev[0] { - MessageSendEvent::HandleError { ref node_id, .. } => { - assert_eq!(*node_id, node_a_id); - }, - _ => panic!("Unexpected event"), - } - - // There should be no more events to process, as the channel was never opened. - assert!(nodes[1].node.get_and_clear_pending_events().is_empty()); -} - -#[xtest(feature = "_externalize_tests")] -pub fn test_can_not_accept_inbound_channel_twice() { - let mut manually_accept_conf = UserConfig::default(); - manually_accept_conf.manually_accept_inbound_channels = true; - let chanmon_cfgs = create_chanmon_cfgs(2); - let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); - let node_chanmgrs = - create_node_chanmgrs(2, &node_cfgs, &[None, Some(manually_accept_conf.clone())]); - let nodes = create_network(2, &node_cfgs, &node_chanmgrs); - - let node_a_id = nodes[0].node.get_our_node_id(); - let node_b_id = nodes[1].node.get_our_node_id(); - - nodes[0] - .node - .create_channel(node_b_id, 100000, 10001, 42, None, Some(manually_accept_conf)) - .unwrap(); - let res = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); - - nodes[1].node.handle_open_channel(node_a_id, &res); - - // Assert that `nodes[1]` has no `MessageSendEvent::SendAcceptChannel` in `msg_events` before - // accepting the inbound channel request. - assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty()); - - let events = nodes[1].node.get_and_clear_pending_events(); - match events[0] { - Event::OpenChannelRequest { temporary_channel_id, .. } => { - nodes[1] - .node - .accept_inbound_channel(&temporary_channel_id, &node_a_id, 0, None) - .unwrap(); - let api_res = - nodes[1].node.accept_inbound_channel(&temporary_channel_id, &node_a_id, 0, None); - match api_res { - Err(APIError::APIMisuseError { err }) => { - assert_eq!(err, "No such channel awaiting to be accepted."); - }, - Ok(_) => panic!("Channel shouldn't be possible to be accepted twice"), - Err(e) => panic!("Unexpected Error {:?}", e), - } - }, - _ => panic!("Unexpected event"), - } - - // Ensure that the channel wasn't closed after attempting to accept it twice. - let accept_msg_ev = nodes[1].node.get_and_clear_pending_msg_events(); - assert_eq!(accept_msg_ev.len(), 1); - - match accept_msg_ev[0] { - MessageSendEvent::SendAcceptChannel { ref node_id, .. } => { - assert_eq!(*node_id, node_a_id); - }, - _ => panic!("Unexpected event"), - } -} - -#[xtest(feature = "_externalize_tests")] -pub fn test_can_not_accept_unknown_inbound_channel() { - let chanmon_cfg = create_chanmon_cfgs(2); - let node_cfg = create_node_cfgs(2, &chanmon_cfg); - let node_chanmgr = create_node_chanmgrs(2, &node_cfg, &[None, None]); - let nodes = create_network(2, &node_cfg, &node_chanmgr); - - let node_b_id = nodes[1].node.get_our_node_id(); - - let unknown_channel_id = ChannelId::new_zero(); - let api_res = nodes[0].node.accept_inbound_channel(&unknown_channel_id, &node_b_id, 0, None); - match api_res { - Err(APIError::APIMisuseError { err }) => { - assert_eq!(err, "No such channel awaiting to be accepted."); - }, - Ok(_) => panic!("It shouldn't be possible to accept an unkown channel"), - Err(e) => panic!("Unexpected Error: {:?}", e), - } -} - -#[xtest(feature = "_externalize_tests")] -pub fn test_onion_value_mpp_set_calculation() { - // Test that we use the onion value `amt_to_forward` when - // calculating whether we've reached the `total_msat` of an MPP - // by having a routing node forward more than `amt_to_forward` - // and checking that the receiving node doesn't generate - // a PaymentClaimable event too early - let node_count = 4; - let chanmon_cfgs = create_chanmon_cfgs(node_count); - let node_cfgs = create_node_cfgs(node_count, &chanmon_cfgs); - let node_chanmgrs = create_node_chanmgrs(node_count, &node_cfgs, &vec![None; node_count]); - let mut nodes = create_network(node_count, &node_cfgs, &node_chanmgrs); - - let node_b_id = nodes[1].node.get_our_node_id(); - let node_c_id = nodes[2].node.get_our_node_id(); - let node_d_id = nodes[3].node.get_our_node_id(); - - let chan_1_id = create_announced_chan_between_nodes(&nodes, 0, 1).0.contents.short_channel_id; - let chan_2_id = create_announced_chan_between_nodes(&nodes, 0, 2).0.contents.short_channel_id; - let chan_3_id = create_announced_chan_between_nodes(&nodes, 1, 3).0.contents.short_channel_id; - let chan_4_id = create_announced_chan_between_nodes(&nodes, 2, 3).0.contents.short_channel_id; - - let total_msat = 100_000; - let expected_paths: &[&[&Node]] = &[&[&nodes[1], &nodes[3]], &[&nodes[2], &nodes[3]]]; - let (mut route, hash, preimage, payment_secret) = - get_route_and_payment_hash!(&nodes[0], nodes[3], total_msat); - let sample_path = route.paths.pop().unwrap(); - - let mut path_1 = sample_path.clone(); - path_1.hops[0].pubkey = node_b_id; - path_1.hops[0].short_channel_id = chan_1_id; - path_1.hops[1].pubkey = node_d_id; - path_1.hops[1].short_channel_id = chan_3_id; - path_1.hops[1].fee_msat = 100_000; - route.paths.push(path_1); - - let mut path_2 = sample_path.clone(); - path_2.hops[0].pubkey = node_c_id; - path_2.hops[0].short_channel_id = chan_2_id; - path_2.hops[1].pubkey = node_d_id; - path_2.hops[1].short_channel_id = chan_4_id; - path_2.hops[1].fee_msat = 1_000; - route.paths.push(path_2); - - // Send payment - let id = PaymentId(nodes[0].keys_manager.backing.get_secure_random_bytes()); - let onion = RecipientOnionFields::secret_only(payment_secret); - let onion_session_privs = - nodes[0].node.test_add_new_pending_payment(hash, onion.clone(), id, &route).unwrap(); - let amt = Some(total_msat); - nodes[0] - .node - .test_send_payment_internal(&route, hash, onion, None, id, amt, onion_session_privs) - .unwrap(); - check_added_monitors(&nodes[0], expected_paths.len()); - - let mut events = nodes[0].node.get_and_clear_pending_msg_events(); - assert_eq!(events.len(), expected_paths.len()); + let mut events = nodes[0].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), expected_paths.len()); // First path let ev = @@ -8160,7 +7278,7 @@ pub fn test_concurrent_monitor_claim() { send_payment(&nodes[0], &[&nodes[1]], 10_000_000); // Route a HTLC from node 0 to node 1 (but don't settle) - route_payment(&nodes[0], &[&nodes[1]], 9_000_000); + let (_, payment_hash_timeout, ..) = route_payment(&nodes[0], &[&nodes[1]], 9_000_000); // Copy ChainMonitor to simulate watchtower Alice and update block height her ChannelMonitor timeout HTLC onchain let chain_source = test_utils::TestChainSource::new(Network::Testnet); @@ -8311,7 +7429,8 @@ pub fn test_concurrent_monitor_claim() { let height = HTLC_TIMEOUT_BROADCAST + 1; connect_blocks(&nodes[0], height - nodes[0].best_block_info().1); check_closed_broadcast(&nodes[0], 1, true); - check_closed_event!(&nodes[0], 1, ClosureReason::HTLCsTimedOut, false, [node_b_id], 100000); + let reason = ClosureReason::HTLCsTimedOut { payment_hash: Some(payment_hash_timeout) }; + check_closed_event!(&nodes[0], 1, reason, false, [node_b_id], 100000); watchtower_alice.chain_monitor.block_connected( &create_dummy_block(BlockHash::all_zeros(), 42, vec![bob_state_y.clone()]), height, @@ -8645,69 +7764,6 @@ pub fn test_onchain_htlc_settlement_after_close() { do_test_onchain_htlc_settlement_after_close(false, false); } -#[xtest(feature = "_externalize_tests")] -pub fn test_duplicate_temporary_channel_id_from_different_peers() { - // Tests that we can accept two different `OpenChannel` requests with the same - // `temporary_channel_id`, as long as they are from different peers. - let chanmon_cfgs = create_chanmon_cfgs(3); - let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); - let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]); - let nodes = create_network(3, &node_cfgs, &node_chanmgrs); - - let node_a_id = nodes[0].node.get_our_node_id(); - let node_b_id = nodes[1].node.get_our_node_id(); - let node_c_id = nodes[2].node.get_our_node_id(); - - // Create an first channel channel - nodes[1].node.create_channel(node_a_id, 100000, 10001, 42, None, None).unwrap(); - let mut open_chan_msg_chan_1_0 = - get_event_msg!(nodes[1], MessageSendEvent::SendOpenChannel, node_a_id); - - // Create an second channel - nodes[2].node.create_channel(node_a_id, 100000, 10001, 43, None, None).unwrap(); - let mut open_chan_msg_chan_2_0 = - get_event_msg!(nodes[2], MessageSendEvent::SendOpenChannel, node_a_id); - - // Modify the `OpenChannel` from `nodes[2]` to `nodes[0]` to ensure that it uses the same - // `temporary_channel_id` as the `OpenChannel` from nodes[1] to nodes[0]. - open_chan_msg_chan_2_0.common_fields.temporary_channel_id = - open_chan_msg_chan_1_0.common_fields.temporary_channel_id; - - // Assert that `nodes[0]` can accept both `OpenChannel` requests, even though they use the same - // `temporary_channel_id` as they are from different peers. - nodes[0].node.handle_open_channel(node_b_id, &open_chan_msg_chan_1_0); - { - let events = nodes[0].node.get_and_clear_pending_msg_events(); - assert_eq!(events.len(), 1); - match &events[0] { - MessageSendEvent::SendAcceptChannel { node_id, msg } => { - assert_eq!(node_id, &node_b_id); - assert_eq!( - msg.common_fields.temporary_channel_id, - open_chan_msg_chan_1_0.common_fields.temporary_channel_id - ); - }, - _ => panic!("Unexpected event"), - } - } - - nodes[0].node.handle_open_channel(node_c_id, &open_chan_msg_chan_2_0); - { - let events = nodes[0].node.get_and_clear_pending_msg_events(); - assert_eq!(events.len(), 1); - match &events[0] { - MessageSendEvent::SendAcceptChannel { node_id, msg } => { - assert_eq!(node_id, &node_c_id); - assert_eq!( - msg.common_fields.temporary_channel_id, - open_chan_msg_chan_1_0.common_fields.temporary_channel_id - ); - }, - _ => panic!("Unexpected event"), - } - } -} - #[xtest(feature = "_externalize_tests")] pub fn test_peer_funding_sidechannel() { // Test that if a peer somehow learns which txid we'll use for our channel funding before we @@ -8814,304 +7870,65 @@ pub fn test_duplicate_conflicting_funding_from_second_peer() { } #[xtest(feature = "_externalize_tests")] -pub fn test_duplicate_funding_err_in_funding() { - // Test that if we have a live channel with one peer, then another peer comes along and tries - // to create a second channel with the same txid we'll fail and not overwrite the - // outpoint_to_peer map in `ChannelManager`. +pub fn test_error_chans_closed() { + // Test that we properly handle error messages, closing appropriate channels. // - // This was previously broken. + // Prior to #787 we'd allow a peer to make us force-close a channel we had with a different + // peer. The "real" fix for that is to index channels with peers_ids, however in the mean time + // we can test various edge cases around it to ensure we don't regress. let chanmon_cfgs = create_chanmon_cfgs(3); let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]); let nodes = create_network(3, &node_cfgs, &node_chanmgrs); let node_b_id = nodes[1].node.get_our_node_id(); - let node_c_id = nodes[2].node.get_our_node_id(); - let (_, _, _, real_channel_id, funding_tx) = create_chan_between_nodes(&nodes[0], &nodes[1]); - let real_chan_funding_txo = - chain::transaction::OutPoint { txid: funding_tx.compute_txid(), index: 0 }; - assert_eq!(ChannelId::v1_from_funding_outpoint(real_chan_funding_txo), real_channel_id); - - nodes[2].node.create_channel(node_b_id, 100_000, 0, 42, None, None).unwrap(); - let mut open_chan_msg = get_event_msg!(nodes[2], MessageSendEvent::SendOpenChannel, node_b_id); - let node_c_temp_chan_id = open_chan_msg.common_fields.temporary_channel_id; - open_chan_msg.common_fields.temporary_channel_id = real_channel_id; - nodes[1].node.handle_open_channel(node_c_id, &open_chan_msg); - let mut accept_chan_msg = - get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, node_c_id); - accept_chan_msg.common_fields.temporary_channel_id = node_c_temp_chan_id; - nodes[2].node.handle_accept_channel(node_b_id, &accept_chan_msg); + // Create some initial channels + let chan_1 = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100000, 10001); + let chan_2 = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100000, 10001); + let chan_3 = create_announced_chan_between_nodes_with_value(&nodes, 0, 2, 100000, 10001); - // Now that we have a second channel with the same funding txo, send a bogus funding message - // and let nodes[1] remove the inbound channel. - let (_, fund_tx, _) = create_funding_transaction(&nodes[2], &node_b_id, 100_000, 42); + assert_eq!(nodes[0].node.list_usable_channels().len(), 3); + assert_eq!(nodes[1].node.list_usable_channels().len(), 2); + assert_eq!(nodes[2].node.list_usable_channels().len(), 1); - nodes[2].node.funding_transaction_generated(node_c_temp_chan_id, node_b_id, fund_tx).unwrap(); + // Closing a channel from a different peer has no effect + nodes[0].node.handle_error( + node_b_id, + &msgs::ErrorMessage { channel_id: chan_3.2, data: "ERR".to_owned() }, + ); + assert_eq!(nodes[0].node.list_usable_channels().len(), 3); - let mut funding_created_msg = - get_event_msg!(nodes[2], MessageSendEvent::SendFundingCreated, node_b_id); - funding_created_msg.temporary_channel_id = real_channel_id; - // Make the signature invalid by changing the funding output - funding_created_msg.funding_output_index += 10; - nodes[1].node.handle_funding_created(node_c_id, &funding_created_msg); - get_err_msg(&nodes[1], &node_c_id); - let err = "Invalid funding_created signature from peer".to_owned(); - let reason = ClosureReason::ProcessingError { err }; - let expected_closing = ExpectedCloseEvent::from_id_reason(real_channel_id, false, reason); - check_closed_events(&nodes[1], &[expected_closing]); -} + // Closing one channel doesn't impact others + nodes[0].node.handle_error( + node_b_id, + &msgs::ErrorMessage { channel_id: chan_2.2, data: "ERR".to_owned() }, + ); + check_added_monitors(&nodes[0], 1); + check_closed_broadcast!(nodes[0], false); -#[xtest(feature = "_externalize_tests")] -pub fn test_duplicate_chan_id() { - // Test that if a given peer tries to open a channel with the same channel_id as one that is - // already open we reject it and keep the old channel. - // - // Previously, full_stack_target managed to figure out that if you tried to open two channels - // with the same funding output (ie post-funding channel_id), we'd create a monitor update for - // the existing channel when we detect the duplicate new channel, screwing up our monitor - // updating logic for the existing channel. - let chanmon_cfgs = create_chanmon_cfgs(2); - let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); - let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); - let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + let reason = + ClosureReason::CounterpartyForceClosed { peer_msg: UntrustedString("ERR".to_string()) }; + check_closed_event!(nodes[0], 1, reason, [node_b_id], 100000); - let node_a_id = nodes[0].node.get_our_node_id(); - let node_b_id = nodes[1].node.get_our_node_id(); + assert_eq!(nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0).len(), 1); + assert_eq!(nodes[0].node.list_usable_channels().len(), 2); + assert!( + nodes[0].node.list_usable_channels()[0].channel_id == chan_1.2 + || nodes[0].node.list_usable_channels()[1].channel_id == chan_1.2 + ); + assert!( + nodes[0].node.list_usable_channels()[0].channel_id == chan_3.2 + || nodes[0].node.list_usable_channels()[1].channel_id == chan_3.2 + ); - // Create an initial channel - nodes[0].node.create_channel(node_b_id, 100000, 10001, 42, None, None).unwrap(); - let mut open_chan_msg = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); - nodes[1].node.handle_open_channel(node_a_id, &open_chan_msg); - nodes[0].node.handle_accept_channel( + // A null channel ID should close all channels + let _chan_4 = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100000, 10001); + nodes[0].node.handle_error( node_b_id, - &get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, node_a_id), + &msgs::ErrorMessage { channel_id: ChannelId::new_zero(), data: "ERR".to_owned() }, ); - - // Try to create a second channel with the same temporary_channel_id as the first and check - // that it is rejected. - nodes[1].node.handle_open_channel(node_a_id, &open_chan_msg); - { - let events = nodes[1].node.get_and_clear_pending_msg_events(); - assert_eq!(events.len(), 1); - match events[0] { - MessageSendEvent::HandleError { - action: ErrorAction::SendErrorMessage { ref msg }, - node_id, - } => { - // Technically, at this point, nodes[1] would be justified in thinking both the - // first (valid) and second (invalid) channels are closed, given they both have - // the same non-temporary channel_id. However, currently we do not, so we just - // move forward with it. - assert_eq!(msg.channel_id, open_chan_msg.common_fields.temporary_channel_id); - assert_eq!(node_id, node_a_id); - }, - _ => panic!("Unexpected event"), - } - } - - // Move the first channel through the funding flow... - let (temp_channel_id, tx, _) = create_funding_transaction(&nodes[0], &node_b_id, 100000, 42); - - nodes[0].node.funding_transaction_generated(temp_channel_id, node_b_id, tx.clone()).unwrap(); - check_added_monitors(&nodes[0], 0); - - let mut funding_created_msg = - get_event_msg!(nodes[0], MessageSendEvent::SendFundingCreated, node_b_id); - let channel_id = ChannelId::v1_from_funding_txid( - funding_created_msg.funding_txid.as_byte_array(), - funding_created_msg.funding_output_index, - ); - - nodes[1].node.handle_funding_created(node_a_id, &funding_created_msg); - { - let mut added_monitors = nodes[1].chain_monitor.added_monitors.lock().unwrap(); - assert_eq!(added_monitors.len(), 1); - assert_eq!(added_monitors[0].0, channel_id); - added_monitors.clear(); - } - expect_channel_pending_event(&nodes[1], &node_a_id); - - let funding_signed_msg = - get_event_msg!(nodes[1], MessageSendEvent::SendFundingSigned, node_a_id); - - let funding_outpoint = crate::chain::transaction::OutPoint { - txid: funding_created_msg.funding_txid, - index: funding_created_msg.funding_output_index, - }; - let channel_id = ChannelId::v1_from_funding_outpoint(funding_outpoint); - - // Now we have the first channel past funding_created (ie it has a txid-based channel_id, not a - // temporary one). - - // First try to open a second channel with a temporary channel id equal to the txid-based one. - // Technically this is allowed by the spec, but we don't support it and there's little reason - // to. Still, it shouldn't cause any other issues. - open_chan_msg.common_fields.temporary_channel_id = channel_id; - nodes[1].node.handle_open_channel(node_a_id, &open_chan_msg); - { - let events = nodes[1].node.get_and_clear_pending_msg_events(); - assert_eq!(events.len(), 1); - match events[0] { - MessageSendEvent::HandleError { - action: ErrorAction::SendErrorMessage { ref msg }, - node_id, - } => { - // Technically, at this point, nodes[1] would be justified in thinking both - // channels are closed, but currently we do not, so we just move forward with it. - assert_eq!(msg.channel_id, open_chan_msg.common_fields.temporary_channel_id); - assert_eq!(node_id, node_a_id); - }, - _ => panic!("Unexpected event"), - } - } - - // Now try to create a second channel which has a duplicate funding output. - nodes[0].node.create_channel(node_b_id, 100000, 10001, 42, None, None).unwrap(); - let open_chan_2_msg = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); - nodes[1].node.handle_open_channel(node_a_id, &open_chan_2_msg); - nodes[0].node.handle_accept_channel( - node_b_id, - &get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, node_a_id), - ); - create_funding_transaction(&nodes[0], &node_b_id, 100000, 42); // Get and check the FundingGenerationReady event - - let funding_created = { - let per_peer_state = nodes[0].node.per_peer_state.read().unwrap(); - let mut a_peer_state = per_peer_state.get(&node_b_id).unwrap().lock().unwrap(); - // Once we call `get_funding_created` the channel has a duplicate channel_id as - // another channel in the ChannelManager - an invalid state. Thus, we'd panic later when we - // try to create another channel. Instead, we drop the channel entirely here (leaving the - // channelmanager in a possibly nonsense state instead). - let chan_id = open_chan_2_msg.common_fields.temporary_channel_id; - let mut channel = a_peer_state.channel_by_id.remove(&chan_id).unwrap(); - - if let Some(mut chan) = channel.as_unfunded_outbound_v1_mut() { - let logger = test_utils::TestLogger::new(); - chan.get_funding_created(tx.clone(), funding_outpoint, false, &&logger) - .map_err(|_| ()) - .unwrap() - } else { - panic!("Unexpected Channel phase") - } - .unwrap() - }; - check_added_monitors(&nodes[0], 0); - nodes[1].node.handle_funding_created(node_a_id, &funding_created); - // At this point we'll look up if the channel_id is present and immediately fail the channel - // without trying to persist the `ChannelMonitor`. - check_added_monitors(&nodes[1], 0); - - let reason = ClosureReason::ProcessingError { - err: "Already had channel with the new channel_id".to_owned(), - }; - let close_event = - ExpectedCloseEvent::from_id_reason(funding_created.temporary_channel_id, false, reason); - check_closed_events(&nodes[1], &[close_event]); - - // ...still, nodes[1] will reject the duplicate channel. - { - let events = nodes[1].node.get_and_clear_pending_msg_events(); - assert_eq!(events.len(), 1); - match events[0] { - MessageSendEvent::HandleError { - action: ErrorAction::SendErrorMessage { ref msg }, - node_id, - } => { - // Technically, at this point, nodes[1] would be justified in thinking both - // channels are closed, but currently we do not, so we just move forward with it. - assert_eq!(msg.channel_id, funding_created.temporary_channel_id); - assert_eq!(node_id, node_a_id); - }, - _ => panic!("Unexpected event"), - } - } - - // finally, finish creating the original channel and send a payment over it to make sure - // everything is functional. - nodes[0].node.handle_funding_signed(node_b_id, &funding_signed_msg); - { - let mut added_monitors = nodes[0].chain_monitor.added_monitors.lock().unwrap(); - assert_eq!(added_monitors.len(), 1); - assert_eq!(added_monitors[0].0, channel_id); - added_monitors.clear(); - } - expect_channel_pending_event(&nodes[0], &node_b_id); - - let events_4 = nodes[0].node.get_and_clear_pending_events(); - assert_eq!(events_4.len(), 0); - assert_eq!(nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().len(), 1); - assert_eq!(nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap()[0], tx); - - let (channel_ready, _) = - create_chan_between_nodes_with_value_confirm(&nodes[0], &nodes[1], &tx); - let (announcement, as_update, bs_update) = - create_chan_between_nodes_with_value_b(&nodes[0], &nodes[1], &channel_ready); - update_nodes_with_chan_announce(&nodes, 0, 1, &announcement, &as_update, &bs_update); - - send_payment(&nodes[0], &[&nodes[1]], 8000000); -} - -#[xtest(feature = "_externalize_tests")] -pub fn test_error_chans_closed() { - // Test that we properly handle error messages, closing appropriate channels. - // - // Prior to #787 we'd allow a peer to make us force-close a channel we had with a different - // peer. The "real" fix for that is to index channels with peers_ids, however in the mean time - // we can test various edge cases around it to ensure we don't regress. - let chanmon_cfgs = create_chanmon_cfgs(3); - let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); - let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]); - let nodes = create_network(3, &node_cfgs, &node_chanmgrs); - - let node_b_id = nodes[1].node.get_our_node_id(); - - // Create some initial channels - let chan_1 = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100000, 10001); - let chan_2 = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100000, 10001); - let chan_3 = create_announced_chan_between_nodes_with_value(&nodes, 0, 2, 100000, 10001); - - assert_eq!(nodes[0].node.list_usable_channels().len(), 3); - assert_eq!(nodes[1].node.list_usable_channels().len(), 2); - assert_eq!(nodes[2].node.list_usable_channels().len(), 1); - - // Closing a channel from a different peer has no effect - nodes[0].node.handle_error( - node_b_id, - &msgs::ErrorMessage { channel_id: chan_3.2, data: "ERR".to_owned() }, - ); - assert_eq!(nodes[0].node.list_usable_channels().len(), 3); - - // Closing one channel doesn't impact others - nodes[0].node.handle_error( - node_b_id, - &msgs::ErrorMessage { channel_id: chan_2.2, data: "ERR".to_owned() }, - ); - check_added_monitors(&nodes[0], 1); - check_closed_broadcast!(nodes[0], false); - - let reason = - ClosureReason::CounterpartyForceClosed { peer_msg: UntrustedString("ERR".to_string()) }; - check_closed_event!(nodes[0], 1, reason, [node_b_id], 100000); - - assert_eq!(nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0).len(), 1); - assert_eq!(nodes[0].node.list_usable_channels().len(), 2); - assert!( - nodes[0].node.list_usable_channels()[0].channel_id == chan_1.2 - || nodes[0].node.list_usable_channels()[1].channel_id == chan_1.2 - ); - assert!( - nodes[0].node.list_usable_channels()[0].channel_id == chan_3.2 - || nodes[0].node.list_usable_channels()[1].channel_id == chan_3.2 - ); - - // A null channel ID should close all channels - let _chan_4 = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100000, 10001); - nodes[0].node.handle_error( - node_b_id, - &msgs::ErrorMessage { channel_id: ChannelId::new_zero(), data: "ERR".to_owned() }, - ); - check_added_monitors(&nodes[0], 2); + check_added_monitors(&nodes[0], 2); let reason = ClosureReason::CounterpartyForceClosed { peer_msg: UntrustedString("ERR".to_string()) }; @@ -9141,192 +7958,6 @@ pub fn test_error_chans_closed() { assert!(nodes[0].node.list_usable_channels()[0].channel_id == chan_3.2); } -#[xtest(feature = "_externalize_tests")] -pub fn test_invalid_funding_tx() { - // Test that we properly handle invalid funding transactions sent to us from a peer. - // - // Previously, all other major lightning implementations had failed to properly sanitize - // funding transactions from their counterparties, leading to a multi-implementation critical - // security vulnerability (though we always sanitized properly, we've previously had - // un-released crashes in the sanitization process). - // - // Further, if the funding transaction is consensus-valid, confirms, and is later spent, we'd - // previously have crashed in `ChannelMonitor` even though we closed the channel as bogus and - // gave up on it. We test this here by generating such a transaction. - let chanmon_cfgs = create_chanmon_cfgs(2); - let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); - let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); - let nodes = create_network(2, &node_cfgs, &node_chanmgrs); - - let node_a_id = nodes[0].node.get_our_node_id(); - let node_b_id = nodes[1].node.get_our_node_id(); - - nodes[0].node.create_channel(node_b_id, 100_000, 10_000, 42, None, None).unwrap(); - nodes[1].node.handle_open_channel( - node_a_id, - &get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id), - ); - nodes[0].node.handle_accept_channel( - node_b_id, - &get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, node_a_id), - ); - - let (temporary_channel_id, mut tx, _) = - create_funding_transaction(&nodes[0], &node_b_id, 100_000, 42); - - // Create a witness program which can be spent by a 4-empty-stack-elements witness and which is - // 136 bytes long. This matches our "accepted HTLC preimage spend" matching, previously causing - // a panic as we'd try to extract a 32 byte preimage from a witness element without checking - // its length. - let mut wit_program: Vec = - channelmonitor::deliberately_bogus_accepted_htlc_witness_program(); - let wit_program_script: ScriptBuf = wit_program.into(); - for output in tx.output.iter_mut() { - // Make the confirmed funding transaction have a bogus script_pubkey - output.script_pubkey = ScriptBuf::new_p2wsh(&wit_program_script.wscript_hash()); - } - - nodes[0] - .node - .funding_transaction_generated_unchecked(temporary_channel_id, node_b_id, tx.clone(), 0) - .unwrap(); - nodes[1].node.handle_funding_created( - node_a_id, - &get_event_msg!(nodes[0], MessageSendEvent::SendFundingCreated, node_b_id), - ); - check_added_monitors(&nodes[1], 1); - expect_channel_pending_event(&nodes[1], &node_a_id); - - nodes[0].node.handle_funding_signed( - node_b_id, - &get_event_msg!(nodes[1], MessageSendEvent::SendFundingSigned, node_a_id), - ); - check_added_monitors(&nodes[0], 1); - expect_channel_pending_event(&nodes[0], &node_b_id); - - let events_1 = nodes[0].node.get_and_clear_pending_events(); - assert_eq!(events_1.len(), 0); - - assert_eq!(nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().len(), 1); - assert_eq!(nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap()[0], tx); - nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().clear(); - - let expected_err = "funding tx had wrong script/value or output index"; - confirm_transaction_at(&nodes[1], &tx, 1); - - let reason = ClosureReason::ProcessingError { err: expected_err.to_string() }; - check_closed_event!(nodes[1], 1, reason, [node_a_id], 100000); - - check_added_monitors(&nodes[1], 1); - let events_2 = nodes[1].node.get_and_clear_pending_msg_events(); - assert_eq!(events_2.len(), 1); - if let MessageSendEvent::HandleError { node_id, action } = &events_2[0] { - assert_eq!(*node_id, node_a_id); - if let msgs::ErrorAction::SendErrorMessage { msg } = action { - assert_eq!( - msg.data, - "Channel closed because of an exception: ".to_owned() + expected_err - ); - } else { - panic!(); - } - } else { - panic!(); - } - assert_eq!(nodes[1].node.list_channels().len(), 0); - - // Now confirm a spend of the (bogus) funding transaction. As long as the witness is 5 elements - // long the ChannelMonitor will try to read 32 bytes from the second-to-last element, panicing - // as its not 32 bytes long. - let mut spend_tx = Transaction { - version: Version::TWO, - lock_time: LockTime::ZERO, - input: tx - .output - .iter() - .enumerate() - .map(|(idx, _)| TxIn { - previous_output: BitcoinOutPoint { txid: tx.compute_txid(), vout: idx as u32 }, - script_sig: ScriptBuf::new(), - sequence: Sequence::ENABLE_RBF_NO_LOCKTIME, - witness: Witness::from_slice( - &channelmonitor::deliberately_bogus_accepted_htlc_witness(), - ), - }) - .collect(), - output: vec![TxOut { value: Amount::from_sat(1000), script_pubkey: ScriptBuf::new() }], - }; - check_spends!(spend_tx, tx); - mine_transaction(&nodes[1], &spend_tx); -} - -#[xtest(feature = "_externalize_tests")] -pub fn test_coinbase_funding_tx() { - // Miners are able to fund channels directly from coinbase transactions, however - // by consensus rules, outputs of a coinbase transaction are encumbered by a 100 - // block maturity timelock. To ensure that a (non-0conf) channel like this is enforceable - // on-chain, the minimum depth is updated to 100 blocks for coinbase funding transactions. - // - // Note that 0conf channels with coinbase funding transactions are unaffected and are - // immediately operational after opening. - let chanmon_cfgs = create_chanmon_cfgs(2); - let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); - let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); - let nodes = create_network(2, &node_cfgs, &node_chanmgrs); - - let node_a_id = nodes[0].node.get_our_node_id(); - let node_b_id = nodes[1].node.get_our_node_id(); - - nodes[0].node.create_channel(node_b_id, 100000, 10001, 42, None, None).unwrap(); - let open_channel = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); - - nodes[1].node.handle_open_channel(node_a_id, &open_channel); - let accept_channel = get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, node_a_id); - - nodes[0].node.handle_accept_channel(node_b_id, &accept_channel); - - // Create the coinbase funding transaction. - let (channel_id, tx, _) = - create_coinbase_funding_transaction(&nodes[0], &node_b_id, 100000, 42); - - nodes[0].node.funding_transaction_generated(channel_id, node_b_id, tx.clone()).unwrap(); - check_added_monitors(&nodes[0], 0); - let funding_created = get_event_msg!(nodes[0], MessageSendEvent::SendFundingCreated, node_b_id); - - nodes[1].node.handle_funding_created(node_a_id, &funding_created); - check_added_monitors(&nodes[1], 1); - expect_channel_pending_event(&nodes[1], &node_a_id); - - let funding_signed = get_event_msg!(nodes[1], MessageSendEvent::SendFundingSigned, node_a_id); - - nodes[0].node.handle_funding_signed(node_b_id, &funding_signed); - check_added_monitors(&nodes[0], 1); - - expect_channel_pending_event(&nodes[0], &node_b_id); - assert!(nodes[0].node.get_and_clear_pending_events().is_empty()); - - // Starting at height 0, we "confirm" the coinbase at height 1. - confirm_transaction_at(&nodes[0], &tx, 1); - // We connect 98 more blocks to have 99 confirmations for the coinbase transaction. - connect_blocks(&nodes[0], COINBASE_MATURITY - 2); - // Check that we have no pending message events (we have not queued a `channel_ready` yet). - assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty()); - // Now connect one more block which results in 100 confirmations of the coinbase transaction. - connect_blocks(&nodes[0], 1); - // There should now be a `channel_ready` which can be handled. - let _ = &nodes[1].node.handle_channel_ready( - node_a_id, - &get_event_msg!(&nodes[0], MessageSendEvent::SendChannelReady, node_b_id), - ); - - confirm_transaction_at(&nodes[1], &tx, 1); - connect_blocks(&nodes[1], COINBASE_MATURITY - 2); - assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty()); - connect_blocks(&nodes[1], 1); - expect_channel_ready_event(&nodes[1], &node_a_id); - create_chan_between_nodes_with_value_confirm_second(&nodes[0], &nodes[1]); -} - fn do_test_tx_confirmed_skipping_blocks_immediate_broadcast(test_height_before_timelock: bool) { // In the first version of the chain::Confirm interface, after a refactor was made to not // broadcast CSV-locked transactions until their CSV lock is up, we wouldn't reliably broadcast @@ -10598,118 +9229,8 @@ fn test_nondust_htlc_fees_dust_exposure_delta() { ); } -#[xtest(feature = "_externalize_tests")] -pub fn test_non_final_funding_tx() { - let chanmon_cfgs = create_chanmon_cfgs(2); - let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); - let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); - let nodes = create_network(2, &node_cfgs, &node_chanmgrs); - - let node_a_id = nodes[0].node.get_our_node_id(); - let node_b_id = nodes[1].node.get_our_node_id(); - - let temp_channel_id = - nodes[0].node.create_channel(node_b_id, 100_000, 0, 42, None, None).unwrap(); - let open_channel_message = - get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); - nodes[1].node.handle_open_channel(node_a_id, &open_channel_message); - let accept_channel_message = - get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, node_a_id); - nodes[0].node.handle_accept_channel(node_b_id, &accept_channel_message); - - let best_height = nodes[0].node.best_block.read().unwrap().height; - - let chan_id = *nodes[0].network_chan_count.borrow(); - let events = nodes[0].node.get_and_clear_pending_events(); - let input = TxIn { - previous_output: BitcoinOutPoint::null(), - script_sig: bitcoin::ScriptBuf::new(), - sequence: Sequence(1), - witness: Witness::from_slice(&[&[1]]), - }; - assert_eq!(events.len(), 1); - let mut tx = match events[0] { - Event::FundingGenerationReady { ref channel_value_satoshis, ref output_script, .. } => { - // Timelock the transaction _beyond_ the best client height + 1. - Transaction { - version: Version(chan_id as i32), - lock_time: LockTime::from_height(best_height + 2).unwrap(), - input: vec![input], - output: vec![TxOut { - value: Amount::from_sat(*channel_value_satoshis), - script_pubkey: output_script.clone(), - }], - } - }, - _ => panic!("Unexpected event"), - }; - // Transaction should fail as it's evaluated as non-final for propagation. - match nodes[0].node.funding_transaction_generated(temp_channel_id, node_b_id, tx.clone()) { - Err(APIError::APIMisuseError { err }) => { - assert_eq!(format!("Funding transaction absolute timelock is non-final"), err); - }, - _ => panic!(), - } - let err = "Error in transaction funding: Misuse error: Funding transaction absolute timelock is non-final"; - let reason = ClosureReason::ProcessingError { err: err.to_owned() }; - let event = ExpectedCloseEvent::from_id_reason(temp_channel_id, false, reason); - check_closed_events(&nodes[0], &[event]); - assert_eq!(get_err_msg(&nodes[0], &node_b_id).data, err); -} - -#[xtest(feature = "_externalize_tests")] -pub fn test_non_final_funding_tx_within_headroom() { - let chanmon_cfgs = create_chanmon_cfgs(2); - let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); - let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); - let nodes = create_network(2, &node_cfgs, &node_chanmgrs); - - let node_a_id = nodes[0].node.get_our_node_id(); - let node_b_id = nodes[1].node.get_our_node_id(); - - let temp_channel_id = - nodes[0].node.create_channel(node_b_id, 100_000, 0, 42, None, None).unwrap(); - let open_channel_message = - get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); - nodes[1].node.handle_open_channel(node_a_id, &open_channel_message); - let accept_channel_message = - get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, node_a_id); - nodes[0].node.handle_accept_channel(node_b_id, &accept_channel_message); - - let best_height = nodes[0].node.best_block.read().unwrap().height; - - let chan_id = *nodes[0].network_chan_count.borrow(); - let events = nodes[0].node.get_and_clear_pending_events(); - let input = TxIn { - previous_output: BitcoinOutPoint::null(), - script_sig: bitcoin::ScriptBuf::new(), - sequence: Sequence(1), - witness: Witness::from_slice(&[[1]]), - }; - assert_eq!(events.len(), 1); - let mut tx = match events[0] { - Event::FundingGenerationReady { ref channel_value_satoshis, ref output_script, .. } => { - // Timelock the transaction within a +1 headroom from the best block. - Transaction { - version: Version(chan_id as i32), - lock_time: LockTime::from_consensus(best_height + 1), - input: vec![input], - output: vec![TxOut { - value: Amount::from_sat(*channel_value_satoshis), - script_pubkey: output_script.clone(), - }], - } - }, - _ => panic!("Unexpected event"), - }; - - // Transaction should be accepted if it's in a +1 headroom from best block. - nodes[0].node.funding_transaction_generated(temp_channel_id, node_b_id, tx.clone()).unwrap(); - get_event_msg!(nodes[0], MessageSendEvent::SendFundingCreated, node_b_id); -} - -fn do_payment_with_custom_min_final_cltv_expiry(valid_delta: bool, use_user_hash: bool) { - let mut chanmon_cfgs = create_chanmon_cfgs(2); +fn do_payment_with_custom_min_final_cltv_expiry(valid_delta: bool, use_user_hash: bool) { + let mut chanmon_cfgs = create_chanmon_cfgs(2); let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); let nodes = create_network(2, &node_cfgs, &node_chanmgrs); @@ -11049,100 +9570,6 @@ pub fn test_remove_expired_inbound_unfunded_channels() { check_closed_event(&nodes[1], 1, reason, false, &[node_a_id], 100000); } -#[xtest(feature = "_externalize_tests")] -pub fn test_channel_close_when_not_timely_accepted() { - // Create network of two nodes - let chanmon_cfgs = create_chanmon_cfgs(2); - let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); - let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); - let nodes = create_network(2, &node_cfgs, &node_chanmgrs); - - let node_a_id = nodes[0].node.get_our_node_id(); - let node_b_id = nodes[1].node.get_our_node_id(); - - // Simulate peer-disconnects mid-handshake - // The channel is initiated from the node 0 side, - // but the nodes disconnect before node 1 could send accept channel - let create_chan_id = - nodes[0].node.create_channel(node_b_id, 100000, 10001, 42, None, None).unwrap(); - let open_channel_msg = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); - assert_eq!(open_channel_msg.common_fields.temporary_channel_id, create_chan_id); - - nodes[0].node.peer_disconnected(node_b_id); - nodes[1].node.peer_disconnected(node_a_id); - - // Make sure that we have not removed the OutboundV1Channel from node[0] immediately. - assert_eq!(nodes[0].node.list_channels().len(), 1); - - // Since channel was inbound from node[1] perspective, it should have been dropped immediately. - assert_eq!(nodes[1].node.list_channels().len(), 0); - - // In the meantime, some time passes. - for _ in 0..UNFUNDED_CHANNEL_AGE_LIMIT_TICKS { - nodes[0].node.timer_tick_occurred(); - } - - // Since we disconnected from peer and did not connect back within time, - // we should have forced-closed the channel by now. - let reason = ClosureReason::FundingTimedOut; - check_closed_event!(nodes[0], 1, reason, [node_b_id], 100000); - assert_eq!(nodes[0].node.list_channels().len(), 0); - - { - // Since accept channel message was never received - // The channel should be forced close by now from node 0 side - // and the peer removed from per_peer_state - let node_0_per_peer_state = nodes[0].node.per_peer_state.read().unwrap(); - assert_eq!(node_0_per_peer_state.len(), 0); - } -} - -#[xtest(feature = "_externalize_tests")] -pub fn test_rebroadcast_open_channel_when_reconnect_mid_handshake() { - // Create network of two nodes - let chanmon_cfgs = create_chanmon_cfgs(2); - let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); - let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); - let nodes = create_network(2, &node_cfgs, &node_chanmgrs); - - let node_a_id = nodes[0].node.get_our_node_id(); - let node_b_id = nodes[1].node.get_our_node_id(); - - // Simulate peer-disconnects mid-handshake - // The channel is initiated from the node 0 side, - // but the nodes disconnect before node 1 could send accept channel - let create_chan_id = - nodes[0].node.create_channel(node_b_id, 100000, 10001, 42, None, None).unwrap(); - let open_channel_msg = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); - assert_eq!(open_channel_msg.common_fields.temporary_channel_id, create_chan_id); - - nodes[0].node.peer_disconnected(node_b_id); - nodes[1].node.peer_disconnected(node_a_id); - - // Make sure that we have not removed the OutboundV1Channel from node[0] immediately. - assert_eq!(nodes[0].node.list_channels().len(), 1); - - // Since channel was inbound from node[1] perspective, it should have been immediately dropped. - assert_eq!(nodes[1].node.list_channels().len(), 0); - - // The peers now reconnect - let init_msg = msgs::Init { - features: nodes[0].node.init_features(), - networks: None, - remote_network_address: None, - }; - nodes[0].node.peer_connected(node_b_id, &init_msg, true).unwrap(); - nodes[1].node.peer_connected(node_a_id, &init_msg, false).unwrap(); - - // Make sure the SendOpenChannel message is added to node_0 pending message events - let msg_events = nodes[0].node.get_and_clear_pending_msg_events(); - assert_eq!(msg_events.len(), 1); - match &msg_events[0] { - MessageSendEvent::SendOpenChannel { msg, .. } => assert_eq!(msg, &open_channel_msg), - _ => panic!("Unexpected message."), - } -} - fn do_test_multi_post_event_actions(do_reload: bool) { // Tests handling multiple post-Event actions at once. // There is specific code in ChannelManager to handle channels where multiple post-Event @@ -11249,471 +9676,3 @@ pub fn test_multi_post_event_actions() { do_test_multi_post_event_actions(true); do_test_multi_post_event_actions(false); } - -#[xtest(feature = "_externalize_tests")] -pub fn test_batch_channel_open() { - let chanmon_cfgs = create_chanmon_cfgs(3); - let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); - let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]); - let nodes = create_network(3, &node_cfgs, &node_chanmgrs); - - let node_a_id = nodes[0].node.get_our_node_id(); - let node_b_id = nodes[1].node.get_our_node_id(); - let node_c_id = nodes[2].node.get_our_node_id(); - - // Initiate channel opening and create the batch channel funding transaction. - let (tx, funding_created_msgs) = create_batch_channel_funding( - &nodes[0], - &[(&nodes[1], 100_000, 0, 42, None), (&nodes[2], 200_000, 0, 43, None)], - ); - - // Go through the funding_created and funding_signed flow with node 1. - nodes[1].node.handle_funding_created(node_a_id, &funding_created_msgs[0]); - check_added_monitors(&nodes[1], 1); - expect_channel_pending_event(&nodes[1], &node_a_id); - - let funding_signed_msg = - get_event_msg!(nodes[1], MessageSendEvent::SendFundingSigned, node_a_id); - nodes[0].node.handle_funding_signed(node_b_id, &funding_signed_msg); - check_added_monitors(&nodes[0], 1); - - // The transaction should not have been broadcast before all channels are ready. - assert_eq!(nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().len(), 0); - - // Go through the funding_created and funding_signed flow with node 2. - nodes[2].node.handle_funding_created(node_a_id, &funding_created_msgs[1]); - check_added_monitors(&nodes[2], 1); - expect_channel_pending_event(&nodes[2], &node_a_id); - - let funding_signed_msg = - get_event_msg!(nodes[2], MessageSendEvent::SendFundingSigned, node_a_id); - chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::InProgress); - nodes[0].node.handle_funding_signed(node_c_id, &funding_signed_msg); - check_added_monitors(&nodes[0], 1); - - // The transaction should not have been broadcast before persisting all monitors has been - // completed. - assert_eq!(nodes[0].tx_broadcaster.txn_broadcast().len(), 0); - assert_eq!(nodes[0].node.get_and_clear_pending_events().len(), 0); - - // Complete the persistence of the monitor. - nodes[0].chain_monitor.complete_sole_pending_chan_update(&ChannelId::v1_from_funding_outpoint( - OutPoint { txid: tx.compute_txid(), index: 1 }, - )); - let events = nodes[0].node.get_and_clear_pending_events(); - - // The transaction should only have been broadcast now. - let broadcasted_txs = nodes[0].tx_broadcaster.txn_broadcast(); - assert_eq!(broadcasted_txs.len(), 1); - assert_eq!(broadcasted_txs[0], tx); - - assert_eq!(events.len(), 2); - assert!(events.iter().any(|e| matches!( - *e, - crate::events::Event::ChannelPending { - ref counterparty_node_id, - .. - } if counterparty_node_id == &node_b_id, - ))); - assert!(events.iter().any(|e| matches!( - *e, - crate::events::Event::ChannelPending { - ref counterparty_node_id, - .. - } if counterparty_node_id == &node_c_id, - ))); -} - -#[xtest(feature = "_externalize_tests")] -pub fn test_close_in_funding_batch() { - // This test ensures that if one of the channels - // in the batch closes, the complete batch will close. - let chanmon_cfgs = create_chanmon_cfgs(3); - let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); - let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]); - let nodes = create_network(3, &node_cfgs, &node_chanmgrs); - - let node_a_id = nodes[0].node.get_our_node_id(); - let node_b_id = nodes[1].node.get_our_node_id(); - - // Initiate channel opening and create the batch channel funding transaction. - let (tx, funding_created_msgs) = create_batch_channel_funding( - &nodes[0], - &[(&nodes[1], 100_000, 0, 42, None), (&nodes[2], 200_000, 0, 43, None)], - ); - - // Go through the funding_created and funding_signed flow with node 1. - nodes[1].node.handle_funding_created(node_a_id, &funding_created_msgs[0]); - check_added_monitors(&nodes[1], 1); - expect_channel_pending_event(&nodes[1], &node_a_id); - - let funding_signed_msg = - get_event_msg!(nodes[1], MessageSendEvent::SendFundingSigned, node_a_id); - nodes[0].node.handle_funding_signed(node_b_id, &funding_signed_msg); - check_added_monitors(&nodes[0], 1); - - // The transaction should not have been broadcast before all channels are ready. - assert_eq!(nodes[0].tx_broadcaster.txn_broadcast().len(), 0); - - // Force-close the channel for which we've completed the initial monitor. - let funding_txo_1 = OutPoint { txid: tx.compute_txid(), index: 0 }; - let funding_txo_2 = OutPoint { txid: tx.compute_txid(), index: 1 }; - let channel_id_1 = ChannelId::v1_from_funding_outpoint(funding_txo_1); - let channel_id_2 = ChannelId::v1_from_funding_outpoint(funding_txo_2); - let err = "Channel force-closed".to_string(); - nodes[0].node.force_close_broadcasting_latest_txn(&channel_id_1, &node_b_id, err).unwrap(); - - // The monitor should become closed. - check_added_monitors(&nodes[0], 1); - { - let mut monitor_updates = nodes[0].chain_monitor.monitor_updates.lock().unwrap(); - let monitor_updates_1 = monitor_updates.get(&channel_id_1).unwrap(); - assert_eq!(monitor_updates_1.len(), 1); - assert_eq!(monitor_updates_1[0].updates.len(), 1); - assert!(matches!( - monitor_updates_1[0].updates[0], - ChannelMonitorUpdateStep::ChannelForceClosed { .. } - )); - } - - let msg_events = nodes[0].node.get_and_clear_pending_msg_events(); - match msg_events[0] { - MessageSendEvent::HandleError { .. } => (), - _ => panic!("Unexpected message."), - } - - // Because the funding was never broadcasted, we should never bother to broadcast the - // commitment transactions either. - let broadcasted_txs = nodes[0].tx_broadcaster.txn_broadcast(); - assert_eq!(broadcasted_txs.len(), 0); - - // All channels in the batch should close immediately. - check_closed_events( - &nodes[0], - &[ - ExpectedCloseEvent { - channel_id: Some(channel_id_1), - discard_funding: true, - channel_funding_txo: Some(funding_txo_1), - user_channel_id: Some(42), - ..Default::default() - }, - ExpectedCloseEvent { - channel_id: Some(channel_id_2), - discard_funding: true, - channel_funding_txo: Some(funding_txo_2), - user_channel_id: Some(43), - ..Default::default() - }, - ], - ); - - // Ensure the channels don't exist anymore. - assert!(nodes[0].node.list_channels().is_empty()); -} - -#[xtest(feature = "_externalize_tests")] -pub fn test_batch_funding_close_after_funding_signed() { - let chanmon_cfgs = create_chanmon_cfgs(3); - let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); - let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]); - let nodes = create_network(3, &node_cfgs, &node_chanmgrs); - - let node_a_id = nodes[0].node.get_our_node_id(); - let node_b_id = nodes[1].node.get_our_node_id(); - let node_c_id = nodes[2].node.get_our_node_id(); - - // Initiate channel opening and create the batch channel funding transaction. - let (tx, funding_created_msgs) = create_batch_channel_funding( - &nodes[0], - &[(&nodes[1], 100_000, 0, 42, None), (&nodes[2], 200_000, 0, 43, None)], - ); - - // Go through the funding_created and funding_signed flow with node 1. - nodes[1].node.handle_funding_created(node_a_id, &funding_created_msgs[0]); - check_added_monitors(&nodes[1], 1); - expect_channel_pending_event(&nodes[1], &node_a_id); - - let funding_signed_msg = - get_event_msg!(nodes[1], MessageSendEvent::SendFundingSigned, node_a_id); - nodes[0].node.handle_funding_signed(node_b_id, &funding_signed_msg); - check_added_monitors(&nodes[0], 1); - - // Go through the funding_created and funding_signed flow with node 2. - nodes[2].node.handle_funding_created(node_a_id, &funding_created_msgs[1]); - check_added_monitors(&nodes[2], 1); - expect_channel_pending_event(&nodes[2], &node_a_id); - - let funding_signed_msg = - get_event_msg!(nodes[2], MessageSendEvent::SendFundingSigned, node_a_id); - chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::InProgress); - nodes[0].node.handle_funding_signed(node_c_id, &funding_signed_msg); - check_added_monitors(&nodes[0], 1); - - // The transaction should not have been broadcast before all channels are ready. - assert_eq!(nodes[0].tx_broadcaster.txn_broadcast().len(), 0); - - // Force-close the channel for which we've completed the initial monitor. - let funding_txo_1 = OutPoint { txid: tx.compute_txid(), index: 0 }; - let funding_txo_2 = OutPoint { txid: tx.compute_txid(), index: 1 }; - let channel_id_1 = ChannelId::v1_from_funding_outpoint(funding_txo_1); - let channel_id_2 = ChannelId::v1_from_funding_outpoint(funding_txo_2); - let err = "Channel force-closed".to_string(); - nodes[0].node.force_close_broadcasting_latest_txn(&channel_id_1, &node_b_id, err).unwrap(); - check_added_monitors(&nodes[0], 2); - { - let mut monitor_updates = nodes[0].chain_monitor.monitor_updates.lock().unwrap(); - let monitor_updates_1 = monitor_updates.get(&channel_id_1).unwrap(); - assert_eq!(monitor_updates_1.len(), 1); - assert_eq!(monitor_updates_1[0].updates.len(), 1); - assert!(matches!( - monitor_updates_1[0].updates[0], - ChannelMonitorUpdateStep::ChannelForceClosed { .. } - )); - let monitor_updates_2 = monitor_updates.get(&channel_id_2).unwrap(); - assert_eq!(monitor_updates_2.len(), 1); - assert_eq!(monitor_updates_2[0].updates.len(), 1); - assert!(matches!( - monitor_updates_2[0].updates[0], - ChannelMonitorUpdateStep::ChannelForceClosed { .. } - )); - } - let msg_events = nodes[0].node.get_and_clear_pending_msg_events(); - match msg_events[0] { - MessageSendEvent::HandleError { .. } => (), - _ => panic!("Unexpected message."), - } - - // Because the funding was never broadcasted, we should never bother to broadcast the - // commitment transactions either. - let broadcasted_txs = nodes[0].tx_broadcaster.txn_broadcast(); - assert_eq!(broadcasted_txs.len(), 0); - - // All channels in the batch should close immediately. - check_closed_events( - &nodes[0], - &[ - ExpectedCloseEvent { - channel_id: Some(channel_id_1), - discard_funding: true, - channel_funding_txo: Some(funding_txo_1), - user_channel_id: Some(42), - ..Default::default() - }, - ExpectedCloseEvent { - channel_id: Some(channel_id_2), - discard_funding: true, - channel_funding_txo: Some(funding_txo_2), - user_channel_id: Some(43), - ..Default::default() - }, - ], - ); - - // Ensure the channels don't exist anymore. - assert!(nodes[0].node.list_channels().is_empty()); -} - -#[xtest(feature = "_externalize_tests")] -pub fn test_funding_and_commitment_tx_confirm_same_block() { - // Tests that a node will forget the channel (when it only requires 1 confirmation) if the - // funding and commitment transaction confirm in the same block. - let chanmon_cfgs = create_chanmon_cfgs(2); - let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); - let mut min_depth_1_block_cfg = test_default_channel_config(); - min_depth_1_block_cfg.channel_handshake_config.minimum_depth = 1; - let node_chanmgrs = create_node_chanmgrs( - 2, - &node_cfgs, - &[Some(min_depth_1_block_cfg.clone()), Some(min_depth_1_block_cfg)], - ); - let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs); - - let node_a_id = nodes[0].node.get_our_node_id(); - let node_b_id = nodes[1].node.get_our_node_id(); - - let funding_tx = create_chan_between_nodes_with_value_init(&nodes[0], &nodes[1], 1_000_000, 0); - let chan_id = ChannelId::v1_from_funding_outpoint(chain::transaction::OutPoint { - txid: funding_tx.compute_txid(), - index: 0, - }); - - assert_eq!(nodes[0].node.list_channels().len(), 1); - assert_eq!(nodes[1].node.list_channels().len(), 1); - - let commitment_tx = { - let mon = get_monitor!(nodes[0], chan_id); - let mut txn = mon.unsafe_get_latest_holder_commitment_txn(&nodes[0].logger); - assert_eq!(txn.len(), 1); - txn.pop().unwrap() - }; - - mine_transactions(&nodes[0], &[&funding_tx, &commitment_tx]); - mine_transactions(&nodes[1], &[&funding_tx, &commitment_tx]); - - check_closed_broadcast(&nodes[0], 1, true); - check_added_monitors(&nodes[0], 1); - let reason = ClosureReason::CommitmentTxConfirmed; - check_closed_event(&nodes[0], 1, reason, false, &[node_b_id], 1_000_000); - - check_closed_broadcast(&nodes[1], 1, true); - check_added_monitors(&nodes[1], 1); - let reason = ClosureReason::CommitmentTxConfirmed; - check_closed_event(&nodes[1], 1, reason, false, &[node_a_id], 1_000_000); - - assert!(nodes[0].node.list_channels().is_empty()); - assert!(nodes[1].node.list_channels().is_empty()); -} - -#[xtest(feature = "_externalize_tests")] -pub fn test_accept_inbound_channel_errors_queued() { - // For manually accepted inbound channels, tests that a close error is correctly handled - // and the channel fails for the initiator. - let mut config0 = test_default_channel_config(); - let mut config1 = config0.clone(); - config1.channel_handshake_limits.their_to_self_delay = 1000; - config1.manually_accept_inbound_channels = true; - config0.channel_handshake_config.our_to_self_delay = 2000; - - let chanmon_cfgs = create_chanmon_cfgs(2); - let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); - let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config0), Some(config1)]); - let nodes = create_network(2, &node_cfgs, &node_chanmgrs); - - let node_a_id = nodes[0].node.get_our_node_id(); - let node_b_id = nodes[1].node.get_our_node_id(); - - nodes[0].node.create_channel(node_b_id, 100_000, 0, 42, None, None).unwrap(); - let open_channel_msg = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); - - nodes[1].node.handle_open_channel(node_a_id, &open_channel_msg); - let events = nodes[1].node.get_and_clear_pending_events(); - match events[0] { - Event::OpenChannelRequest { temporary_channel_id, .. } => { - match nodes[1].node.accept_inbound_channel(&temporary_channel_id, &node_a_id, 23, None) - { - Err(APIError::ChannelUnavailable { err: _ }) => (), - _ => panic!(), - } - }, - _ => panic!("Unexpected event"), - } - assert_eq!( - get_err_msg(&nodes[1], &node_a_id).channel_id, - open_channel_msg.common_fields.temporary_channel_id - ); -} - -#[xtest(feature = "_externalize_tests")] -pub fn test_manual_funding_abandon() { - let mut cfg = UserConfig::default(); - cfg.channel_handshake_config.minimum_depth = 1; - let chanmon_cfgs = create_chanmon_cfgs(2); - let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); - let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(cfg.clone()), Some(cfg)]); - let nodes = create_network(2, &node_cfgs, &node_chanmgrs); - - let node_a_id = nodes[0].node.get_our_node_id(); - let node_b_id = nodes[1].node.get_our_node_id(); - - assert!(nodes[0].node.create_channel(node_b_id, 100_000, 0, 42, None, None).is_ok()); - let open_channel = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); - - nodes[1].node.handle_open_channel(node_a_id, &open_channel); - let accept_channel = get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, node_a_id); - - nodes[0].node.handle_accept_channel(node_b_id, &accept_channel); - let (temp_channel_id, _tx, funding_outpoint) = - create_funding_transaction(&nodes[0], &node_b_id, 100_000, 42); - nodes[0] - .node - .unsafe_manual_funding_transaction_generated(temp_channel_id, node_b_id, funding_outpoint) - .unwrap(); - check_added_monitors(&nodes[0], 0); - - let funding_created = get_event_msg!(nodes[0], MessageSendEvent::SendFundingCreated, node_b_id); - nodes[1].node.handle_funding_created(node_a_id, &funding_created); - check_added_monitors(&nodes[1], 1); - expect_channel_pending_event(&nodes[1], &node_a_id); - - let funding_signed = get_event_msg!(nodes[1], MessageSendEvent::SendFundingSigned, node_a_id); - let err = msgs::ErrorMessage { channel_id: funding_signed.channel_id, data: "".to_string() }; - nodes[0].node.handle_error(node_b_id, &err); - - let close_events = nodes[0].node.get_and_clear_pending_events(); - assert_eq!(close_events.len(), 2); - assert!(close_events.iter().any(|ev| matches!(ev, Event::ChannelClosed { .. }))); - assert!(close_events.iter().any(|ev| match ev { - Event::DiscardFunding { channel_id, funding_info: FundingInfo::OutPoint { outpoint } } => { - assert_eq!(*channel_id, err.channel_id); - assert_eq!(*outpoint, funding_outpoint); - true - }, - _ => false, - })); -} - -#[xtest(feature = "_externalize_tests")] -pub fn test_funding_signed_event() { - let mut cfg = UserConfig::default(); - cfg.channel_handshake_config.minimum_depth = 1; - let chanmon_cfgs = create_chanmon_cfgs(2); - let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); - let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(cfg.clone()), Some(cfg)]); - let nodes = create_network(2, &node_cfgs, &node_chanmgrs); - - let node_a_id = nodes[0].node.get_our_node_id(); - let node_b_id = nodes[1].node.get_our_node_id(); - - assert!(nodes[0].node.create_channel(node_b_id, 100_000, 0, 42, None, None).is_ok()); - let open_channel = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id); - - nodes[1].node.handle_open_channel(node_a_id, &open_channel); - let accept_channel = get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, node_a_id); - - nodes[0].node.handle_accept_channel(node_b_id, &accept_channel); - let (temp_channel_id, tx, funding_outpoint) = - create_funding_transaction(&nodes[0], &node_b_id, 100_000, 42); - nodes[0] - .node - .unsafe_manual_funding_transaction_generated(temp_channel_id, node_b_id, funding_outpoint) - .unwrap(); - check_added_monitors(&nodes[0], 0); - - let funding_created = get_event_msg!(nodes[0], MessageSendEvent::SendFundingCreated, node_b_id); - nodes[1].node.handle_funding_created(node_a_id, &funding_created); - check_added_monitors(&nodes[1], 1); - expect_channel_pending_event(&nodes[1], &node_a_id); - - let funding_signed = get_event_msg!(nodes[1], MessageSendEvent::SendFundingSigned, node_a_id); - nodes[0].node.handle_funding_signed(node_b_id, &funding_signed); - check_added_monitors(&nodes[0], 1); - let events = &nodes[0].node.get_and_clear_pending_events(); - assert_eq!(events.len(), 2); - match &events[0] { - crate::events::Event::FundingTxBroadcastSafe { funding_txo, .. } => { - assert_eq!(funding_txo.txid, funding_outpoint.txid); - assert_eq!(funding_txo.vout, funding_outpoint.index.into()); - }, - _ => panic!("Unexpected event"), - }; - match &events[1] { - crate::events::Event::ChannelPending { counterparty_node_id, .. } => { - assert_eq!(node_b_id, *counterparty_node_id); - }, - _ => panic!("Unexpected event"), - }; - - mine_transaction(&nodes[0], &tx); - mine_transaction(&nodes[1], &tx); - - let as_channel_ready = get_event_msg!(nodes[1], MessageSendEvent::SendChannelReady, node_a_id); - nodes[1].node.handle_channel_ready(node_a_id, &as_channel_ready); - let as_channel_ready = get_event_msg!(nodes[0], MessageSendEvent::SendChannelReady, node_b_id); - nodes[0].node.handle_channel_ready(node_b_id, &as_channel_ready); - - expect_channel_ready_event(&nodes[0], &node_b_id); - expect_channel_ready_event(&nodes[1], &node_a_id); - nodes[0].node.get_and_clear_pending_msg_events(); - nodes[1].node.get_and_clear_pending_msg_events(); -} diff --git a/lightning/src/ln/funding.rs b/lightning/src/ln/funding.rs new file mode 100644 index 00000000000..e42c338d865 --- /dev/null +++ b/lightning/src/ln/funding.rs @@ -0,0 +1,208 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +//! Types pertaining to funding channels. + +#[cfg(splicing)] +use bitcoin::{Amount, ScriptBuf, SignedAmount, TxOut}; +use bitcoin::{Script, Sequence, Transaction, Weight}; + +use crate::events::bump_transaction::{Utxo, EMPTY_SCRIPT_SIG_WEIGHT}; +use crate::sign::{P2TR_KEY_PATH_WITNESS_WEIGHT, P2WPKH_WITNESS_WEIGHT}; + +/// The components of a splice's funding transaction that are contributed by one party. +#[cfg(splicing)] +pub enum SpliceContribution { + /// When funds are added to a channel. + SpliceIn { + /// The amount to contribute to the splice. + value: Amount, + + /// The inputs included in the splice's funding transaction to meet the contributed amount + /// plus fees. Any excess amount will be sent to a change output. + inputs: Vec, + + /// An optional change output script. This will be used if needed or, when not set, + /// generated using [`SignerProvider::get_destination_script`]. + change_script: Option, + }, + /// When funds are removed from a channel. + SpliceOut { + /// The outputs to include in the splice's funding transaction. The total value of all + /// outputs plus fees will be the amount that is removed. + outputs: Vec, + }, +} + +#[cfg(splicing)] +impl SpliceContribution { + pub(super) fn value(&self) -> SignedAmount { + match self { + SpliceContribution::SpliceIn { value, .. } => { + value.to_signed().unwrap_or(SignedAmount::MAX) + }, + SpliceContribution::SpliceOut { outputs } => { + let value_removed = outputs + .iter() + .map(|txout| txout.value) + .sum::() + .to_signed() + .unwrap_or(SignedAmount::MAX); + -value_removed + }, + } + } + + pub(super) fn inputs(&self) -> &[FundingTxInput] { + match self { + SpliceContribution::SpliceIn { inputs, .. } => &inputs[..], + SpliceContribution::SpliceOut { .. } => &[], + } + } + + pub(super) fn outputs(&self) -> &[TxOut] { + match self { + SpliceContribution::SpliceIn { .. } => &[], + SpliceContribution::SpliceOut { outputs } => &outputs[..], + } + } + + pub(super) fn into_tx_parts(self) -> (Vec, Vec, Option) { + match self { + SpliceContribution::SpliceIn { inputs, change_script, .. } => { + (inputs, vec![], change_script) + }, + SpliceContribution::SpliceOut { outputs } => (vec![], outputs, None), + } + } +} + +/// An input to contribute to a channel's funding transaction either when using the v2 channel +/// establishment protocol or when splicing. +#[derive(Clone)] +pub struct FundingTxInput { + /// The unspent [`TxOut`] that the input spends. + /// + /// [`TxOut`]: bitcoin::TxOut + pub(super) utxo: Utxo, + + /// The sequence number to use in the [`TxIn`]. + /// + /// [`TxIn`]: bitcoin::TxIn + pub(super) sequence: Sequence, + + /// The transaction containing the unspent [`TxOut`] referenced by [`utxo`]. + /// + /// [`TxOut`]: bitcoin::TxOut + /// [`utxo`]: Self::utxo + pub(super) prevtx: Transaction, +} + +impl FundingTxInput { + fn new bool>( + prevtx: Transaction, vout: u32, witness_weight: Weight, script_filter: F, + ) -> Result { + Ok(FundingTxInput { + utxo: Utxo { + outpoint: bitcoin::OutPoint { txid: prevtx.compute_txid(), vout }, + output: prevtx + .output + .get(vout as usize) + .filter(|output| script_filter(&output.script_pubkey)) + .ok_or(())? + .clone(), + satisfaction_weight: EMPTY_SCRIPT_SIG_WEIGHT + witness_weight.to_wu(), + }, + sequence: Sequence::ENABLE_RBF_NO_LOCKTIME, + prevtx, + }) + } + + /// Creates an input spending a P2WPKH output from the given `prevtx` at index `vout`. + /// + /// Uses [`Sequence::ENABLE_RBF_NO_LOCKTIME`] as the [`TxIn::sequence`], which can be overridden + /// by [`set_sequence`]. + /// + /// Returns `Err` if no such output exists in `prevtx` at index `vout`. + /// + /// [`TxIn::sequence`]: bitcoin::TxIn::sequence + /// [`set_sequence`]: Self::set_sequence + pub fn new_p2wpkh(prevtx: Transaction, vout: u32) -> Result { + let witness_weight = Weight::from_wu(P2WPKH_WITNESS_WEIGHT); + FundingTxInput::new(prevtx, vout, witness_weight, Script::is_p2wpkh) + } + + /// Creates an input spending a P2WSH output from the given `prevtx` at index `vout`. + /// + /// Requires passing the weight of witness needed to satisfy the output's script. + /// + /// Uses [`Sequence::ENABLE_RBF_NO_LOCKTIME`] as the [`TxIn::sequence`], which can be overridden + /// by [`set_sequence`]. + /// + /// Returns `Err` if no such output exists in `prevtx` at index `vout`. + /// + /// [`TxIn::sequence`]: bitcoin::TxIn::sequence + /// [`set_sequence`]: Self::set_sequence + pub fn new_p2wsh(prevtx: Transaction, vout: u32, witness_weight: Weight) -> Result { + FundingTxInput::new(prevtx, vout, witness_weight, Script::is_p2wsh) + } + + /// Creates an input spending a P2TR output from the given `prevtx` at index `vout`. + /// + /// This is meant for inputs spending a taproot output using the key path. See + /// [`new_p2tr_script_spend`] for when spending using a script path. + /// + /// Uses [`Sequence::ENABLE_RBF_NO_LOCKTIME`] as the [`TxIn::sequence`], which can be overridden + /// by [`set_sequence`]. + /// + /// Returns `Err` if no such output exists in `prevtx` at index `vout`. + /// + /// [`new_p2tr_script_spend`]: Self::new_p2tr_script_spend + /// + /// [`TxIn::sequence`]: bitcoin::TxIn::sequence + /// [`set_sequence`]: Self::set_sequence + pub fn new_p2tr_key_spend(prevtx: Transaction, vout: u32) -> Result { + let witness_weight = Weight::from_wu(P2TR_KEY_PATH_WITNESS_WEIGHT); + FundingTxInput::new(prevtx, vout, witness_weight, Script::is_p2tr) + } + + /// Creates an input spending a P2TR output from the given `prevtx` at index `vout`. + /// + /// Requires passing the weight of witness needed to satisfy a script path of the taproot + /// output. See [`new_p2tr_key_spend`] for when spending using the key path. + /// + /// Uses [`Sequence::ENABLE_RBF_NO_LOCKTIME`] as the [`TxIn::sequence`], which can be overridden + /// by [`set_sequence`]. + /// + /// Returns `Err` if no such output exists in `prevtx` at index `vout`. + /// + /// [`new_p2tr_key_spend`]: Self::new_p2tr_key_spend + /// + /// [`TxIn::sequence`]: bitcoin::TxIn::sequence + /// [`set_sequence`]: Self::set_sequence + pub fn new_p2tr_script_spend( + prevtx: Transaction, vout: u32, witness_weight: Weight, + ) -> Result { + FundingTxInput::new(prevtx, vout, witness_weight, Script::is_p2tr) + } + + /// The sequence number to use in the [`TxIn`]. + /// + /// [`TxIn`]: bitcoin::TxIn + pub fn sequence(&self) -> Sequence { + self.sequence + } + + /// Sets the sequence number to use in the [`TxIn`]. + /// + /// [`TxIn`]: bitcoin::TxIn + pub fn set_sequence(&mut self, sequence: Sequence) { + self.sequence = sequence; + } +} diff --git a/lightning/src/ln/inbound_payment.rs b/lightning/src/ln/inbound_payment.rs index a7d45b896a9..f7b2a4a2b57 100644 --- a/lightning/src/ln/inbound_payment.rs +++ b/lightning/src/ln/inbound_payment.rs @@ -213,7 +213,6 @@ pub fn create_from_hash( Ok(construct_payment_secret(&iv_bytes, &metadata_bytes, &keys.metadata_key)) } -#[cfg(async_payments)] pub(crate) fn create_for_spontaneous_payment( keys: &ExpandedKey, min_value_msat: Option, invoice_expiry_delta_secs: u32, current_time: u64, min_final_cltv_expiry_delta: Option, diff --git a/lightning/src/ln/interactivetxs.rs b/lightning/src/ln/interactivetxs.rs index 3fcf3f4ee01..216addbda80 100644 --- a/lightning/src/ln/interactivetxs.rs +++ b/lightning/src/ln/interactivetxs.rs @@ -11,23 +11,28 @@ use crate::io_extras::sink; use crate::prelude::*; use bitcoin::absolute::LockTime as AbsoluteLockTime; -use bitcoin::amount::Amount; +use bitcoin::amount::{Amount, SignedAmount}; use bitcoin::consensus::Encodable; use bitcoin::constants::WITNESS_SCALE_FACTOR; +use bitcoin::key::Secp256k1; use bitcoin::policy::MAX_STANDARD_TX_WEIGHT; -use bitcoin::secp256k1::PublicKey; +use bitcoin::secp256k1::{Message, PublicKey}; +use bitcoin::sighash::SighashCache; use bitcoin::transaction::Version; -use bitcoin::{OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Txid, Weight, Witness}; +use bitcoin::{ + sighash, EcdsaSighashType, OutPoint, ScriptBuf, Sequence, TapSighashType, Transaction, TxIn, + TxOut, Txid, Weight, Witness, XOnlyPublicKey, +}; use crate::chain::chaininterface::fee_for_weight; use crate::events::bump_transaction::{BASE_INPUT_WEIGHT, EMPTY_SCRIPT_SIG_WEIGHT}; use crate::ln::chan_utils::FUNDING_TRANSACTION_WITNESS_WEIGHT; use crate::ln::channel::{FundingNegotiationContext, TOTAL_BITCOIN_SUPPLY_SATOSHIS}; +use crate::ln::funding::FundingTxInput; use crate::ln::msgs; use crate::ln::msgs::{MessageSendEvent, SerialId, TxSignatures}; use crate::ln::types::ChannelId; use crate::sign::{EntropySource, P2TR_KEY_PATH_WITNESS_WEIGHT, P2WPKH_WITNESS_WEIGHT}; -use crate::util::ser::TransactionU16LenLimited; use core::fmt::Display; use core::ops::Deref; @@ -210,12 +215,24 @@ pub(crate) struct NegotiatedTxInput { txin: TxIn, // The weight of the input including an estimate of its witness weight. weight: Weight, + prev_output: TxOut, +} + +impl NegotiatedTxInput { + pub(super) fn is_local(&self, holder_is_initiator: bool) -> bool { + !is_serial_id_valid_for_counterparty(holder_is_initiator, self.serial_id) + } + + pub(super) fn prev_output(&self) -> &TxOut { + &self.prev_output + } } impl_writeable_tlv_based!(NegotiatedTxInput, { (1, serial_id, required), (3, txin, required), (5, weight, required), + (7, prev_output, required), }); impl_writeable_tlv_based!(ConstructedTransaction, { @@ -361,6 +378,10 @@ impl ConstructedTransaction { .zip(witnesses) .for_each(|(input, witness)| input.witness = witness); } + + pub fn holder_is_initiator(&self) -> bool { + self.holder_is_initiator + } } /// The InteractiveTxSigningSession coordinates the signing flow of interactively constructed @@ -375,6 +396,7 @@ pub(crate) struct InteractiveTxSigningSession { unsigned_tx: ConstructedTransaction, holder_sends_tx_signatures_first: bool, has_received_commitment_signed: bool, + has_received_tx_signatures: bool, holder_tx_signatures: Option, } @@ -395,13 +417,8 @@ impl InteractiveTxSigningSession { &self.holder_tx_signatures } - pub fn received_commitment_signed(&mut self) -> Option { + pub fn received_commitment_signed(&mut self) { self.has_received_commitment_signed = true; - if self.holder_sends_tx_signatures_first { - self.holder_tx_signatures.clone() - } else { - None - } } /// Handles a `tx_signatures` message received from the counterparty. @@ -414,14 +431,18 @@ impl InteractiveTxSigningSession { /// transaction will be finalized and returned as Some, otherwise None. /// /// Returns an error if the witness count does not equal the counterparty's input count in the - /// unsigned transaction. + /// unsigned transaction or if the counterparty already provided their `tx_signatures`. pub fn received_tx_signatures( &mut self, tx_signatures: TxSignatures, - ) -> Result<(Option, Option), ()> { + ) -> Result<(Option, Option), String> { + if self.has_received_tx_signatures { + return Err("Already received a tx_signatures message".to_string()); + } if self.remote_inputs_count() != tx_signatures.witnesses.len() { - return Err(()); + return Err("Witness count did not match contributed input count".to_string()); } self.unsigned_tx.add_remote_witnesses(tx_signatures.witnesses.clone()); + self.has_received_tx_signatures = true; let holder_tx_signatures = if !self.holder_sends_tx_signatures_first { self.holder_tx_signatures.clone() @@ -446,21 +467,37 @@ impl InteractiveTxSigningSession { /// Returns an error if the witness count does not equal the holder's input count in the /// unsigned transaction. pub fn provide_holder_witnesses( - &mut self, channel_id: ChannelId, witnesses: Vec, - ) -> Result<(), ()> { - if self.local_inputs_count() != witnesses.len() { - return Err(()); - } + &mut self, secp_ctx: &Secp256k1, channel_id: ChannelId, + witnesses: Vec, + ) -> Result<(Option, Option), String> { + let local_inputs_count = self.local_inputs_count(); + if local_inputs_count != witnesses.len() { + return Err(format!( + "Provided witness count of {} does not match required count for {} inputs", + witnesses.len(), + local_inputs_count + )); + } + if self.holder_tx_signatures.is_some() { + return Err("Holder witnesses were already provided".to_string()); + } + self.verify_interactive_tx_signatures(secp_ctx, &witnesses)?; self.unsigned_tx.add_local_witnesses(witnesses.clone()); self.holder_tx_signatures = Some(TxSignatures { channel_id, + witnesses, tx_hash: self.unsigned_tx.compute_txid(), - witnesses: witnesses.into_iter().collect(), shared_input_signature: None, }); - Ok(()) + let funding_tx_opt = self.has_received_tx_signatures.then(|| self.finalize_funding_tx()); + let holder_tx_signatures = + (self.holder_sends_tx_signatures_first || self.has_received_tx_signatures).then(|| { + debug_assert!(self.has_received_commitment_signed); + self.holder_tx_signatures.clone().expect("Holder tx_signatures were just provided") + }); + Ok((funding_tx_opt, holder_tx_signatures)) } pub fn remote_inputs_count(&self) -> usize { @@ -500,6 +537,161 @@ impl InteractiveTxSigningSession { output: outputs.iter().cloned().map(|output| output.into_tx_out()).collect(), } } + + pub fn verify_interactive_tx_signatures( + &self, secp_ctx: &Secp256k1, witnesses: &Vec, + ) -> Result<(), String> { + let unsigned_tx = self.unsigned_tx(); + let built_tx = unsigned_tx.build_unsigned_tx(); + let prev_outputs: Vec<&TxOut> = + unsigned_tx.inputs().map(|input| input.prev_output()).collect::>(); + let all_prevouts = sighash::Prevouts::All(&prev_outputs[..]); + + let mut cache = SighashCache::new(&built_tx); + + let script_pubkeys = unsigned_tx + .inputs() + .enumerate() + .filter(|(_, input)| input.is_local(unsigned_tx.holder_is_initiator())); + + for ((input_idx, input), witness) in script_pubkeys.zip(witnesses) { + if witness.is_empty() { + let err = format!("The witness for input at index {input_idx} is empty"); + return Err(err); + } + + let prev_output = input.prev_output(); + let script_pubkey = &prev_output.script_pubkey; + + // P2WPKH + if script_pubkey.is_p2wpkh() { + if witness.len() != 2 { + let err = format!("The witness for input at index {input_idx} does not have the correct number of elements for a P2WPKH spend. Expected 2 got {}", witness.len()); + return Err(err); + } + let pubkey = PublicKey::from_slice(&witness[1]).map_err(|_| { + format!("The witness for input at index {input_idx} contains an invalid ECDSA public key") + })?; + + let sig = + bitcoin::ecdsa::Signature::from_slice(&witness[0]).map_err(|_| { + format!("The witness for input at index {input_idx} contains an invalid signature") + })?; + if !matches!(sig.sighash_type, EcdsaSighashType::All) { + let err = format!("Signature does not use SIGHASH_ALL for input at index {input_idx} for P2WPKH spend"); + return Err(err); + } + + let sighash = cache + .p2wpkh_signature_hash( + input_idx, + script_pubkey, + prev_output.value, + EcdsaSighashType::All, + ) + .map_err(|_| { + debug_assert!(false, "Funding transaction sighash should be calculable"); + "The transaction sighash could not be calculated".to_string() + })?; + let msg = Message::from_digest_slice(&sighash[..]) + .expect("Sighash is a SHA256 which is 32 bytes long"); + secp_ctx.verify_ecdsa(&msg, &sig.signature, &pubkey).map_err(|_| { + format!("Failed signature verification for input at index {input_idx} for P2WPKH spend") + })?; + + continue; + } + + // P2TR key path spend witness includes signature and optional annex + if script_pubkey.is_p2tr() && witness.len() == 1 { + let pubkey = match script_pubkey.instructions().nth(1) { + Some(Ok(bitcoin::script::Instruction::PushBytes(push_bytes))) => { + XOnlyPublicKey::from_slice(push_bytes.as_bytes()) + }, + _ => { + let err = format!("The scriptPubKey of the previous output for input at index {input_idx} for a P2TR key path spend is invalid"); + return Err(err) + }, + }.map_err(|_| { + format!("The scriptPubKey of the previous output for input at index {input_idx} for a P2TR key path spend has an invalid public key") + })?; + + let sig = bitcoin::taproot::Signature::from_slice(&witness[0]).map_err(|_| { + format!("The witness for input at index {input_idx} for a P2TR key path spend has an invalid signature") + })?; + if !matches!(sig.sighash_type, TapSighashType::Default | TapSighashType::All) { + let err = format!("Signature does not use SIGHASH_DEFAULT or SIGHASH_ALL for input at index {input_idx} for P2TR key path spend"); + return Err(err); + } + + let sighash = cache + .taproot_key_spend_signature_hash(input_idx, &all_prevouts, sig.sighash_type) + .map_err(|_| { + debug_assert!(false, "Funding transaction sighash should be calculable"); + "The transaction sighash could not be calculated".to_string() + })?; + let msg = Message::from_digest_slice(&sighash[..]) + .expect("Sighash is a SHA256 which is 32 bytes long"); + secp_ctx.verify_schnorr(&sig.signature, &msg, &pubkey).map_err(|_| { + format!("Failed signature verification for input at index {input_idx} for P2TR key path spend") + })?; + + continue; + } + + // P2WSH - No validation just sighash checks + if script_pubkey.is_p2wsh() { + for element in witness { + match element.len() { + // Possibly a DER-encoded ECDSA signature with a sighash type byte assuming low-S + 70..=73 => { + if !bitcoin::ecdsa::Signature::from_slice(element) + .map(|sig| matches!(sig.sighash_type, EcdsaSighashType::All)) + .unwrap_or(true) + { + let err = format!("An ECDSA signature in the witness for input {input_idx} does not use SIGHASH_ALL"); + return Err(err); + } + }, + _ => (), + } + } + continue; + } + + // P2TR script path - No validation, just sighash checks + if script_pubkey.is_p2tr() { + for element in witness { + match element.len() { + // Schnorr sig + sighash type byte. + // If this were just 64 bytes, it would implicitly be SIGHASH_DEFAULT (= SIGHASH_ALL) + 65 => { + if !bitcoin::taproot::Signature::from_slice(element) + .map(|sig| matches!(sig.sighash_type, TapSighashType::All)) + .unwrap_or(true) + { + let err = format!("A (likely) Schnorr signature in the witness for input {input_idx} does not use SIGHASH_DEFAULT or SIGHASH_ALL"); + return Err(err); + } + }, + _ => (), + } + } + continue; + } + + debug_assert!( + false, + "We don't allow contributing inputs that are not spending P2WPKH, P2WSH, or P2TR" + ); + let err = format!( + "Input at index {input_idx} does not spend from one of P2WPKH, P2WSH, or P2TR" + ); + return Err(err); + } + + Ok(()) + } } impl_writeable_tlv_based!(InteractiveTxSigningSession, { @@ -507,6 +699,7 @@ impl_writeable_tlv_based!(InteractiveTxSigningSession, { (3, holder_sends_tx_signatures_first, required), (5, has_received_commitment_signed, required), (7, holder_tx_signatures, required), + (9, has_received_tx_signatures, required), }); #[derive(Debug)] @@ -676,10 +869,9 @@ impl NegotiationContext { return Err(AbortReason::UnexpectedFundingInput); } } else if let Some(prevtx) = &msg.prevtx { - let transaction = prevtx.as_transaction(); - let txid = transaction.compute_txid(); + let txid = prevtx.compute_txid(); - if let Some(tx_out) = transaction.output.get(msg.prevtx_out as usize) { + if let Some(tx_out) = prevtx.output.get(msg.prevtx_out as usize) { if !tx_out.script_pubkey.is_witness_program() { // The receiving node: // - MUST fail the negotiation if: @@ -860,14 +1052,9 @@ impl NegotiationContext { return Err(AbortReason::UnexpectedFundingInput); } } else if let Some(prevtx) = &msg.prevtx { - let prev_txid = prevtx.as_transaction().compute_txid(); + let prev_txid = prevtx.compute_txid(); let prev_outpoint = OutPoint { txid: prev_txid, vout: msg.prevtx_out }; - let prev_output = prevtx - .as_transaction() - .output - .get(vout) - .ok_or(AbortReason::PrevTxOutInvalid)? - .clone(); + let prev_output = prevtx.output.get(vout).ok_or(AbortReason::PrevTxOutInvalid)?.clone(); let txin = TxIn { previous_output: prev_outpoint, sequence: Sequence(msg.sequence), @@ -1090,6 +1277,7 @@ macro_rules! define_state_transitions { holder_sends_tx_signatures_first: tx.holder_sends_tx_signatures_first, unsigned_tx: tx, has_received_commitment_signed: false, + has_received_tx_signatures: false, holder_tx_signatures: None, }; Ok(NegotiationComplete(signing_session)) @@ -1247,7 +1435,7 @@ impl_writeable_tlv_based_enum!(AddingRole, #[derive(Clone, Debug, Eq, PartialEq)] struct SingleOwnedInput { input: TxIn, - prev_tx: TransactionU16LenLimited, + prev_tx: Transaction, prev_output: TxOut, } @@ -1302,13 +1490,6 @@ impl InputOwned { } } - pub fn into_tx_in(self) -> TxIn { - match self { - InputOwned::Single(single) => single.input, - InputOwned::Shared(shared) => shared.input, - } - } - pub fn value(&self) -> u64 { match self { InputOwned::Single(single) => single.prev_output.value.to_sat(), @@ -1349,6 +1530,13 @@ impl InputOwned { InputOwned::Shared(shared) => estimate_input_weight(&shared.prev_output), } } + + fn into_tx_in_with_prev_output(self) -> (TxIn, TxOut) { + match self { + InputOwned::Single(single) => (single.input, single.prev_output), + InputOwned::Shared(shared) => (shared.input, shared.prev_output), + } + } } #[derive(Clone, Debug, Eq, PartialEq)] @@ -1498,10 +1686,6 @@ impl InteractiveTxInput { self.input.tx_in_mut() } - pub fn into_txin(self) -> TxIn { - self.input.into_tx_in() - } - pub fn value(&self) -> u64 { self.input.value() } @@ -1520,7 +1704,8 @@ impl InteractiveTxInput { fn into_negotiated_input(self) -> NegotiatedTxInput { let weight = self.input.estimate_input_weight(); - NegotiatedTxInput { serial_id: self.serial_id, txin: self.input.into_tx_in(), weight } + let (txin, prev_output) = self.input.into_tx_in_with_prev_output(); + NegotiatedTxInput { serial_id: self.serial_id, txin, weight, prev_output } } } @@ -1652,7 +1837,7 @@ where pub feerate_sat_per_kw: u32, pub is_initiator: bool, pub funding_tx_locktime: AbsoluteLockTime, - pub inputs_to_contribute: Vec<(TxIn, TransactionU16LenLimited)>, + pub inputs_to_contribute: Vec<(TxIn, Transaction)>, pub shared_funding_input: Option, pub shared_funding_output: SharedOwnedOutput, pub outputs_to_contribute: Vec, @@ -1694,7 +1879,7 @@ impl InteractiveTxConstructor { // Check for the existence of prevouts' for (txin, tx) in inputs_to_contribute.iter() { let vout = txin.previous_output.vout as usize; - if tx.as_transaction().output.get(vout).is_none() { + if tx.output.get(vout).is_none() { return Err(AbortReason::PrevTxOutInvalid); } } @@ -1703,7 +1888,7 @@ impl InteractiveTxConstructor { .map(|(txin, tx)| { let serial_id = generate_holder_serial_id(entropy_source, is_initiator); let vout = txin.previous_output.vout as usize; - let prev_output = tx.as_transaction().output.get(vout).unwrap().clone(); // checked above + let prev_output = tx.output.get(vout).unwrap().clone(); // checked above let input = InputOwned::Single(SingleOwnedInput { input: txin, prev_tx: tx, prev_output }); (serial_id, input) @@ -1884,28 +2069,21 @@ impl InteractiveTxConstructor { /// - `change_output_dust_limit` - The dust limit (in sats) to consider. pub(super) fn calculate_change_output_value( context: &FundingNegotiationContext, is_splice: bool, shared_output_funding_script: &ScriptBuf, - funding_outputs: &Vec, change_output_dust_limit: u64, + change_output_dust_limit: u64, ) -> Result, AbortReason> { - assert!(context.our_funding_contribution_satoshis > 0); - let our_funding_contribution_satoshis = context.our_funding_contribution_satoshis as u64; + assert!(context.our_funding_contribution > SignedAmount::ZERO); + let our_funding_contribution_satoshis = context.our_funding_contribution.to_sat() as u64; let mut total_input_satoshis = 0u64; let mut our_funding_inputs_weight = 0u64; - for (txin, tx) in context.our_funding_inputs.iter() { - let txid = tx.as_transaction().compute_txid(); - if txin.previous_output.txid != txid { - return Err(AbortReason::PrevTxOutInvalid); - } - let output = tx - .as_transaction() - .output - .get(txin.previous_output.vout as usize) - .ok_or(AbortReason::PrevTxOutInvalid)?; - total_input_satoshis = total_input_satoshis.saturating_add(output.value.to_sat()); - let weight = estimate_input_weight(output).to_wu(); + for FundingTxInput { utxo, .. } in context.our_funding_inputs.iter() { + total_input_satoshis = total_input_satoshis.saturating_add(utxo.output.value.to_sat()); + + let weight = BASE_INPUT_WEIGHT + utxo.satisfaction_weight; our_funding_inputs_weight = our_funding_inputs_weight.saturating_add(weight); } + let funding_outputs = &context.our_funding_outputs; let total_output_satoshis = funding_outputs.iter().fold(0u64, |total, out| total.saturating_add(out.value.to_sat())); let our_funding_outputs_weight = funding_outputs.iter().fold(0u64, |weight, out| { @@ -1945,6 +2123,7 @@ pub(super) fn calculate_change_output_value( mod tests { use crate::chain::chaininterface::{fee_for_weight, FEERATE_FLOOR_SATS_PER_KW}; use crate::ln::channel::{FundingNegotiationContext, TOTAL_BITCOIN_SUPPLY_SATOSHIS}; + use crate::ln::funding::FundingTxInput; use crate::ln::interactivetxs::{ calculate_change_output_value, generate_holder_serial_id, AbortReason, HandleTxCompleteValue, InteractiveTxConstructor, InteractiveTxConstructorArgs, @@ -1954,23 +2133,25 @@ mod tests { use crate::ln::types::ChannelId; use crate::sign::EntropySource; use crate::util::atomic_counter::AtomicCounter; - use crate::util::ser::TransactionU16LenLimited; use bitcoin::absolute::LockTime as AbsoluteLockTime; use bitcoin::amount::Amount; use bitcoin::hashes::Hash; - use bitcoin::key::UntweakedPublicKey; - use bitcoin::opcodes; + use bitcoin::hex::FromHex; + use bitcoin::key::{TweakedPublicKey, UntweakedPublicKey}; use bitcoin::script::Builder; use bitcoin::secp256k1::{Keypair, PublicKey, Secp256k1, SecretKey}; use bitcoin::transaction::Version; + use bitcoin::{opcodes, WScriptHash, Weight, XOnlyPublicKey}; use bitcoin::{ - OutPoint, PubkeyHash, ScriptBuf, Sequence, Transaction, TxIn, TxOut, WPubkeyHash, Witness, + OutPoint, PubkeyHash, ScriptBuf, Sequence, SignedAmount, Transaction, TxIn, TxOut, + WPubkeyHash, }; use core::ops::Deref; use super::{ - get_output_weight, P2TR_INPUT_WEIGHT_LOWER_BOUND, P2WPKH_INPUT_WEIGHT_LOWER_BOUND, - P2WSH_INPUT_WEIGHT_LOWER_BOUND, TX_COMMON_FIELDS_WEIGHT, + get_output_weight, AddingRole, ConstructedTransaction, InteractiveTxOutput, + InteractiveTxSigningSession, NegotiatedTxInput, OutputOwned, P2TR_INPUT_WEIGHT_LOWER_BOUND, + P2WPKH_INPUT_WEIGHT_LOWER_BOUND, P2WSH_INPUT_WEIGHT_LOWER_BOUND, TX_COMMON_FIELDS_WEIGHT, }; const TEST_FEERATE_SATS_PER_KW: u32 = FEERATE_FLOOR_SATS_PER_KW * 10; @@ -2017,12 +2198,12 @@ mod tests { struct TestSession { description: &'static str, - inputs_a: Vec<(TxIn, TransactionU16LenLimited)>, + inputs_a: Vec<(TxIn, Transaction)>, a_shared_input: Option<(OutPoint, TxOut, u64)>, /// The funding output, with the value contributed shared_output_a: (TxOut, u64), outputs_a: Vec, - inputs_b: Vec<(TxIn, TransactionU16LenLimited)>, + inputs_b: Vec<(TxIn, Transaction)>, b_shared_input: Option<(OutPoint, TxOut, u64)>, /// The funding output, with the value contributed shared_output_b: (TxOut, u64), @@ -2288,7 +2469,7 @@ mod tests { } } - fn generate_inputs(outputs: &[TestOutput]) -> Vec<(TxIn, TransactionU16LenLimited)> { + fn generate_inputs(outputs: &[TestOutput]) -> Vec<(TxIn, Transaction)> { let tx = generate_tx(outputs); let txid = tx.compute_txid(); tx.output @@ -2301,7 +2482,7 @@ mod tests { sequence: Sequence::ENABLE_RBF_NO_LOCKTIME, witness: Default::default(), }; - (txin, TransactionU16LenLimited::new(tx.clone()).unwrap()) + (txin, tx.clone()) }) .collect() } @@ -2349,12 +2530,12 @@ mod tests { (generate_txout(&TestOutput::P2WSH(value)), local_value) } - fn generate_fixed_number_of_inputs(count: u16) -> Vec<(TxIn, TransactionU16LenLimited)> { + fn generate_fixed_number_of_inputs(count: u16) -> Vec<(TxIn, Transaction)> { // Generate transactions with a total `count` number of outputs such that no transaction has a // serialized length greater than u16::MAX. let max_outputs_per_prevtx = 1_500; let mut remaining = count; - let mut inputs: Vec<(TxIn, TransactionU16LenLimited)> = Vec::with_capacity(count as usize); + let mut inputs: Vec<(TxIn, Transaction)> = Vec::with_capacity(count as usize); while remaining > 0 { let tx_output_count = remaining.min(max_outputs_per_prevtx); @@ -2367,7 +2548,7 @@ mod tests { ); let txid = tx.compute_txid(); - let mut temp: Vec<(TxIn, TransactionU16LenLimited)> = tx + let mut temp: Vec<(TxIn, Transaction)> = tx .output .iter() .enumerate() @@ -2378,7 +2559,7 @@ mod tests { sequence: Sequence::ENABLE_RBF_NO_LOCKTIME, witness: Default::default(), }; - (input, TransactionU16LenLimited::new(tx.clone()).unwrap()) + (input, tx.clone()) }) .collect(); @@ -2589,10 +2770,9 @@ mod tests { expect_error: Some((AbortReason::PrevTxOutInvalid, ErrorCulprit::NodeA)), }); - let tx = - TransactionU16LenLimited::new(generate_tx(&[TestOutput::P2WPKH(1_000_000)])).unwrap(); + let tx = generate_tx(&[TestOutput::P2WPKH(1_000_000)]); let invalid_sequence_input = TxIn { - previous_output: OutPoint { txid: tx.as_transaction().compute_txid(), vout: 0 }, + previous_output: OutPoint { txid: tx.compute_txid(), vout: 0 }, ..Default::default() }; do_test_interactive_tx_constructor(TestSession { @@ -2608,7 +2788,7 @@ mod tests { expect_error: Some((AbortReason::IncorrectInputSequenceValue, ErrorCulprit::NodeA)), }); let duplicate_input = TxIn { - previous_output: OutPoint { txid: tx.as_transaction().compute_txid(), vout: 0 }, + previous_output: OutPoint { txid: tx.compute_txid(), vout: 0 }, sequence: Sequence::ENABLE_RBF_NO_LOCKTIME, ..Default::default() }; @@ -2626,7 +2806,7 @@ mod tests { }); // Non-initiator uses same prevout as initiator. let duplicate_input = TxIn { - previous_output: OutPoint { txid: tx.as_transaction().compute_txid(), vout: 0 }, + previous_output: OutPoint { txid: tx.compute_txid(), vout: 0 }, sequence: Sequence::ENABLE_RBF_NO_LOCKTIME, ..Default::default() }; @@ -2643,7 +2823,7 @@ mod tests { expect_error: Some((AbortReason::PrevTxOutInvalid, ErrorCulprit::NodeA)), }); let duplicate_input = TxIn { - previous_output: OutPoint { txid: tx.as_transaction().compute_txid(), vout: 0 }, + previous_output: OutPoint { txid: tx.compute_txid(), vout: 0 }, sequence: Sequence::ENABLE_RBF_NO_LOCKTIME, ..Default::default() }; @@ -2957,28 +3137,28 @@ mod tests { #[test] fn test_calculate_change_output_value_open() { let input_prevouts = [ - TxOut { value: Amount::from_sat(70_000), script_pubkey: ScriptBuf::new() }, - TxOut { value: Amount::from_sat(60_000), script_pubkey: ScriptBuf::new() }, + TxOut { + value: Amount::from_sat(70_000), + script_pubkey: ScriptBuf::new_p2wpkh(&WPubkeyHash::all_zeros()), + }, + TxOut { + value: Amount::from_sat(60_000), + script_pubkey: ScriptBuf::new_p2wpkh(&WPubkeyHash::all_zeros()), + }, ]; let inputs = input_prevouts .iter() .map(|txout| { - let tx = Transaction { + let prevtx = Transaction { input: Vec::new(), output: vec![(*txout).clone()], lock_time: AbsoluteLockTime::ZERO, version: Version::TWO, }; - let txid = tx.compute_txid(); - let txin = TxIn { - previous_output: OutPoint { txid, vout: 0 }, - script_sig: ScriptBuf::new(), - sequence: Sequence::ZERO, - witness: Witness::new(), - }; - (txin, TransactionU16LenLimited::new(tx).unwrap()) + + FundingTxInput::new_p2wpkh(prevtx, 0).unwrap() }) - .collect::>(); + .collect(); let our_contributed = 110_000; let txout = TxOut { value: Amount::from_sat(10_000), script_pubkey: ScriptBuf::new() }; let outputs = vec![txout]; @@ -2993,69 +3173,395 @@ mod tests { // There is leftover for change let context = FundingNegotiationContext { is_initiator: true, - our_funding_contribution_satoshis: our_contributed as i64, - their_funding_contribution_satoshis: None, + our_funding_contribution: SignedAmount::from_sat(our_contributed as i64), funding_tx_locktime: AbsoluteLockTime::ZERO, funding_feerate_sat_per_1000_weight, shared_funding_input: None, our_funding_inputs: inputs, + our_funding_outputs: outputs, change_script: None, }; assert_eq!( - calculate_change_output_value(&context, false, &ScriptBuf::new(), &outputs, 300), + calculate_change_output_value(&context, false, &ScriptBuf::new(), 300), Ok(Some(gross_change - fees - common_fees)), ); // There is leftover for change, without common fees let context = FundingNegotiationContext { is_initiator: false, ..context }; assert_eq!( - calculate_change_output_value(&context, false, &ScriptBuf::new(), &outputs, 300), + calculate_change_output_value(&context, false, &ScriptBuf::new(), 300), Ok(Some(gross_change - fees)), ); // Insufficient inputs, no leftover let context = FundingNegotiationContext { is_initiator: false, - our_funding_contribution_satoshis: 130_000, + our_funding_contribution: SignedAmount::from_sat(130_000), ..context }; assert_eq!( - calculate_change_output_value(&context, false, &ScriptBuf::new(), &outputs, 300), + calculate_change_output_value(&context, false, &ScriptBuf::new(), 300), Err(AbortReason::InsufficientFees), ); // Very small leftover let context = FundingNegotiationContext { is_initiator: false, - our_funding_contribution_satoshis: 118_000, + our_funding_contribution: SignedAmount::from_sat(118_000), ..context }; assert_eq!( - calculate_change_output_value(&context, false, &ScriptBuf::new(), &outputs, 300), + calculate_change_output_value(&context, false, &ScriptBuf::new(), 300), Ok(None), ); // Small leftover, but not dust let context = FundingNegotiationContext { is_initiator: false, - our_funding_contribution_satoshis: 117_992, + our_funding_contribution: SignedAmount::from_sat(117_992), ..context }; assert_eq!( - calculate_change_output_value(&context, false, &ScriptBuf::new(), &outputs, 100), + calculate_change_output_value(&context, false, &ScriptBuf::new(), 100), Ok(Some(262)), ); // Larger fee, smaller change let context = FundingNegotiationContext { is_initiator: true, - our_funding_contribution_satoshis: our_contributed as i64, + our_funding_contribution: SignedAmount::from_sat(our_contributed as i64), funding_feerate_sat_per_1000_weight: funding_feerate_sat_per_1000_weight * 3, ..context }; assert_eq!( - calculate_change_output_value(&context, false, &ScriptBuf::new(), &outputs, 300), + calculate_change_output_value(&context, false, &ScriptBuf::new(), 300), Ok(Some(4060)), ); } + + fn do_verify_tx_signatures( + transaction: Transaction, prev_outputs: Vec, + ) -> Result<(), String> { + let inputs: Vec = transaction + .input + .iter() + .cloned() + .zip(prev_outputs.into_iter()) + .enumerate() + .map(|(idx, (txin, prev_output))| { + NegotiatedTxInput { + serial_id: idx as u64, // even values will be holder (initiator in this test) + txin, + weight: Weight::from_wu(0), // N/A for test + prev_output, + } + }) + .collect(); + + let outputs: Vec = transaction + .output + .iter() + .cloned() + .map(|txout| InteractiveTxOutput { + serial_id: 0, // N/A for test + added_by: AddingRole::Local, + output: OutputOwned::Single(txout), + }) + .collect(); + + let unsigned_tx = ConstructedTransaction { + holder_is_initiator: true, + inputs, + outputs, + local_inputs_value_satoshis: 0, // N/A for test + local_outputs_value_satoshis: 0, // N/A for test + remote_inputs_value_satoshis: 0, // N/A for test + remote_outputs_value_satoshis: 0, // N/A for test + lock_time: transaction.lock_time, + holder_sends_tx_signatures_first: false, + }; + + let secp_ctx = Secp256k1::new(); + + InteractiveTxSigningSession { + unsigned_tx, + holder_sends_tx_signatures_first: false, // N/A for test + has_received_commitment_signed: false, // N/A for test + has_received_tx_signatures: false, // N/A for test + holder_tx_signatures: None, + } + .verify_interactive_tx_signatures( + &secp_ctx, + &transaction + .input + .into_iter() + .enumerate() + .filter(|(idx, _)| idx % 2 == 0) // we only want initiator inputs (corresponds to even serial_id) + .map(|(_, txin)| txin.witness) + .collect(), + ) + } + + #[test] + fn test_verify_tx_signatures_p2tr_key_path_p2wsh_no_sig() { + // Uses transaction https://mempool.space/tx/c28d01b47b8426039306e4209534fc5235da4a31406179639c54c48212be7655 + let transaction: Transaction = bitcoin::consensus::encode::deserialize_hex("02000000000105d08ef8a4eac88a9568d660732d6e1bd8f216fecb46b7ebc7fc7b5a85e3ba1da50000000000ffffffff3ae09cc085873112f0602cac61e005827e7f21ce03595c6bf1e5ab41643e2e240000000000ffffffff030d20d2b28c4f27797e90ab2259392e99070307f0ee14a621025f8adc9054720000000000100000007d2e78b06110de8ac2298e71fa6fd96e24a287597f3a3fbfaa60837e40453a990000000000100000007d2e78b06110de8ac2298e71fa6fd96e24a287597f3a3fbfaa60837e40453a990100000000100000000104310d01000000002251207434164bd41e2185651f084b6a79e11ce57abe69093b7f939bb1c8786e5d233b0140e612c3728bcc6ed6c4ef67238e57f0332fa77a4c2e76db183e28b7f3cea5eab6b235b6f0cbab8035fd79b3c1990c5c3f3a56e2c7d5e4609b390ddaad8ac1c1d7024730440220036e88464b21c8bd819d97ae746622da00053ec1374a932f33aa1ab60170c9da022041cabc146ebdd12f6316a2f72f870771e8e6ff51f3cadad4027eab2e443770110121030c7196376bc1df61b6da6ee711868fd30e370dd273332bfb02a2287d11e2e9c50200282102fd481d39bdbc090313b530fddfd1aa004a9e3263da1406cf806670fdeb8ebb91ac736460b2680200282102092f44ee333630b985e490dbbc69865e499853cba15a51426d0f4e5906087e55ac736460b26802002821021dadb5ffb2cb74f5427f039e2913738e5cd8e93cc0d12db4cfa4f555005c326aac736460b26800000000").unwrap(); + let prev_outputs = + vec![ + // Added by holder + TxOut { + value: Amount::from_sat(17414236), + script_pubkey: ScriptBuf::new_p2tr_tweaked( + TweakedPublicKey::dangerous_assume_tweaked(XOnlyPublicKey::from_slice( + &<[u8; 32]>::from_hex( + "7434164bd41e2185651f084b6a79e11ce57abe69093b7f939bb1c8786e5d233b", + ) + .unwrap(), + ).unwrap()), + ), + }, + // Added by remote (corresponding input should not be checked) + TxOut { + value: Amount::from_sat(227321), + script_pubkey: ScriptBuf::new_p2wpkh(&WPubkeyHash::from_byte_array( + <[u8; 20]>::from_hex("92b8c3a56fac121ddcdffbc85b02fb9ef681038a").unwrap(), + )), + }, + // Added by holder + TxOut { + value: Amount::from_sat(330), + script_pubkey: ScriptBuf::new_p2wsh(&WScriptHash::from_byte_array( + <[u8; 32]>::from_hex( + "97a4f4b73947411e18486b7182063f160f9b3a238664b91ff70a56eaffca8b9d", + ) + .unwrap(), + )), + }, + // Added by remote (corresponding input should not be checked) + TxOut { + value: Amount::from_sat(330), + script_pubkey: ScriptBuf::new_p2wsh(&WScriptHash::from_byte_array( + <[u8; 32]>::from_hex( + "0d0f49839e6bbf78271ea31d979895758ed66312b4fbab215da8a68a951f36ee", + ) + .unwrap(), + )), + }, + // Added by holder + TxOut { + value: Amount::from_sat(330), + script_pubkey: ScriptBuf::new_p2wsh(&WScriptHash::from_byte_array( + <[u8; 32]>::from_hex( + "f2c42991382f63a20308c35ce67133cd8564ede8f8615062d814ec69112ddd46", + ) + .unwrap(), + )), + } + ]; + + assert!(do_verify_tx_signatures(transaction, prev_outputs).is_ok()); + } + + #[test] + fn test_verify_tx_signatures_p2wpkh_anyonecanpay_should_fail() { + // Using on-chain transaction: https://mempool.space/tx/fe62d242fbdd57a3bdb0d158b80e3c77754f17653eb23e3b64203076e6966cae + let transaction: Transaction = bitcoin::consensus::encode::deserialize_hex("020000000001010889a9a8424c16e069d0690b10a035f166ecb0788434703776b8ccf3209cb6c00000000000fdffffff052302000000000000160014bd42e2a4f83e5d905bccf4dcff7bb88e514749054a01000000000000220020003d7374616d703a7b2270223a227372632d3230222c226f70223a227472616e4a0100000000000022002073666572222c227469636b223a2249524f4e42222c22616d74223a3130307d0064530300000000002251202838c8f586f4dcdb5fb080a9c28497287e46cab65c8dcf9de27e659afe2564a61423000000000000160014cc054f448ca15a5aa1b21f2adb6607fec4410b6d02483045022100d84f8fb0f82c22128ba75b54e6c1be27aeee967acfe0a6e624a47acdf20cf3c102200248271599dba21f24ab8593529ca95a2f27aebd953b12c5f8aff3809c9743998121025ede2bca4b5a86da349fb8827eec4bb95afb513bb8c260867bbd55e7d0a2f48d00000000").unwrap(); + + let prev_outputs = vec![ + // Added by holder + TxOut { + value: Amount::from_sat(228980), + script_pubkey: ScriptBuf::new_p2wpkh(&WPubkeyHash::from_byte_array( + <[u8; 20]>::from_hex("cc054f448ca15a5aa1b21f2adb6607fec4410b6d").unwrap(), + )), + }, + ]; + + match do_verify_tx_signatures(transaction, prev_outputs) { + Ok(_) => panic!("Should not be valid"), + Err(err) => { + assert_eq!( + &err, + "Signature does not use SIGHASH_ALL for input at index 0 for P2WPKH spend" + ); + }, + } + } + + #[test] + fn test_verify_tx_signatures_p2wsh_with_anyonecanpay_should_fail() { + // Using on-chain transaction: https://mempool.space/tx/c28d01b47b8426039306e4209534fc5235da4a31406179639c54c48212be7655 + let transaction: Transaction = bitcoin::consensus::encode::deserialize_hex("0200000000010163c80d9fe4cfd02c6e0521a3818ecc1593573c85f0026dd0a57f16c61101d2a10000000000fdffffff02838ef72e0000000022002054313a8b88c0b1f408f8e4ba2a7c71909ebb35ec3e5cc81518c5a797afb48e9d00000000000000000a6a0853594d423a62643304473044022039c1263f05745d0a1c3c7afe40cbaf39a0445f66985c700b5bb7161ac8eece54022057a20fe506aadc254efd4686dd15037be22be965f758ab83498c86893bf8f4a68101010103d55388632103024f3166b9833e75cb2d0695b221e7a86170b9900d43aaa9d62172c51f796fe1ac675321030d8e88d0f843d2671f0762cd8010cb6e96ddf3d1558f593d607f7f261b1b031b210388b5390d3d2a24762d0680474dd26149ab1ae050e18b01ec831cbf0a5914537721023a0ddacf091d5d9430467be66f4a0ecb6ced6bb255ae89b626b9fa74966d42ea21023a0363a3f5afcf71ae05c84f09edb48c2101625a08abc3b8467854cc100187f521029e7fdb5297ff32dd34c52b99aeb09ca64015c787f5e0958c68c25eb8c5de265955ae6800000000").unwrap(); + + let prev_outputs = vec![ + // Added by holder + TxOut { + value: Amount::from_sat(787976283), + script_pubkey: ScriptBuf::new_p2wsh(&WScriptHash::from_byte_array( + <[u8; 32]>::from_hex( + "54313a8b88c0b1f408f8e4ba2a7c71909ebb35ec3e5cc81518c5a797afb48e9d", + ) + .unwrap(), + )), + }, + ]; + + match do_verify_tx_signatures(transaction, prev_outputs) { + Ok(_) => panic!("Should not be valid"), + Err(err) => { + assert_eq!( + &err, + "An ECDSA signature in the witness for input 0 does not use SIGHASH_ALL" + ); + }, + } + } + + #[test] + fn test_verify_tx_signatures_p2tr_key_path_anyonecanpay_should_fail() { + // Using on-chain transaction: https://mempool.space/tx/f7636876156f3a8a48a6cddb150e07363c1641495f4b319faab1e8c4527e58db + let transaction: Transaction = bitcoin::consensus::encode::deserialize_hex("02000000000102977aba41d493f93acc890e49c292dad6cbe423cb1356c6e6191cb93eed3f60c20200000000ffffffff1d956f8838a87c551c308f49fe80da594bfb888209ae8159bc77c6f471dd3b540000000000ffffffff022202000000000000225120fcb2498c6a6a335951f4c96fc89266c388e1ef4c416a2c6fca438a2f5cbb7ffe26d0030000000000225120cbc74f986822b48c4801ef5a1cadc44b27f7d23e699d8244c391d5defd69802a0141b7b9685f6b790e24392670fa06b9af34331bd3308a58b4d8b2cd86a4bcea19a2a780565b410062b58fbff026ab74513f0bac00711eba9f80e3d6b2a7cf3887a1810140a5ae4d75b89e54cfe470eb152e527a403e30b2fb3fdf5dcad1019f015827a1871431dd7202cb520ddcd3b0205cc2b9aafcb6b52522562d381d05cac4522f258100000000").unwrap(); + + let prev_outputs = + vec![ + // Added by holder (SIGHASH_ALL | ACP) + TxOut { + value: Amount::from_sat(546), + script_pubkey: ScriptBuf::new_p2tr_tweaked( + TweakedPublicKey::dangerous_assume_tweaked(XOnlyPublicKey::from_slice( + &<[u8; 32]>::from_hex( + "cbc74f986822b48c4801ef5a1cadc44b27f7d23e699d8244c391d5defd69802a", + ) + .unwrap(), + ).unwrap()), + ), + }, + // Added by remote (corresponding input should not be checked) + TxOut { + value: Amount::from_sat(250148), + script_pubkey: ScriptBuf::new_p2tr_tweaked( + TweakedPublicKey::dangerous_assume_tweaked(XOnlyPublicKey::from_slice( + &<[u8; 32]>::from_hex( + "56cee5ccf725d94a428100de365fdfa134ff4deb1a0dca14470e70b4a64ff32b", + ) + .unwrap(), + ).unwrap()), + ), + }, + ]; + + match do_verify_tx_signatures(transaction, prev_outputs) { + Ok(_) => panic!("Should not be valid"), + Err(err) => { + assert_eq!( + &err, + "Signature does not use SIGHASH_DEFAULT or SIGHASH_ALL for input at index 0 for P2TR key path spend" + ); + }, + } + } + + #[test] + fn test_verify_tx_signatures_p2wsh_multisig() { + // Using on-chain transaction: https://mempool.space/tx/c28d01b47b8426039306e4209534fc5235da4a31406179639c54c48212be7655 + let transaction: Transaction = bitcoin::consensus::encode::deserialize_hex("02000000000101d457eb6d1d7b9d0921d24449aede5f45a1e14e8f90aefd12f429e80741c0410d0100000000fdffffff02242c00000000000022002033476d89781e0006ce6a15f0b916cd5d53cf1a0f34d9d44273821148f8299db550340300000000001600146283a887af0a60239b5e18c5409a60cdf0404b8f0400473044022045fa871b357509376288e1933c010c988a55c370182a3d82cf4541a4850ee28c02203f22903165cccc06a124d5058c07c097fca3faf6048b62a9770a2c2eb23078810147304402202bd94f4be066f81ec8dffdef137c8ea99fdd6dcb8f69ee6ce95f8b4b237566f20220080e392372323b383efde7e013f570545103cdb04bf641587350d11cb4c4585b01695221030e17c0365f9f933b5fe08711069fb0e83af497eff9aa69488e13d04698e12c95210383b4b6d2e49bc7f3d211393193ae4a8b0d34d076632764950feb0f11451dcad22103de6d5d777364d5a7b92cf1ae3f45d2f71f2ec88289938e267f2cbc8a88eff33253aeb4d60d00").unwrap(); + + let prev_outputs = vec![ + // Added by holder + TxOut { + value: Amount::from_sat(221691), + script_pubkey: ScriptBuf::new_p2wsh(&WScriptHash::from_byte_array( + <[u8; 32]>::from_hex( + "dca8b773bb8a3beb76dff2c2998642449ec989d158ce049ec94a1af29b69b008", + ) + .unwrap(), + )), + }, + ]; + + assert!(do_verify_tx_signatures(transaction, prev_outputs).is_ok()); + } + + #[test] + fn test_verify_tx_signatures_p2wpkh() { + // Using on-chain transaction: https://mempool.space/tx/3db96e0b60bb823c55e35560521ec4bb05962ac109400f1e5c56b8fe642958e6 + let transaction: Transaction = bitcoin::consensus::encode::deserialize_hex("02000000000102939dd4cba8ca232c39647e7366c4f1a05ad0102a563b1df4f3befc351ca8c65d0000000000ffffffff58f776fcc31c6b9bfbd5839ea7a6b5da1bfc9bcc9e948f3b4be0792483b7d0e10100000000ffffffff029ad30000000000001600140b29de1e14f8ebc26b65d307b66d521ceb8d40b0e97d01000000000017a9149be05916325c1333820ebc00c80d6bf5c60a52b48702483045022100867147fa982ec6bc73fea19f68efea136f85de113d782bd6810d8b441454c89d02206e14c424bfc99c4edc041c226b1298bdeb2637733fa994d1aba5124befc2f04a012103c297c5a04f842757d1b4a8115c86dedf6e271afff8185eb73d21b45fe3d00e8402483045022100ccc1e6d1b30afa65069e5228e8e962552177e60f25b56f17d31363baaf9c7a5c022056d661653a5ad31760ec8e0bb1be50faa23883aebf4646fabadcdb628766d8a6012103c297c5a04f842757d1b4a8115c86dedf6e271afff8185eb73d21b45fe3d00e8400000000").unwrap(); + + let prev_outputs = vec![ + // Added by holder + TxOut { + value: Amount::from_sat(104127), + script_pubkey: ScriptBuf::new_p2wpkh(&WPubkeyHash::from_byte_array( + <[u8; 20]>::from_hex("0b29de1e14f8ebc26b65d307b66d521ceb8d40b0").unwrap(), + )), + }, + // Added by remote (corresponding input should not be checked) + TxOut { + value: Amount::from_sat(48509), + script_pubkey: ScriptBuf::new_p2wpkh(&WPubkeyHash::from_byte_array( + <[u8; 20]>::from_hex("0b29de1e14f8ebc26b65d307b66d521ceb8d40b0").unwrap(), + )), + }, + ]; + + assert!(do_verify_tx_signatures(transaction, prev_outputs).is_ok()); + } + + #[test] + fn test_verify_tx_signatures_p2tr_key_path() { + // Using on-chain transaction: https://mempool.space/tx/d26108e025ada641e4f1163e372c74087c0e471f3756bd3c736854bee9b5a06a + let transaction: Transaction = bitcoin::consensus::encode::deserialize_hex("020000000001025f17ea06dd80e90a7c59bf2710903d938561f45c08cd3187d379e988f282d3c30000000000fdffffff10146764d4bb7e5fe0503df41a042ff39b175070ab1dd05345cbe1a12ac6fbbd0100000000fdffffff01a04f020000000000220020d55050579d2bcdf9ecfdf75df7741b8ac16d572b5cdf326028b4f3538ad34b5e0140e769ec44d5e30fe84ff5d873ed20d1a8ffa8b444e208b0584e24cb94b798286f46f4ba0d4dedfa279870c6b2e43aee45802128e7227e45a043c6193743c1c3240400483045022100cdf0cedd4e35d23af24e0c786bce5bbb47147e867e72fdb49b165a0fa7cac668022035493bb8f280115846c3475f51f7e1b56ec67dfb73744faa74b47d049e20436f01473044022034e60933f7a42effe174dbbb33ec60c1e4b06df1f0356caffbfae053944b552702207b59f352bb8a6100ca14c6dc486eb17145bde714c26766cd8bc2e0a139b06789014752210329a0c88d99fa89cb9497205a237da07b26737e5382dafca6cf40a3fd454b955021032e80b176382ccb76832cd773cf76cbb89883ea74a5b1bb5fa0e30b0bfc87ed8452ae7ed70d00").unwrap(); + + let prev_outputs = + vec![ + // Added by holder + TxOut { + value: Amount::from_sat(25841), + script_pubkey: ScriptBuf::new_p2tr_tweaked( + TweakedPublicKey::dangerous_assume_tweaked(XOnlyPublicKey::from_slice( + &<[u8; 32]>::from_hex( + "ce78617dd8b31b96b24e89140639f9d87b6c6cf3b2cc8f3ff2b3afa0e505d7ec", + ) + .unwrap(), + ).unwrap()), + ), + }, + // Added by remote (corresponding input should not be checked) + TxOut { + value: Amount::from_sat(126239), + script_pubkey: ScriptBuf::new_p2wsh(&WScriptHash::from_byte_array( + <[u8; 32]>::from_hex( + "c9b4e860479f930f054949e5a0be58d25958204e819cc1c62f89c48216eaab27", + ) + .unwrap(), + )), + }, + ]; + + assert!(do_verify_tx_signatures(transaction, prev_outputs).is_ok()); + } + + #[test] + fn test_verify_tx_signatures_p2tr_script_path() { + // Using on-chain transaction: https://mempool.space/tx/905ecdf95a84804b192f4dc221cfed4d77959b81ed66013a7e41a6e61e7ed530 + let transaction: Transaction = bitcoin::consensus::encode::deserialize_hex("02000000000101b41b20295ac85fd2ae3e3d02900f1a1e7ddd6139b12e341386189c03d6f5795b0000000000fdffffff0100000000000000003c6a3a546878205361746f7368692120e2889e2f32316d696c20466972737420546170726f6f74206d756c7469736967207370656e64202d426974476f044123b1d4ff27b16af4b0fcb9672df671701a1a7f5a6bb7352b051f461edbc614aa6068b3e5313a174f90f3d95dc4e06f69bebd9cf5a3098fde034b01e69e8e788901400fd4a0d3f36a1f1074cb15838a48f572dc18d412d0f0f0fc1eeda9fa4820c942abb77e4d1a3c2b99ccf4ad29d9189e6e04a017fe611748464449f681bc38cf394420febe583fa77e49089f89b78fa8c116710715d6e40cc5f5a075ef1681550dd3c4ad20d0fa46cb883e940ac3dc5421f05b03859972639f51ed2eccbf3dc5a62e2e1b15ac41c02e44c9e47eaeb4bb313adecd11012dfad435cd72ce71f525329f24d75c5b9432774e148e9209baf3f1656a46986d5f38ddf4e20912c6ac28f48d6bf747469fb100000000").unwrap(); + + let prev_outputs = + vec![ + // Added by holder + TxOut { + value: Amount::from_sat(7500), + script_pubkey: ScriptBuf::new_p2tr_tweaked( + TweakedPublicKey::dangerous_assume_tweaked(XOnlyPublicKey::from_slice( + &<[u8; 32]>::from_hex( + "2fcad7470279652cc5f88b8908678d6f4d57af5627183b03fc8404cb4e16d889", + ) + .unwrap(), + ).unwrap()), + ), + }, + ]; + + assert!(do_verify_tx_signatures(transaction, prev_outputs).is_ok()); + } } diff --git a/lightning/src/ln/mod.rs b/lightning/src/ln/mod.rs index a513582cb64..07ef7238fbe 100644 --- a/lightning/src/ln/mod.rs +++ b/lightning/src/ln/mod.rs @@ -18,6 +18,7 @@ pub mod channel_keys; pub mod channel_state; pub mod channelmanager; mod features; +pub mod funding; pub mod inbound_payment; pub mod msgs; pub mod onion_payment; @@ -60,7 +61,7 @@ pub use onion_utils::process_onion_failure; #[cfg(fuzzing)] pub use onion_utils::AttributionData; -#[cfg(all(test, async_payments))] +#[cfg(test)] #[allow(unused_mut)] mod async_payments_tests; #[cfg(test)] @@ -77,7 +78,7 @@ pub mod bolt11_payment_tests; mod chanmon_update_fail_tests; #[cfg(test)] #[allow(unused_mut)] -mod channel_acceptance_tests; +mod channel_open_tests; #[cfg(test)] #[allow(unused_mut)] mod channel_type_tests; diff --git a/lightning/src/ln/monitor_tests.rs b/lightning/src/ln/monitor_tests.rs index 13b9301084b..417f892a079 100644 --- a/lightning/src/ln/monitor_tests.rs +++ b/lightning/src/ln/monitor_tests.rs @@ -82,8 +82,8 @@ fn chanmon_fail_from_stale_commitment() { // Don't bother delivering the new HTLC add/commits, instead confirming the pre-HTLC commitment // transaction for nodes[1]. mine_transaction(&nodes[1], &bs_txn[0]); - check_added_monitors!(nodes[1], 1); check_closed_broadcast!(nodes[1], true); + check_added_monitors!(nodes[1], 1); check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [nodes[2].node.get_our_node_id()], 100000); assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty()); @@ -138,9 +138,9 @@ fn revoked_output_htlc_resolution_timing() { // Confirm the revoked commitment transaction, closing the channel. mine_transaction(&nodes[1], &revoked_local_txn[0]); + check_closed_broadcast!(nodes[1], true); check_added_monitors!(nodes[1], 1); check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [nodes[0].node.get_our_node_id()], 1000000); - check_closed_broadcast!(nodes[1], true); // Two justice transactions will be broadcast, one on the unpinnable, revoked to_self output, // and one on the pinnable revoked HTLC output. @@ -658,14 +658,14 @@ fn do_test_claim_value_force_close(anchors: bool, prev_commitment_tx: bool) { assert_eq!(remote_txn[0].output[b_broadcast_txn[0].input[0].previous_output.vout as usize].value.to_sat(), 3_000); assert_eq!(remote_txn[0].output[b_broadcast_txn[1].input[0].previous_output.vout as usize].value.to_sat(), 4_000); - assert!(nodes[0].node.list_channels().is_empty()); check_closed_broadcast!(nodes[0], true); check_added_monitors!(nodes[0], 1); check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed, [nodes[1].node.get_our_node_id()], 1000000); - assert!(nodes[1].node.list_channels().is_empty()); + assert!(nodes[0].node.list_channels().is_empty()); check_closed_broadcast!(nodes[1], true); check_added_monitors!(nodes[1], 1); check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [nodes[0].node.get_our_node_id()], 1000000); + assert!(nodes[1].node.list_channels().is_empty()); assert!(nodes[0].node.get_and_clear_pending_events().is_empty()); assert!(nodes[1].node.get_and_clear_pending_events().is_empty()); @@ -896,6 +896,9 @@ fn do_test_balances_on_local_commitment_htlcs(anchors: bool) { check_closed_broadcast!(nodes[0], true); let reason = ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(true), message }; check_closed_event!(nodes[0], 1, reason, [nodes[1].node.get_our_node_id()], 1000000); + if anchors { + handle_bump_close_event(&nodes[0]); + } let commitment_tx = { let mut txn = nodes[0].tx_broadcaster.unique_txn_broadcast(); assert_eq!(txn.len(), 1); @@ -905,9 +908,15 @@ fn do_test_balances_on_local_commitment_htlcs(anchors: bool) { }; let commitment_tx_conf_height_a = block_from_scid(mine_transaction(&nodes[0], &commitment_tx)); if nodes[0].connect_style.borrow().updates_best_block_first() { + if anchors { + handle_bump_close_event(&nodes[0]); + } let mut txn = nodes[0].tx_broadcaster.txn_broadcast(); - assert_eq!(txn.len(), 1); + assert_eq!(txn.len(), if anchors { 2 } else { 1 }); assert_eq!(txn[0].compute_txid(), commitment_tx.compute_txid()); + if anchors { + check_spends!(txn[1], txn[0]); // Anchor output spend. + } } let htlc_balance_known_preimage = Balance::MaybeTimeoutClaimableHTLC { @@ -935,8 +944,8 @@ fn do_test_balances_on_local_commitment_htlcs(anchors: bool) { // Get nodes[1]'s HTLC claim tx for the second HTLC mine_transaction(&nodes[1], &commitment_tx); - check_added_monitors!(nodes[1], 1); check_closed_broadcast!(nodes[1], true); + check_added_monitors!(nodes[1], 1); check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [nodes[0].node.get_our_node_id()], 1000000); let bs_htlc_claim_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0); assert_eq!(bs_htlc_claim_txn.len(), 1); @@ -1153,16 +1162,16 @@ fn test_no_preimage_inbound_htlc_balances() { mine_transaction(&nodes[0], &as_txn[0]); nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().clear(); - check_added_monitors!(nodes[0], 1); check_closed_broadcast!(nodes[0], true); + check_added_monitors!(nodes[0], 1); check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed, [nodes[1].node.get_our_node_id()], 1000000); assert_eq!(as_pre_spend_claims, sorted_vec(nodes[0].chain_monitor.chain_monitor.get_monitor(chan_id).unwrap().get_claimable_balances())); mine_transaction(&nodes[1], &as_txn[0]); - check_added_monitors!(nodes[1], 1); check_closed_broadcast!(nodes[1], true); + check_added_monitors!(nodes[1], 1); check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [nodes[0].node.get_our_node_id()], 1000000); let node_b_commitment_claimable = nodes[1].best_block_info().1 + ANTI_REORG_DELAY - 1; @@ -1398,14 +1407,22 @@ fn do_test_revoked_counterparty_commitment_balances(anchors: bool, confirm_htlc_ }); assert!(failed_payments.is_empty()); match &events[0] { - Event::ChannelClosed { reason: ClosureReason::HTLCsTimedOut, .. } => {}, + Event::ChannelClosed { reason: ClosureReason::HTLCsTimedOut { .. }, .. } => {}, _ => panic!(), } connect_blocks(&nodes[1], htlc_cltv_timeout + 1 - 10); check_closed_broadcast!(nodes[1], true); check_added_monitors!(nodes[1], 1); - check_closed_event!(nodes[1], 1, ClosureReason::HTLCsTimedOut, [nodes[0].node.get_our_node_id()], 1000000); + check_closed_events(&nodes[1], &[ExpectedCloseEvent { + channel_capacity_sats: Some(1_000_000), + channel_id: Some(chan_id), + counterparty_node_id: Some(nodes[0].node.get_our_node_id()), + discard_funding: false, + reason: None, // Could be due to any HTLC timing out, so don't bother checking + channel_funding_txo: None, + user_channel_id: None, + }]); // Prior to channel closure, B considers the preimage HTLC as its own, and otherwise only // lists the two on-chain timeout-able HTLCs as claimable balances. @@ -2486,6 +2503,7 @@ fn do_test_yield_anchors_events(have_htlcs: bool) { nodes[1].node.force_close_broadcasting_latest_txn(&chan_id, &nodes[0].node.get_our_node_id(), "".to_string()).unwrap(); } { + handle_bump_close_event(&nodes[1]); let txn = nodes[1].tx_broadcaster.txn_broadcast(); assert_eq!(txn.len(), 1); check_spends!(txn[0], funding_tx); @@ -2553,15 +2571,18 @@ fn do_test_yield_anchors_events(have_htlcs: bool) { } { + if nodes[1].connect_style.borrow().updates_best_block_first() { + handle_bump_close_event(&nodes[1]); + } let mut txn = nodes[1].tx_broadcaster.unique_txn_broadcast(); // Both HTLC claims are pinnable at this point, // and will be broadcast in a single transaction. - assert_eq!(txn.len(), if nodes[1].connect_style.borrow().updates_best_block_first() { 2 } else { 1 }); + assert_eq!(txn.len(), if nodes[1].connect_style.borrow().updates_best_block_first() { 3 } else { 1 }); if nodes[1].connect_style.borrow().updates_best_block_first() { - let new_commitment_tx = txn.remove(0); - check_spends!(new_commitment_tx, funding_tx); + check_spends!(txn[1], funding_tx); + check_spends!(txn[2], txn[1]); // Anchor output spend. } - let htlc_claim_tx = txn.pop().unwrap(); + let htlc_claim_tx = &txn[0]; assert_eq!(htlc_claim_tx.input.len(), 2); assert_eq!(htlc_claim_tx.input[0].previous_output.vout, 2); assert_eq!(htlc_claim_tx.input[1].previous_output.vout, 3); @@ -2722,8 +2743,8 @@ fn test_anchors_aggregated_revoked_htlc_tx() { for node in &nodes { mine_transactions(node, &[&revoked_commitment_txs[0], &anchor_txs[0], &revoked_commitment_txs[1], &anchor_txs[1]]); } - check_added_monitors!(&nodes[0], 2); check_closed_broadcast(&nodes[0], 2, true); + check_added_monitors!(&nodes[0], 2); check_closed_event!(&nodes[0], 2, ClosureReason::CommitmentTxConfirmed, [nodes[1].node.get_our_node_id(); 2], 1000000); // Alice should detect the confirmed revoked commitments, and attempt to claim all of the @@ -2952,6 +2973,7 @@ fn do_test_anchors_monitor_fixes_counterparty_payment_script_on_reload(confirm_c let reason = ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(true), message }; check_closed_event!(&nodes[0], 1, reason, false, [nodes[1].node.get_our_node_id()], 100000); + handle_bump_close_event(&nodes[0]); let commitment_tx = { let mut txn = nodes[0].tx_broadcaster.unique_txn_broadcast(); @@ -2967,6 +2989,7 @@ fn do_test_anchors_monitor_fixes_counterparty_payment_script_on_reload(confirm_c // with the incorrect P2WPKH script but reading it with the correct P2WSH script. *nodes[1].chain_monitor.expect_monitor_round_trip_fail.lock().unwrap() = Some(chan_id); let commitment_tx_conf_height = block_from_scid(mine_transaction(&nodes[1], &commitment_tx)); + check_closed_broadcast(&nodes[1], 1, true); let serialized_monitor = get_monitor!(nodes[1], chan_id).encode(); reload_node!(nodes[1], user_config, &nodes[1].node.encode(), &[&serialized_monitor], persister, chain_monitor, node_deserialized); commitment_tx_conf_height @@ -2974,8 +2997,8 @@ fn do_test_anchors_monitor_fixes_counterparty_payment_script_on_reload(confirm_c let serialized_monitor = get_monitor!(nodes[1], chan_id).encode(); reload_node!(nodes[1], user_config, &nodes[1].node.encode(), &[&serialized_monitor], persister, chain_monitor, node_deserialized); let commitment_tx_conf_height = block_from_scid(mine_transaction(&nodes[1], &commitment_tx)); - check_added_monitors(&nodes[1], 1); check_closed_broadcast(&nodes[1], 1, true); + check_added_monitors(&nodes[1], 1); commitment_tx_conf_height }; check_closed_event!(&nodes[1], 1, ClosureReason::CommitmentTxConfirmed, false, @@ -3036,6 +3059,9 @@ fn do_test_monitor_claims_with_random_signatures(anchors: bool, confirm_counterp get_monitor!(closing_node, chan_id).broadcast_latest_holder_commitment_txn( &closing_node.tx_broadcaster, &closing_node.fee_estimator, &closing_node.logger ); + if anchors { + handle_bump_close_event(&closing_node); + } // The commitment transaction comes first. let commitment_tx = { @@ -3046,13 +3072,14 @@ fn do_test_monitor_claims_with_random_signatures(anchors: bool, confirm_counterp }; mine_transaction(closing_node, &commitment_tx); - check_added_monitors!(closing_node, 1); check_closed_broadcast!(closing_node, true); - check_closed_event!(closing_node, 1, ClosureReason::CommitmentTxConfirmed, [other_node.node.get_our_node_id()], 1_000_000); + check_added_monitors!(closing_node, 1); + let message = "ChannelMonitor-initiated commitment transaction broadcast".to_string(); + check_closed_event!(closing_node, 1, ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(true), message }, [other_node.node.get_our_node_id()], 1_000_000); mine_transaction(other_node, &commitment_tx); - check_added_monitors!(other_node, 1); check_closed_broadcast!(other_node, true); + check_added_monitors!(other_node, 1); check_closed_event!(other_node, 1, ClosureReason::CommitmentTxConfirmed, [closing_node.node.get_our_node_id()], 1_000_000); // If we update the best block to the new height before providing the confirmed transactions, @@ -3211,3 +3238,65 @@ fn test_update_replay_panics() { monitor.update_monitor(&updates[2], &nodes[1].tx_broadcaster, &nodes[1].fee_estimator, &nodes[1].logger).unwrap(); monitor.update_monitor(&updates[3], &nodes[1].tx_broadcaster, &nodes[1].fee_estimator, &nodes[1].logger).unwrap(); } + +#[test] +fn test_claim_event_never_handled() { + // When a payment is claimed, the `ChannelMonitorUpdate` containing the payment preimage goes + // out and when it completes the `PaymentClaimed` event is generated. If the channel then + // progresses forward a few steps, the payment preimage will then eventually be removed from + // the channel. By that point, we have to make sure that the `PaymentClaimed` event has been + // handled (which ensures the user has maked the payment received). + // Otherwise, it is possible that, on restart, we load with a stale `ChannelManager` which + // doesn't have the `PaymentClaimed` event and it needs to rebuild it from the + // `ChannelMonitor`'s payment information and preimage. + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let persister; + let new_chain_mon; + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes_1_reload; + let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let node_a_id = nodes[0].node.get_our_node_id(); + let node_b_id = nodes[1].node.get_our_node_id(); + + let init_node_ser = nodes[1].node.encode(); + + let chan = create_announced_chan_between_nodes(&nodes, 0, 1); + + // Send the payment we'll ultimately test the PaymentClaimed event for. + let (preimage_a, payment_hash_a, ..) = route_payment(&nodes[0], &[&nodes[1]], 1_000_000); + + nodes[1].node.claim_funds(preimage_a); + check_added_monitors(&nodes[1], 1); + + let mut updates = get_htlc_update_msgs(&nodes[1], &node_a_id); + nodes[0].node.handle_update_fulfill_htlc(node_b_id, updates.update_fulfill_htlcs.remove(0)); + expect_payment_sent(&nodes[0], preimage_a, None, false, false); + + nodes[0].node.handle_commitment_signed_batch_test(node_b_id, &updates.commitment_signed); + check_added_monitors(&nodes[0], 1); + + // Once the `PaymentClaimed` event is generated, further RAA `ChannelMonitorUpdate`s will be + // blocked until it is handled, ensuring we never get far enough to remove the preimage. + let (raa, cs) = get_revoke_commit_msgs(&nodes[0], &node_b_id); + nodes[1].node.handle_revoke_and_ack(node_a_id, &raa); + nodes[1].node.handle_commitment_signed_batch_test(node_a_id, &cs); + check_added_monitors(&nodes[1], 0); + + // The last RAA here should be blocked waiting on us to handle the PaymentClaimed event before + // continuing. Otherwise, we'd be able to make enough progress that the payment preimage is + // removed from node A's `ChannelMonitor`. This leaves us unable to make further progress. + assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty()); + + // Finally, reload node B with an empty `ChannelManager` and check that we get the + // `PaymentClaimed` event. + let chan_0_monitor_serialized = get_monitor!(nodes[1], chan.2).encode(); + let mons = &[&chan_0_monitor_serialized[..]]; + reload_node!(nodes[1], &init_node_ser, mons, persister, new_chain_mon, nodes_1_reload); + + expect_payment_claimed!(nodes[1], payment_hash_a, 1_000_000); + // The reload logic spuriously generates a redundant payment preimage-containing + // `ChannelMonitorUpdate`. + check_added_monitors(&nodes[1], 2); +} diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index e0219a5523f..71f73e04c2f 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -29,7 +29,7 @@ use bitcoin::hash_types::Txid; use bitcoin::script::ScriptBuf; use bitcoin::secp256k1::ecdsa::Signature; use bitcoin::secp256k1::PublicKey; -use bitcoin::{secp256k1, Witness}; +use bitcoin::{secp256k1, Transaction, Witness}; use crate::blinded_path::payment::{ BlindedPaymentTlvs, ForwardTlvs, ReceiveTlvs, UnauthenticatedReceiveTlvs, @@ -63,8 +63,7 @@ use crate::util::base32; use crate::util::logger; use crate::util::ser::{ BigSize, FixedLengthReader, HighZeroBytesDroppedBigSize, Hostname, LengthLimitedRead, - LengthReadable, LengthReadableArgs, Readable, ReadableArgs, TransactionU16LenLimited, - WithoutLength, Writeable, Writer, + LengthReadable, LengthReadableArgs, Readable, ReadableArgs, WithoutLength, Writeable, Writer, }; use crate::routing::gossip::{NodeAlias, NodeId}; @@ -524,7 +523,7 @@ pub struct TxAddInput { pub serial_id: SerialId, /// Serialized transaction that contains the output this input spends to verify that it is /// non-malleable. Omitted for shared input. - pub prevtx: Option, + pub prevtx: Option, /// The index of the output being spent pub prevtx_out: u32, /// The sequence number of this input @@ -2738,16 +2737,58 @@ impl_writeable_msg!(SpliceLocked, { splice_txid, }, {}); -impl_writeable_msg!(TxAddInput, { - channel_id, - serial_id, - prevtx, - prevtx_out, - sequence, -}, { - (0, shared_input_txid, option), // `funding_txid` -}); +impl Writeable for TxAddInput { + fn write(&self, w: &mut W) -> Result<(), io::Error> { + self.channel_id.write(w)?; + self.serial_id.write(w)?; + + match &self.prevtx { + Some(tx) => { + (tx.serialized_length() as u16).write(w)?; + tx.write(w)?; + }, + None => 0u16.write(w)?, + } + + self.prevtx_out.write(w)?; + self.sequence.write(w)?; + + encode_tlv_stream!(w, { + (0, self.shared_input_txid, option), + }); + Ok(()) + } +} + +impl LengthReadable for TxAddInput { + fn read_from_fixed_length_buffer(r: &mut R) -> Result { + let channel_id: ChannelId = Readable::read(r)?; + let serial_id: SerialId = Readable::read(r)?; + + let prevtx_len: u16 = Readable::read(r)?; + let prevtx = if prevtx_len > 0 { + let mut tx_reader = FixedLengthReader::new(r, prevtx_len as u64); + let tx: Transaction = Readable::read(&mut tx_reader)?; + if tx_reader.bytes_remain() { + return Err(DecodeError::BadLengthDescriptor); + } + + Some(tx) + } else { + None + }; + + let prevtx_out: u32 = Readable::read(r)?; + let sequence: u32 = Readable::read(r)?; + let mut shared_input_txid: Option = None; + decode_tlv_stream!(r, { + (0, shared_input_txid, option), + }); + + Ok(TxAddInput { channel_id, serial_id, prevtx, prevtx_out, sequence, shared_input_txid }) + } +} impl_writeable_msg!(TxAddOutput, { channel_id, serial_id, @@ -4224,10 +4265,7 @@ mod tests { ChannelFeatures, ChannelTypeFeatures, InitFeatures, NodeFeatures, }; use crate::types::payment::{PaymentHash, PaymentPreimage, PaymentSecret}; - use crate::util::ser::{ - BigSize, Hostname, LengthReadable, Readable, ReadableArgs, TransactionU16LenLimited, - Writeable, - }; + use crate::util::ser::{BigSize, Hostname, LengthReadable, Readable, ReadableArgs, Writeable}; use crate::util::test_utils; use bitcoin::hex::DisplayHex; use bitcoin::{Amount, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Witness}; @@ -5299,7 +5337,7 @@ mod tests { let tx_add_input = msgs::TxAddInput { channel_id: ChannelId::from_bytes([2; 32]), serial_id: 4886718345, - prevtx: Some(TransactionU16LenLimited::new(Transaction { + prevtx: Some(Transaction { version: Version::TWO, lock_time: LockTime::ZERO, input: vec![TxIn { @@ -5320,7 +5358,7 @@ mod tests { script_pubkey: Address::from_str("bc1qxmk834g5marzm227dgqvynd23y2nvt2ztwcw2z").unwrap().assume_checked().script_pubkey(), }, ], - }).unwrap()), + }), prevtx_out: 305419896, sequence: 305419896, shared_input_txid: None, diff --git a/lightning/src/ln/offers_tests.rs b/lightning/src/ln/offers_tests.rs index ba9858be197..6c56ecc4270 100644 --- a/lightning/src/ln/offers_tests.rs +++ b/lightning/src/ln/offers_tests.rs @@ -227,7 +227,6 @@ pub(super) fn extract_invoice_request<'a, 'b, 'c>( Ok(PeeledOnion::Offers(message, _, reply_path)) => match message { OffersMessage::InvoiceRequest(invoice_request) => (invoice_request, reply_path.unwrap()), OffersMessage::Invoice(invoice) => panic!("Unexpected invoice: {:?}", invoice), - #[cfg(async_payments)] OffersMessage::StaticInvoice(invoice) => panic!("Unexpected static invoice: {:?}", invoice), OffersMessage::InvoiceError(error) => panic!("Unexpected invoice_error: {:?}", error), }, @@ -242,7 +241,6 @@ fn extract_invoice<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, message: &OnionMessage) Ok(PeeledOnion::Offers(message, _, reply_path)) => match message { OffersMessage::InvoiceRequest(invoice_request) => panic!("Unexpected invoice_request: {:?}", invoice_request), OffersMessage::Invoice(invoice) => (invoice, reply_path.unwrap()), - #[cfg(async_payments)] OffersMessage::StaticInvoice(invoice) => panic!("Unexpected static invoice: {:?}", invoice), OffersMessage::InvoiceError(error) => panic!("Unexpected invoice_error: {:?}", error), }, @@ -259,7 +257,6 @@ fn extract_invoice_error<'a, 'b, 'c>( Ok(PeeledOnion::Offers(message, _, _)) => match message { OffersMessage::InvoiceRequest(invoice_request) => panic!("Unexpected invoice_request: {:?}", invoice_request), OffersMessage::Invoice(invoice) => panic!("Unexpected invoice: {:?}", invoice), - #[cfg(async_payments)] OffersMessage::StaticInvoice(invoice) => panic!("Unexpected invoice: {:?}", invoice), OffersMessage::InvoiceError(error) => error, }, @@ -1235,7 +1232,7 @@ fn pays_bolt12_invoice_asynchronously() { let onion_message = alice.onion_messenger.next_onion_message_for_peer(bob_id).unwrap(); bob.onion_messenger.handle_onion_message(alice_id, &onion_message); - // Re-process the same onion message to ensure idempotency — + // Re-process the same onion message to ensure idempotency — // we should not generate a duplicate `InvoiceReceived` event. bob.onion_messenger.handle_onion_message(alice_id, &onion_message); diff --git a/lightning/src/ln/onion_payment.rs b/lightning/src/ln/onion_payment.rs index 79952faca9a..474c7017e2f 100644 --- a/lightning/src/ln/onion_payment.rs +++ b/lightning/src/ln/onion_payment.rs @@ -74,6 +74,34 @@ fn check_blinded_forward( Ok((amt_to_forward, outgoing_cltv_value)) } +fn check_trampoline_onion_constraints( + outer_hop_data: &msgs::InboundTrampolineEntrypointPayload, trampoline_cltv_value: u32, + trampoline_amount: u64, +) -> Result<(), InboundHTLCErr> { + if outer_hop_data.outgoing_cltv_value < trampoline_cltv_value { + let err = InboundHTLCErr { + reason: LocalHTLCFailureReason::FinalIncorrectCLTVExpiry, + err_data: outer_hop_data.outgoing_cltv_value.to_be_bytes().to_vec(), + msg: "Trampoline onion's CLTV value exceeded the outer onion's", + }; + return Err(err); + } + let outgoing_amount = outer_hop_data + .multipath_trampoline_data + .as_ref() + .map_or(outer_hop_data.amt_to_forward, |mtd| mtd.total_msat); + if outgoing_amount < trampoline_amount { + let err = InboundHTLCErr { + reason: LocalHTLCFailureReason::FinalIncorrectHTLCAmount, + err_data: outgoing_amount.to_be_bytes().to_vec(), + msg: "Trampoline onion's amt value exceeded the outer onion's", + }; + return Err(err); + } + + Ok(()) +} + enum RoutingInfo { Direct { short_channel_id: u64, @@ -135,7 +163,9 @@ pub(super) fn create_fwd_pending_htlc_info( reason: LocalHTLCFailureReason::InvalidOnionPayload, err_data: Vec::new(), }), - onion_utils::Hop::TrampolineForward { next_trampoline_hop_data, next_trampoline_hop_hmac, new_trampoline_packet_bytes, trampoline_shared_secret, .. } => { + onion_utils::Hop::TrampolineForward { ref outer_hop_data, next_trampoline_hop_data, next_trampoline_hop_hmac, new_trampoline_packet_bytes, trampoline_shared_secret, .. } => { + // TODO: return reason as forward issue, not as receiving issue when forwarding is ready. + check_trampoline_onion_constraints(outer_hop_data, next_trampoline_hop_data.outgoing_cltv_value, next_trampoline_hop_data.amt_to_forward)?; ( RoutingInfo::Trampoline { next_trampoline: next_trampoline_hop_data.next_trampoline, @@ -150,7 +180,7 @@ pub(super) fn create_fwd_pending_htlc_info( None ) }, - onion_utils::Hop::TrampolineBlindedForward { outer_hop_data, next_trampoline_hop_data, next_trampoline_hop_hmac, new_trampoline_packet_bytes, trampoline_shared_secret, .. } => { + onion_utils::Hop::TrampolineBlindedForward { ref outer_hop_data, next_trampoline_hop_data, next_trampoline_hop_hmac, new_trampoline_packet_bytes, trampoline_shared_secret, .. } => { let (amt_to_forward, outgoing_cltv_value) = check_blinded_forward( msg.amount_msat, msg.cltv_expiry, &next_trampoline_hop_data.payment_relay, &next_trampoline_hop_data.payment_constraints, &next_trampoline_hop_data.features ).map_err(|()| { @@ -162,6 +192,15 @@ pub(super) fn create_fwd_pending_htlc_info( err_data: vec![0; 32], } })?; + check_trampoline_onion_constraints(outer_hop_data, outgoing_cltv_value, amt_to_forward).map_err(|e| { + // The Trampoline onion's amt and CLTV values cannot exceed the outer onion's, but + // we're inside a blinded path + InboundHTLCErr { + reason: LocalHTLCFailureReason::InvalidOnionBlinding, + err_data: vec![0; 32], + msg: e.msg, + } + })?; ( RoutingInfo::Trampoline { next_trampoline: next_trampoline_hop_data.next_trampoline, @@ -281,14 +320,18 @@ pub(super) fn create_recv_pending_htlc_info( intro_node_blinding_point.is_none(), true, invoice_request) } onion_utils::Hop::TrampolineReceive { + ref outer_hop_data, trampoline_hop_data: msgs::InboundOnionReceivePayload { payment_data, keysend_preimage, custom_tlvs, sender_intended_htlc_amt_msat, cltv_expiry_height, payment_metadata, .. }, .. - } => + } => { + check_trampoline_onion_constraints(outer_hop_data, cltv_expiry_height, sender_intended_htlc_amt_msat)?; (payment_data, keysend_preimage, custom_tlvs, sender_intended_htlc_amt_msat, - cltv_expiry_height, payment_metadata, None, false, keysend_preimage.is_none(), None), + cltv_expiry_height, payment_metadata, None, false, keysend_preimage.is_none(), None) + } onion_utils::Hop::TrampolineBlindedReceive { + ref outer_hop_data, trampoline_hop_data: msgs::InboundOnionBlindedReceivePayload { sender_intended_htlc_amt_msat, total_msat, cltv_expiry_height, payment_secret, intro_node_blinding_point, payment_constraints, payment_context, keysend_preimage, @@ -306,6 +349,15 @@ pub(super) fn create_recv_pending_htlc_info( } })?; let payment_data = msgs::FinalOnionHopData { payment_secret, total_msat }; + check_trampoline_onion_constraints(outer_hop_data, cltv_expiry_height, sender_intended_htlc_amt_msat).map_err(|e| { + // The Trampoline onion's amt and CLTV values cannot exceed the outer onion's, but + // we're inside a blinded path + InboundHTLCErr { + reason: LocalHTLCFailureReason::InvalidOnionBlinding, + err_data: vec![0; 32], + msg: e.msg, + } + })?; (Some(payment_data), keysend_preimage, custom_tlvs, sender_intended_htlc_amt_msat, cltv_expiry_height, None, Some(payment_context), intro_node_blinding_point.is_none(), true, invoice_request) @@ -602,6 +654,25 @@ where outgoing_cltv_value, }) } + onion_utils::Hop::TrampolineBlindedForward { next_trampoline_hop_data: msgs::InboundTrampolineBlindedForwardPayload { next_trampoline, ref payment_relay, ref payment_constraints, ref features, .. }, outer_shared_secret, trampoline_shared_secret, incoming_trampoline_public_key, .. } => { + let (amt_to_forward, outgoing_cltv_value) = match check_blinded_forward( + msg.amount_msat, msg.cltv_expiry, &payment_relay, &payment_constraints, &features + ) { + Ok((amt, cltv)) => (amt, cltv), + Err(()) => { + return encode_relay_error("Trampoline blinded forward amt or CLTV values exceeded the outer onion's", + LocalHTLCFailureReason::InvalidOnionBlinding, outer_shared_secret.secret_bytes(), Some(trampoline_shared_secret.secret_bytes()), &[0; 32]); + } + }; + let next_trampoline_packet_pubkey = onion_utils::next_hop_pubkey(secp_ctx, + incoming_trampoline_public_key, &trampoline_shared_secret.secret_bytes()); + Some(NextPacketDetails { + next_packet_pubkey: next_trampoline_packet_pubkey, + outgoing_connector: HopConnector::Trampoline(next_trampoline), + outgoing_amt_msat: amt_to_forward, + outgoing_cltv_value, + }) + } _ => None }; diff --git a/lightning/src/ln/onion_utils.rs b/lightning/src/ln/onion_utils.rs index d45860b0e26..9987a87437e 100644 --- a/lightning/src/ln/onion_utils.rs +++ b/lightning/src/ln/onion_utils.rs @@ -992,41 +992,19 @@ pub fn process_onion_failure( where L::Target: Logger, { - let (path, primary_session_priv) = match htlc_source { + let (path, session_priv) = match htlc_source { HTLCSource::OutboundRoute { ref path, ref session_priv, .. } => (path, session_priv), _ => unreachable!(), }; - if path.has_trampoline_hops() { - // If we have Trampoline hops, the outer onion session_priv is a hash of the inner one. - let session_priv_hash = Sha256::hash(&primary_session_priv.secret_bytes()).to_byte_array(); - let outer_session_priv = - SecretKey::from_slice(&session_priv_hash[..]).expect("You broke SHA-256!"); - process_onion_failure_inner( - secp_ctx, - logger, - path, - &outer_session_priv, - Some(primary_session_priv), - encrypted_packet, - ) - } else { - process_onion_failure_inner( - secp_ctx, - logger, - path, - primary_session_priv, - None, - encrypted_packet, - ) - } + process_onion_failure_inner(secp_ctx, logger, path, &session_priv, None, encrypted_packet) } /// Process failure we got back from upstream on a payment we sent (implying htlc_source is an /// OutboundRoute). fn process_onion_failure_inner( - secp_ctx: &Secp256k1, logger: &L, path: &Path, outer_session_priv: &SecretKey, - inner_session_priv: Option<&SecretKey>, mut encrypted_packet: OnionErrorPacket, + secp_ctx: &Secp256k1, logger: &L, path: &Path, session_priv: &SecretKey, + trampoline_session_priv_override: Option, mut encrypted_packet: OnionErrorPacket, ) -> DecodedOnionFailure where L::Target: Logger, @@ -1097,22 +1075,27 @@ where let nontrampoline_bt = if path.has_trampoline_hops() { None } else { path.blinded_tail.as_ref() }; let nontrampolines = - construct_onion_keys_generic(secp_ctx, &path.hops, nontrampoline_bt, outer_session_priv) - .map(|(shared_secret, _, _, route_hop_option, _)| { + construct_onion_keys_generic(secp_ctx, &path.hops, nontrampoline_bt, session_priv).map( + |(shared_secret, _, _, route_hop_option, _)| { (route_hop_option.map(|rh| ErrorHop::RouteHop(rh)), shared_secret) - }); + }, + ); let trampolines = if path.has_trampoline_hops() { // Trampoline hops are part of the blinded tail, so this can never panic let blinded_tail = path.blinded_tail.as_ref(); let hops = &blinded_tail.unwrap().trampoline_hops; - let inner_session_priv = - inner_session_priv.expect("Trampoline hops always have an inner session priv"); - Some(construct_onion_keys_generic(secp_ctx, hops, blinded_tail, inner_session_priv).map( - |(shared_secret, _, _, route_hop_option, _)| { - (route_hop_option.map(|tram_hop| ErrorHop::TrampolineHop(tram_hop)), shared_secret) - }, - )) + let trampoline_session_priv = trampoline_session_priv_override + .unwrap_or_else(|| compute_trampoline_session_priv(session_priv)); + Some( + construct_onion_keys_generic(secp_ctx, hops, blinded_tail, &trampoline_session_priv) + .map(|(shared_secret, _, _, route_hop_option, _)| { + ( + route_hop_option.map(|tram_hop| ErrorHop::TrampolineHop(tram_hop)), + shared_secret, + ) + }), + ) } else { None }; @@ -1390,6 +1373,16 @@ where short_channel_id = route_hop.short_channel_id() } + // If next hop is from a trampoline and there is only one trampoline hop + // in the trampoline route, means that the trampoline hop is also a + // final non-blinded node. + let is_next_hop_trampoline = + matches!(next_hop, Some((_, (Some(ErrorHop::TrampolineHop(..)), _)))); + if is_next_hop_trampoline { + is_from_final_non_blinded_node = + path.blinded_tail.as_ref().map_or(0, |bt| bt.trampoline_hops.len()) == 1 + } + res = Some(FailureLearnings { network_update, short_channel_id, @@ -1678,6 +1671,13 @@ pub enum LocalHTLCFailureReason { HTLCMaximum, /// The HTLC was failed because our remote peer is offline. PeerOffline, + /// We have been unable to forward a payment to the next Trampoline node but may be able to + /// do it later. + TemporaryTrampolineFailure, + /// The amount or CLTV expiry were insufficient to route the payment to the next Trampoline. + TrampolineFeeOrExpiryInsufficient, + /// The specified next Trampoline node cannot be reached from our node. + UnknownNextTrampoline, } impl LocalHTLCFailureReason { @@ -1718,6 +1718,9 @@ impl LocalHTLCFailureReason { Self::InvalidOnionPayload | Self::InvalidTrampolinePayload => PERM | 22, Self::MPPTimeout => 23, Self::InvalidOnionBlinding => BADONION | PERM | 24, + Self::TemporaryTrampolineFailure => NODE | 25, + Self::TrampolineFeeOrExpiryInsufficient => NODE | 26, + Self::UnknownNextTrampoline => PERM | 27, Self::UnknownFailureCode { code } => *code, } } @@ -1852,6 +1855,9 @@ impl_writeable_tlv_based_enum!(LocalHTLCFailureReason, (79, HTLCMinimum) => {}, (81, HTLCMaximum) => {}, (83, PeerOffline) => {}, + (85, TemporaryTrampolineFailure) => {}, + (87, TrampolineFeeOrExpiryInsufficient) => {}, + (89, UnknownNextTrampoline) => {}, ); impl From<&HTLCFailReason> for HTLCHandlingFailureReason { @@ -2018,6 +2024,11 @@ impl HTLCFailReason { debug_assert!(false, "Unknown failure code: {}", code) } }, + LocalHTLCFailureReason::TemporaryTrampolineFailure => debug_assert!(data.is_empty()), + LocalHTLCFailureReason::TrampolineFeeOrExpiryInsufficient => { + debug_assert_eq!(data.len(), 10) + }, + LocalHTLCFailureReason::UnknownNextTrampoline => debug_assert!(data.is_empty()), } Self(HTLCFailReasonRepr::Reason { data, failure_reason }) @@ -2513,18 +2524,24 @@ pub fn create_payment_onion( ) } +pub(super) fn compute_trampoline_session_priv(outer_onion_session_priv: &SecretKey) -> SecretKey { + // When creating the inner trampoline onion, we set the session priv to the hash of the outer + // onion session priv. + let session_priv_hash = Sha256::hash(&outer_onion_session_priv.secret_bytes()).to_byte_array(); + SecretKey::from_slice(&session_priv_hash[..]).expect("You broke SHA-256!") +} + /// Build a payment onion, returning the first hop msat and cltv values as well. /// `cur_block_height` should be set to the best known block height + 1. pub(crate) fn create_payment_onion_internal( secp_ctx: &Secp256k1, path: &Path, session_priv: &SecretKey, total_msat: u64, recipient_onion: &RecipientOnionFields, cur_block_height: u32, payment_hash: &PaymentHash, keysend_preimage: &Option, invoice_request: Option<&InvoiceRequest>, - prng_seed: [u8; 32], secondary_session_priv: Option, - secondary_prng_seed: Option<[u8; 32]>, + prng_seed: [u8; 32], trampoline_session_priv_override: Option, + trampoline_prng_seed_override: Option<[u8; 32]>, ) -> Result<(msgs::OnionPacket, u64, u32), APIError> { let mut outer_total_msat = total_msat; let mut outer_starting_htlc_offset = cur_block_height; - let mut outer_session_priv_override = None; let mut trampoline_packet_option = None; if let Some(blinded_tail) = &path.blinded_tail { @@ -2539,12 +2556,15 @@ pub(crate) fn create_payment_onion_internal( keysend_preimage, )?; + let trampoline_session_priv = trampoline_session_priv_override + .unwrap_or_else(|| compute_trampoline_session_priv(session_priv)); + let trampoline_prng_seed = trampoline_prng_seed_override.unwrap_or(prng_seed); let onion_keys = - construct_trampoline_onion_keys(&secp_ctx, &blinded_tail, &session_priv); + construct_trampoline_onion_keys(&secp_ctx, &blinded_tail, &trampoline_session_priv); let trampoline_packet = construct_trampoline_onion_packet( trampoline_payloads, onion_keys, - prng_seed, + trampoline_prng_seed, payment_hash, // TODO: specify a fixed size for privacy in future spec upgrade None, @@ -2554,11 +2574,6 @@ pub(crate) fn create_payment_onion_internal( })?; trampoline_packet_option = Some(trampoline_packet); - - outer_session_priv_override = Some(secondary_session_priv.unwrap_or_else(|| { - let session_priv_hash = Sha256::hash(&session_priv.secret_bytes()).to_byte_array(); - SecretKey::from_slice(&session_priv_hash[..]).expect("You broke SHA-256!") - })); } } @@ -2572,14 +2587,11 @@ pub(crate) fn create_payment_onion_internal( trampoline_packet_option, )?; - let outer_session_priv = outer_session_priv_override.as_ref().unwrap_or(session_priv); - let onion_keys = construct_onion_keys(&secp_ctx, &path, outer_session_priv); - let outer_onion_prng_seed = secondary_prng_seed.unwrap_or(prng_seed); - let onion_packet = - construct_onion_packet(onion_payloads, onion_keys, outer_onion_prng_seed, payment_hash) - .map_err(|_| APIError::InvalidRoute { - err: "Route size too large considering onion data".to_owned(), - })?; + let onion_keys = construct_onion_keys(&secp_ctx, &path, session_priv); + let onion_packet = construct_onion_packet(onion_payloads, onion_keys, prng_seed, payment_hash) + .map_err(|_| APIError::InvalidRoute { + err: "Route size too large considering onion data".to_owned(), + })?; Ok((onion_packet, htlc_msat, htlc_cltv)) } @@ -3571,7 +3583,7 @@ mod tests { &logger, &build_trampoline_test_path(), &outer_session_priv, - Some(&trampoline_session_priv), + Some(trampoline_session_priv), error_packet, ); assert_eq!( @@ -3584,19 +3596,15 @@ mod tests { // shared secret cryptography sanity tests let session_priv = get_test_session_key(); let path = build_trampoline_test_path(); + let outer_onion_keys = construct_onion_keys(&Secp256k1::new(), &path, &session_priv); + let trampoline_session_priv = compute_trampoline_session_priv(&session_priv); let trampoline_onion_keys = construct_trampoline_onion_keys( &secp_ctx, &path.blinded_tail.as_ref().unwrap(), - &session_priv, + &trampoline_session_priv, ); - let outer_onion_keys = { - let session_priv_hash = Sha256::hash(&session_priv.secret_bytes()).to_byte_array(); - let outer_session_priv = SecretKey::from_slice(&session_priv_hash[..]).unwrap(); - construct_onion_keys(&Secp256k1::new(), &path, &outer_session_priv) - }; - let htlc_source = HTLCSource::OutboundRoute { path, session_priv, diff --git a/lightning/src/ln/our_peer_storage.rs b/lightning/src/ln/our_peer_storage.rs index 430c9f559f9..178637430b1 100644 --- a/lightning/src/ln/our_peer_storage.rs +++ b/lightning/src/ln/our_peer_storage.rs @@ -13,7 +13,9 @@ use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::hashes::{Hash, HashEngine, Hmac, HmacEngine}; +use bitcoin::secp256k1::PublicKey; +use crate::ln::types::ChannelId; use crate::sign::PeerStorageKey; use crate::crypto::chacha20poly1305rfc::ChaCha20Poly1305RFC; @@ -146,6 +148,34 @@ fn derive_nonce(key: &PeerStorageKey, random_bytes: &[u8]) -> [u8; 12] { nonce } +/// [`PeerStorageMonitorHolder`] represents a single channel sent over the wire. +/// This would be used inside [`ChannelManager`] to determine +/// if the user has lost channel states so that we can do something about it. +/// +/// The main idea here is to just enable node to figure out that it has lost some data +/// using peer storage backups. +/// +/// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager +/// +/// TODO(aditya): Write FundRecoverer to use `monitor_bytes` to drop onchain. +pub(crate) struct PeerStorageMonitorHolder { + /// Channel Id of the channel. + pub(crate) channel_id: ChannelId, + /// Node Id of the channel partner. + pub(crate) counterparty_node_id: PublicKey, + /// Minimum seen secret to determine if we have lost state. + pub(crate) min_seen_secret: u64, + /// Whole serialised ChannelMonitor to recover funds. + pub(crate) monitor_bytes: Vec, +} + +impl_writeable_tlv_based!(PeerStorageMonitorHolder, { + (0, channel_id, required), + (2, counterparty_node_id, required), + (4, min_seen_secret, required), + (6, monitor_bytes, required_vec), +}); + #[cfg(test)] mod tests { use crate::ln::our_peer_storage::{derive_nonce, DecryptedOurPeerStorage}; diff --git a/lightning/src/ln/outbound_payment.rs b/lightning/src/ln/outbound_payment.rs index ffc3ee4ae19..476964db889 100644 --- a/lightning/src/ln/outbound_payment.rs +++ b/lightning/src/ln/outbound_payment.rs @@ -20,7 +20,7 @@ use crate::ln::channel_state::ChannelDetails; use crate::ln::channelmanager::{EventCompletionAction, HTLCSource, PaymentId}; use crate::ln::onion_utils; use crate::ln::onion_utils::{DecodedOnionFailure, HTLCFailReason}; -use crate::offers::invoice::Bolt12Invoice; +use crate::offers::invoice::{Bolt12Invoice, DerivedSigningPubkey, InvoiceBuilder}; use crate::offers::invoice_request::InvoiceRequest; use crate::offers::nonce::Nonce; use crate::offers::static_invoice::StaticInvoice; @@ -37,9 +37,6 @@ use crate::util::ser::ReadableArgs; #[cfg(feature = "std")] use crate::util::time::Instant; -#[cfg(async_payments)] -use crate::offers::invoice::{DerivedSigningPubkey, InvoiceBuilder}; - use core::fmt::{self, Display, Formatter}; use core::ops::Deref; use core::sync::atomic::{AtomicBool, Ordering}; @@ -54,12 +51,11 @@ use crate::sync::Mutex; /// [`ChannelManager::timer_tick_occurred`]: crate::ln::channelmanager::ChannelManager::timer_tick_occurred pub(crate) const IDEMPOTENCY_TIMEOUT_TICKS: u8 = 7; -#[cfg(async_payments)] /// The default relative expiration to wait for a pending outbound HTLC to a often-offline /// payee to fulfill. const ASYNC_PAYMENT_TIMEOUT_RELATIVE_EXPIRY: Duration = Duration::from_secs(60 * 60 * 24 * 7); -#[cfg(all(async_payments, test))] +#[cfg(test)] pub(crate) const TEST_ASYNC_PAYMENT_TIMEOUT_RELATIVE_EXPIRY: Duration = ASYNC_PAYMENT_TIMEOUT_RELATIVE_EXPIRY; @@ -637,7 +633,6 @@ pub enum Bolt12PaymentError { UnknownRequiredFeatures, /// The invoice was valid for the corresponding [`PaymentId`], but sending the payment failed. SendingFailed(RetryableSendFailure), - #[cfg(async_payments)] /// Failed to create a blinded path back to ourselves. /// /// We attempted to initiate payment to a [`StaticInvoice`] but failed to create a reply path for @@ -1111,7 +1106,6 @@ impl OutboundPayments { Ok(()) } - #[cfg(async_payments)] #[rustfmt::skip] pub(super) fn static_invoice_received( &self, invoice: &StaticInvoice, payment_id: PaymentId, features: Bolt12InvoiceFeatures, @@ -1201,7 +1195,6 @@ impl OutboundPayments { }; } - #[cfg(async_payments)] #[rustfmt::skip] pub(super) fn send_payment_for_static_invoice< R: Deref, ES: Deref, NS: Deref, NL: Deref, IH, SP, L: Deref diff --git a/lightning/src/ln/payment_tests.rs b/lightning/src/ln/payment_tests.rs index b11294f1158..67c7599a6c0 100644 --- a/lightning/src/ln/payment_tests.rs +++ b/lightning/src/ln/payment_tests.rs @@ -3581,7 +3581,7 @@ fn no_extra_retries_on_back_to_back_fail() { expect_and_process_pending_htlcs(&nodes[1], false); expect_htlc_handling_failed_destinations!( nodes[1].node.get_and_clear_pending_events(), - &[next_hop_failure.clone()] + core::slice::from_ref(&next_hop_failure) ); check_added_monitors(&nodes[1], 1); @@ -3755,7 +3755,7 @@ fn test_simple_partial_retry() { HTLCHandlingFailureType::Forward { node_id: Some(node_c_id), channel_id: chan_2.2 }; expect_htlc_handling_failed_destinations!( nodes[1].node.get_and_clear_pending_events(), - &[next_hop_failure.clone()] + core::slice::from_ref(&next_hop_failure) ); check_added_monitors(&nodes[1], 2); @@ -4247,7 +4247,10 @@ fn do_claim_from_closed_chan(fail_payment: bool) { // We fail the HTLC on the A->B->D path first as it expires 4 blocks earlier. We go ahead // and expire both immediately, though, by connecting another 4 blocks. let reason = HTLCHandlingFailureType::Receive { payment_hash: hash }; - expect_and_process_pending_htlcs_and_htlc_handling_failed(&nodes[3], &[reason.clone()]); + expect_and_process_pending_htlcs_and_htlc_handling_failed( + &nodes[3], + core::slice::from_ref(&reason), + ); connect_blocks(&nodes[3], 4); expect_and_process_pending_htlcs_and_htlc_handling_failed(&nodes[3], &[reason]); @@ -4267,8 +4270,8 @@ fn do_claim_from_closed_chan(fail_payment: bool) { assert_eq!(bs_tx.len(), 1); mine_transaction(&nodes[3], &bs_tx[0]); - check_added_monitors(&nodes[3], 1); check_closed_broadcast(&nodes[3], 1, true); + check_added_monitors(&nodes[3], 1); let reason = ClosureReason::CommitmentTxConfirmed; check_closed_event!(&nodes[3], 1, reason, false, [node_b_id], 1000000); diff --git a/lightning/src/ln/quiescence_tests.rs b/lightning/src/ln/quiescence_tests.rs index 17b6535d95b..c13f9e72645 100644 --- a/lightning/src/ln/quiescence_tests.rs +++ b/lightning/src/ln/quiescence_tests.rs @@ -33,7 +33,7 @@ fn test_quiescence_tie() { assert!(stfu_node_0.initiator && stfu_node_1.initiator); assert!(nodes[0].node.exit_quiescence(&nodes[1].node.get_our_node_id(), &chan_id).unwrap()); - assert!(!nodes[1].node.exit_quiescence(&nodes[0].node.get_our_node_id(), &chan_id).unwrap()); + assert!(nodes[1].node.exit_quiescence(&nodes[0].node.get_our_node_id(), &chan_id).unwrap()); } #[test] @@ -173,7 +173,8 @@ fn allow_shutdown_while_awaiting_quiescence(local_shutdown: bool) { // Now that the state machine is no longer pending, and `closing_signed` is ready to be sent, // make sure we're still not waiting for the quiescence handshake to complete. - local_node.node.exit_quiescence(&remote_node_id, &chan_id).unwrap(); + // Note that we never actually reached full quiescence here. + assert!(!local_node.node.exit_quiescence(&remote_node_id, &chan_id).unwrap()); let _ = get_event_msg!(local_node, MessageSendEvent::SendClosingSigned, remote_node_id); check_added_monitors(local_node, 2); // One for the last revoke_and_ack, another for closing_signed @@ -197,6 +198,7 @@ fn test_quiescence_waits_for_async_signer_and_monitor_update() { let (preimage, payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1]], payment_amount); nodes[1].node.claim_funds(preimage); check_added_monitors(&nodes[1], 1); + expect_payment_claimed!(&nodes[1], payment_hash, payment_amount); let mut update = get_htlc_update_msgs!(&nodes[1], node_id_0); nodes[0].node.handle_update_fulfill_htlc(node_id_1, update.update_fulfill_htlcs.remove(0)); @@ -223,8 +225,6 @@ fn test_quiescence_waits_for_async_signer_and_monitor_update() { nodes[1].enable_channel_signer_op(&node_id_0, &chan_id, SignerOp::ReleaseCommitmentSecret); nodes[1].node.signer_unblocked(Some((node_id_0, chan_id))); - expect_payment_claimed!(&nodes[1], payment_hash, payment_amount); - macro_rules! find_msg { ($events: expr, $msg: ident) => {{ $events @@ -280,8 +280,8 @@ fn test_quiescence_waits_for_async_signer_and_monitor_update() { let stfu = get_event_msg!(&nodes[0], MessageSendEvent::SendStfu, node_id_1); nodes[1].node.handle_stfu(node_id_0, &stfu); - nodes[0].node.exit_quiescence(&node_id_1, &chan_id).unwrap(); - nodes[1].node.exit_quiescence(&node_id_0, &chan_id).unwrap(); + assert!(nodes[0].node.exit_quiescence(&node_id_1, &chan_id).unwrap()); + assert!(nodes[1].node.exit_quiescence(&node_id_0, &chan_id).unwrap()); // After exiting quiescence, we should be able to resume payments from nodes[0]. send_payment(&nodes[0], &[&nodes[1]], payment_amount); @@ -337,8 +337,8 @@ fn test_quiescence_on_final_revoke_and_ack_pending_monitor_update() { panic!(); } - nodes[0].node.exit_quiescence(&node_id_1, &chan_id).unwrap(); - nodes[1].node.exit_quiescence(&node_id_0, &chan_id).unwrap(); + assert!(nodes[0].node.exit_quiescence(&node_id_1, &chan_id).unwrap()); + assert!(nodes[1].node.exit_quiescence(&node_id_0, &chan_id).unwrap()); } #[test] @@ -407,8 +407,8 @@ fn quiescence_updates_go_to_holding_cell(fail_htlc: bool) { let stfu = get_event_msg!(&nodes[0], MessageSendEvent::SendStfu, node_id_1); nodes[1].node.handle_stfu(node_id_0, &stfu); - nodes[0].node.exit_quiescence(&node_id_1, &chan_id).unwrap(); - nodes[1].node.exit_quiescence(&node_id_0, &chan_id).unwrap(); + assert!(nodes[0].node.exit_quiescence(&node_id_1, &chan_id).unwrap()); + assert!(nodes[1].node.exit_quiescence(&node_id_0, &chan_id).unwrap()); // Now that quiescence is over, nodes are allowed to make updates again. nodes[1] will have its // outbound HTLC finally go out, along with the fail/claim of nodes[0]'s payment. @@ -418,14 +418,11 @@ fn quiescence_updates_go_to_holding_cell(fail_htlc: bool) { if fail_htlc { nodes[0].node.handle_update_fail_htlc(node_id_1, &update.update_fail_htlcs[0]); } else { + expect_payment_claimed!(nodes[1], payment_hash2, payment_amount); nodes[0].node.handle_update_fulfill_htlc(node_id_1, update.update_fulfill_htlcs.remove(0)); } commitment_signed_dance!(&nodes[0], &nodes[1], update.commitment_signed, false); - if !fail_htlc { - expect_payment_claimed!(nodes[1], payment_hash2, payment_amount); - } - // The payment from nodes[0] should now be seen as failed/successful. let events = nodes[0].node.get_and_clear_pending_events(); assert_eq!(events.len(), 2); @@ -454,6 +451,7 @@ fn quiescence_updates_go_to_holding_cell(fail_htlc: bool) { if fail_htlc { nodes[1].node.handle_update_fail_htlc(node_id_0, &update.update_fail_htlcs[0]); } else { + expect_payment_claimed!(nodes[0], payment_hash1, payment_amount); nodes[1].node.handle_update_fulfill_htlc(node_id_0, update.update_fulfill_htlcs.remove(0)); } commitment_signed_dance!(&nodes[1], &nodes[0], update.commitment_signed, false); @@ -463,7 +461,6 @@ fn quiescence_updates_go_to_holding_cell(fail_htlc: bool) { let conditions = PaymentFailedConditions::new(); expect_payment_failed_conditions(&nodes[1], payment_hash1, true, conditions); } else { - expect_payment_claimed!(nodes[0], payment_hash1, payment_amount); expect_payment_sent(&nodes[1], payment_preimage1, None, true, true); } } @@ -551,3 +548,112 @@ fn test_quiescence_timeout_while_waiting_for_counterparty_stfu() { }; assert!(nodes[1].node.get_and_clear_pending_msg_events().iter().find_map(f).is_some()); } + +fn do_test_quiescence_during_disconnection(with_pending_claim: bool, propose_disconnected: bool) { + // Test that we'll start trying for quiescence immediately after reconnection if we're waiting + // to do some quiescence-required action. + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + let chan_id = create_announced_chan_between_nodes(&nodes, 0, 1).2; + + let node_a_id = nodes[0].node.get_our_node_id(); + let node_b_id = nodes[1].node.get_our_node_id(); + + // First get both nodes off the starting state so we don't have to deal with channel_ready + // retransmissions on reconect. + send_payment(&nodes[0], &[&nodes[1]], 100_000); + + let (preimage, payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1]], 100_000); + if with_pending_claim { + // Optionally reconnect with pending quiescence while there's some pending messages to + // deliver. + nodes[1].node.claim_funds(preimage); + check_added_monitors(&nodes[1], 1); + expect_payment_claimed!(nodes[1], payment_hash, 100_000); + let _ = get_htlc_update_msgs(&nodes[1], &node_a_id); + } + + if !propose_disconnected { + nodes[1].node.maybe_propose_quiescence(&node_a_id, &chan_id).unwrap(); + } + + nodes[0].node.peer_disconnected(node_b_id); + nodes[1].node.peer_disconnected(node_a_id); + + if propose_disconnected { + nodes[1].node.maybe_propose_quiescence(&node_a_id, &chan_id).unwrap(); + } + + let init_msg = msgs::Init { + features: nodes[1].node.init_features(), + networks: None, + remote_network_address: None, + }; + nodes[0].node.peer_connected(node_b_id, &init_msg, true).unwrap(); + nodes[1].node.peer_connected(node_a_id, &init_msg, true).unwrap(); + + let reestab_a = get_event_msg!(nodes[0], MessageSendEvent::SendChannelReestablish, node_b_id); + let reestab_b = get_event_msg!(nodes[1], MessageSendEvent::SendChannelReestablish, node_a_id); + + nodes[0].node.handle_channel_reestablish(node_b_id, &reestab_b); + get_event_msg!(nodes[0], MessageSendEvent::SendChannelUpdate, node_b_id); + + nodes[1].node.handle_channel_reestablish(node_a_id, &reestab_a); + let mut bs_msgs = nodes[1].node.get_and_clear_pending_msg_events(); + bs_msgs.retain(|msg| !matches!(msg, MessageSendEvent::SendChannelUpdate { .. })); + assert_eq!(bs_msgs.len(), 1, "{bs_msgs:?}"); + let stfu = if with_pending_claim { + // Node B should first re-send its channel update, then try to enter quiescence once that + // completes... + let msg = bs_msgs.pop().unwrap(); + if let MessageSendEvent::UpdateHTLCs { mut updates, .. } = msg { + let fulfill = updates.update_fulfill_htlcs.pop().unwrap(); + nodes[0].node.handle_update_fulfill_htlc(node_b_id, fulfill); + let cs = updates.commitment_signed; + nodes[0].node.handle_commitment_signed_batch_test(node_b_id, &cs); + check_added_monitors(&nodes[0], 1); + + let (raa, cs) = get_revoke_commit_msgs(&nodes[0], &node_b_id); + nodes[1].node.handle_revoke_and_ack(node_a_id, &raa); + check_added_monitors(&nodes[1], 1); + nodes[1].node.handle_commitment_signed_batch_test(node_a_id, &cs); + check_added_monitors(&nodes[1], 1); + + let mut bs_raa_stfu = nodes[1].node.get_and_clear_pending_msg_events(); + assert_eq!(bs_raa_stfu.len(), 2); + if let MessageSendEvent::SendRevokeAndACK { msg, .. } = &bs_raa_stfu[0] { + nodes[0].node.handle_revoke_and_ack(node_b_id, &msg); + expect_payment_sent!(&nodes[0], preimage); + } else { + panic!("Unexpected first message {bs_raa_stfu:?}"); + } + + bs_raa_stfu.pop().unwrap() + } else { + panic!("Unexpected message {msg:?}"); + } + } else { + bs_msgs.pop().unwrap() + }; + if let MessageSendEvent::SendStfu { msg, .. } = stfu { + nodes[0].node.handle_stfu(node_b_id, &msg); + } else { + panic!("Unexpected message {stfu:?}"); + } + + let stfu_resp = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_b_id); + nodes[1].node.handle_stfu(node_a_id, &stfu_resp); + + assert!(nodes[0].node.exit_quiescence(&node_b_id, &chan_id).unwrap()); + assert!(nodes[1].node.exit_quiescence(&node_a_id, &chan_id).unwrap()); +} + +#[test] +fn test_quiescence_during_disconnection() { + do_test_quiescence_during_disconnection(false, false); + do_test_quiescence_during_disconnection(true, false); + do_test_quiescence_during_disconnection(false, true); + do_test_quiescence_during_disconnection(true, true); +} diff --git a/lightning/src/ln/reload_tests.rs b/lightning/src/ln/reload_tests.rs index 5682b3b44b1..bc2cbc64d6d 100644 --- a/lightning/src/ln/reload_tests.rs +++ b/lightning/src/ln/reload_tests.rs @@ -1305,3 +1305,118 @@ fn test_htlc_localremoved_persistence() { let htlc_fail_msg_after_reload = msgs.2.unwrap().update_fail_htlcs[0].clone(); assert_eq!(htlc_fail_msg, htlc_fail_msg_after_reload); } + + + +#[test] +#[cfg(peer_storage)] +fn test_peer_storage() { + let chanmon_cfgs = create_chanmon_cfgs(2); + let (persister, chain_monitor); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let nodes_0_deserialized; + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let node_a_id = nodes[0].node.get_our_node_id(); + let node_b_id = nodes[1].node.get_our_node_id(); + + let (_, _, cid, _) = create_announced_chan_between_nodes(&nodes, 0, 1); + send_payment(&nodes[0], &[&nodes[1]], 1000); + let nodes_0_serialized = nodes[0].node.encode(); + let old_state_monitor = get_monitor!(nodes[0], cid).encode(); + send_payment(&nodes[0], &[&nodes[1]], 10000); + send_payment(&nodes[0], &[&nodes[1]], 9999); + + // Update peer storage with latest commitment txns + connect_blocks(&nodes[0], 1); + connect_blocks(&nodes[0], 1); + + let peer_storage_msg_events_node0 = + nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_msg_events(); + let peer_storage_msg_events_node1 = + nodes[1].chain_monitor.chain_monitor.get_and_clear_pending_msg_events(); + assert_ne!(peer_storage_msg_events_node0.len(), 0); + assert_ne!(peer_storage_msg_events_node1.len(), 0); + + for ps_msg in peer_storage_msg_events_node0 { + match ps_msg { + MessageSendEvent::SendPeerStorage { ref node_id, ref msg } => { + assert_eq!(*node_id, node_b_id); + nodes[1].node.handle_peer_storage(node_a_id, msg.clone()); + }, + _ => panic!("Unexpected event"), + } + } + + for ps_msg in peer_storage_msg_events_node1 { + match ps_msg { + MessageSendEvent::SendPeerStorage { ref node_id, ref msg } => { + assert_eq!(*node_id, node_a_id); + nodes[0].node.handle_peer_storage(node_b_id, msg.clone()); + }, + _ => panic!("Unexpected event"), + } + } + + nodes[0].node.peer_disconnected(node_b_id); + nodes[1].node.peer_disconnected(node_a_id); + + // Reload Node! + // TODO: Handle the case where we've completely forgotten about an active channel. + reload_node!( + nodes[0], + test_default_channel_config(), + &nodes_0_serialized, + &[&old_state_monitor[..]], + persister, + chain_monitor, + nodes_0_deserialized + ); + + let init_msg = msgs::Init { + features: nodes[1].node.init_features(), + networks: None, + remote_network_address: None, + }; + + nodes[0].node.peer_connected(node_b_id, &init_msg, true).unwrap(); + nodes[1].node.peer_connected(node_a_id, &init_msg, true).unwrap(); + + let node_1_events = nodes[1].node.get_and_clear_pending_msg_events(); + assert_eq!(node_1_events.len(), 2); + + let node_0_events = nodes[0].node.get_and_clear_pending_msg_events(); + assert_eq!(node_0_events.len(), 1); + + match node_0_events[0] { + MessageSendEvent::SendChannelReestablish { ref node_id, .. } => { + assert_eq!(*node_id, node_b_id); + // nodes[0] would send a stale channel reestablish, so there's no need to handle this. + }, + _ => panic!("Unexpected event"), + } + + if let MessageSendEvent::SendPeerStorageRetrieval { node_id, msg } = &node_1_events[0] { + assert_eq!(*node_id, node_a_id); + // Should Panic here! + let res = std::panic::catch_unwind(|| { + nodes[0].node.handle_peer_storage_retrieval(node_b_id, msg.clone()); + }); + assert!(res.is_err()); + } else { + panic!("Unexpected event {node_1_events:?}") + } + + if let MessageSendEvent::SendChannelReestablish { .. } = &node_1_events[1] { + // After the `peer_storage_retreival` message would come a `channel_reestablish` (which + // would also cause nodes[0] to panic) but it already went down due to lost state so + // there's nothing to deliver. + } else { + panic!("Unexpected event {node_1_events:?}") + } + // When we panic'd, we expect to panic on `Drop`. + let res = std::panic::catch_unwind(|| drop(nodes)); + assert!(res.is_err()); +} + diff --git a/lightning/src/ln/reorg_tests.rs b/lightning/src/ln/reorg_tests.rs index cbe0d8250ed..d85c95c0a1d 100644 --- a/lightning/src/ln/reorg_tests.rs +++ b/lightning/src/ln/reorg_tests.rs @@ -78,8 +78,8 @@ fn do_test_onchain_htlc_reorg(local_commitment: bool, claim: bool) { // Give node 2 node 1's transactions and get its response (claiming the HTLC instead). connect_block(&nodes[2], &create_dummy_block(nodes[2].best_block_hash(), 42, node_1_commitment_txn.clone())); - check_added_monitors!(nodes[2], 1); check_closed_broadcast!(nodes[2], true); // We should get a BroadcastChannelUpdate (and *only* a BroadcstChannelUpdate) + check_added_monitors!(nodes[2], 1); check_closed_event!(nodes[2], 1, ClosureReason::CommitmentTxConfirmed, [nodes[1].node.get_our_node_id()], 100000); let node_2_commitment_txn = nodes[2].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0); assert_eq!(node_2_commitment_txn.len(), 1); // ChannelMonitor: 1 offered HTLC-Claim @@ -112,8 +112,8 @@ fn do_test_onchain_htlc_reorg(local_commitment: bool, claim: bool) { // ...but return node 2's commitment tx (and claim) in case claim is set and we're preparing to reorg vec![node_2_commitment_txn.pop().unwrap()] }; - check_added_monitors!(nodes[1], 1); check_closed_broadcast!(nodes[1], true); // We should get a BroadcastChannelUpdate (and *only* a BroadcstChannelUpdate) + check_added_monitors!(nodes[1], 1); check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [nodes[2].node.get_our_node_id()], 100000); // Connect ANTI_REORG_DELAY - 2 blocks, giving us a confirmation count of ANTI_REORG_DELAY - 1. connect_blocks(&nodes[1], ANTI_REORG_DELAY - 2); @@ -210,9 +210,9 @@ fn test_counterparty_revoked_reorg() { // Now mine A's old commitment transaction, which should close the channel, but take no action // on any of the HTLCs, at least until we get six confirmations (which we won't get). mine_transaction(&nodes[1], &revoked_local_txn[0]); + check_closed_broadcast!(nodes[1], true); check_added_monitors!(nodes[1], 1); check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [nodes[0].node.get_our_node_id()], 1000000); - check_closed_broadcast!(nodes[1], true); // Connect up to one block before the revoked transaction would be considered final, then do a // reorg that disconnects the full chain and goes up to the height at which the revoked @@ -487,7 +487,15 @@ fn test_set_outpoints_partial_claiming() { // Connect blocks on node B connect_blocks(&nodes[1], TEST_FINAL_CLTV + LATENCY_GRACE_PERIOD_BLOCKS + 1); check_closed_broadcast!(nodes[1], true); - check_closed_event!(nodes[1], 1, ClosureReason::HTLCsTimedOut, [nodes[0].node.get_our_node_id()], 1000000); + check_closed_events(&nodes[1], &[ExpectedCloseEvent { + channel_capacity_sats: Some(1_000_000), + channel_id: Some(chan.2), + counterparty_node_id: Some(nodes[0].node.get_our_node_id()), + discard_funding: false, + reason: None, // Could be due to either HTLC timing out, so don't bother checking + channel_funding_txo: None, + user_channel_id: None, + }]); check_added_monitors!(nodes[1], 1); // Verify node B broadcast 2 HTLC-timeout txn let partial_claim_tx = { @@ -561,12 +569,12 @@ fn do_test_to_remote_after_local_detection(style: ConnectStyle) { mine_transaction(&nodes[0], &remote_txn_a[0]); mine_transaction(&nodes[1], &remote_txn_a[0]); - assert!(nodes[0].node.list_channels().is_empty()); check_closed_broadcast!(nodes[0], true); + assert!(nodes[0].node.list_channels().is_empty()); check_added_monitors!(nodes[0], 1); check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed, [nodes[1].node.get_our_node_id()], 1000000); - assert!(nodes[1].node.list_channels().is_empty()); check_closed_broadcast!(nodes[1], true); + assert!(nodes[1].node.list_channels().is_empty()); check_added_monitors!(nodes[1], 1); check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [nodes[0].node.get_our_node_id()], 1000000); @@ -818,7 +826,7 @@ fn do_test_retries_own_commitment_broadcast_after_reorg(anchors: bool, revoked_c let (_, _, chan_id, funding_tx) = create_announced_chan_between_nodes(&nodes, 0, 1); // Route a payment so we have an HTLC to claim as well. - let _ = route_payment(&nodes[0], &[&nodes[1]], 1_000_000); + let (_, payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1]], 1_000_000); if revoked_counterparty_commitment { // Trigger a fee update such that we advance the state. We will have B broadcast its state @@ -843,7 +851,11 @@ fn do_test_retries_own_commitment_broadcast_after_reorg(anchors: bool, revoked_c connect_blocks(&nodes[0], TEST_FINAL_CLTV + LATENCY_GRACE_PERIOD_BLOCKS + 1); check_closed_broadcast(&nodes[0], 1, true); check_added_monitors(&nodes[0], 1); - check_closed_event(&nodes[0], 1, ClosureReason::HTLCsTimedOut, false, &[nodes[1].node.get_our_node_id()], 100_000); + let reason = ClosureReason::HTLCsTimedOut { payment_hash: Some(payment_hash) }; + check_closed_event(&nodes[0], 1, reason, false, &[nodes[1].node.get_our_node_id()], 100_000); + if anchors { + handle_bump_close_event(&nodes[0]); + } { let mut txn = nodes[0].tx_broadcaster.txn_broadcast(); @@ -870,6 +882,9 @@ fn do_test_retries_own_commitment_broadcast_after_reorg(anchors: bool, revoked_c check_added_monitors(&nodes[1], 1); let reason = ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(true), message }; check_closed_event(&nodes[1], 1, reason, false, &[nodes[0].node.get_our_node_id()], 100_000); + if anchors { + handle_bump_close_event(&nodes[1]); + } let commitment_b = { let mut txn = nodes[1].tx_broadcaster.txn_broadcast(); @@ -882,13 +897,23 @@ fn do_test_retries_own_commitment_broadcast_after_reorg(anchors: bool, revoked_c // Confirm B's commitment, A should now broadcast an HTLC timeout for commitment B. mine_transaction(&nodes[0], &commitment_b); { - let mut txn = nodes[0].tx_broadcaster.txn_broadcast(); if nodes[0].connect_style.borrow().updates_best_block_first() { // `commitment_a` is rebroadcast because the best block was updated prior to seeing // `commitment_b`. - assert_eq!(txn.len(), 2); - check_spends!(txn.last().unwrap(), commitment_b); + if anchors { + handle_bump_close_event(&nodes[0]); + let mut txn = nodes[0].tx_broadcaster.txn_broadcast(); + assert_eq!(txn.len(), 3); + check_spends!(txn[0], commitment_b); + check_spends!(txn[1], funding_tx); + check_spends!(txn[2], txn[1]); // Anchor output spend transaction. + } else { + let mut txn = nodes[0].tx_broadcaster.txn_broadcast(); + assert_eq!(txn.len(), 2); + check_spends!(txn.last().unwrap(), commitment_b); + } } else { + let mut txn = nodes[0].tx_broadcaster.txn_broadcast(); assert_eq!(txn.len(), 1); check_spends!(txn[0], commitment_b); } @@ -898,11 +923,15 @@ fn do_test_retries_own_commitment_broadcast_after_reorg(anchors: bool, revoked_c // blocks, one to get us back to the original height, and another to retry our pending claims. disconnect_blocks(&nodes[0], 1); connect_blocks(&nodes[0], 2); + if anchors { + handle_bump_close_event(&nodes[0]); + } { let mut txn = nodes[0].tx_broadcaster.unique_txn_broadcast(); if anchors { - assert_eq!(txn.len(), 1); + assert_eq!(txn.len(), 2); check_spends!(txn[0], funding_tx); + check_spends!(txn[1], txn[0]); // Anchor output spend. } else { assert_eq!(txn.len(), 2); check_spends!(txn[0], txn[1]); // HTLC timeout A @@ -977,6 +1006,7 @@ fn do_test_split_htlc_expiry_tracking(use_third_htlc: bool, reorg_out: bool) { let message = "Channel force-closed".to_owned(); let reason = ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(true), message }; check_closed_event(&nodes[1], 1, reason, false, &[node_a_id], 10_000_000); + handle_bump_close_event(&nodes[1]); let mut txn = nodes[1].tx_broadcaster.txn_broadcast(); assert_eq!(txn.len(), 1); @@ -990,19 +1020,13 @@ fn do_test_split_htlc_expiry_tracking(use_third_htlc: bool, reorg_out: bool) { check_added_monitors(&nodes[0], 1); mine_transaction(&nodes[1], &commitment_tx); - let mut bump_events = nodes[1].chain_monitor.chain_monitor.get_and_clear_pending_events(); - assert_eq!(bump_events.len(), 1); - match bump_events.pop().unwrap() { - Event::BumpTransaction(bump_event) => { - nodes[1].bump_tx_handler.handle_event(&bump_event); - }, - ev => panic!("Unexpected event {ev:?}"), - } + handle_bump_events(&nodes[1], nodes[1].connect_style.borrow().updates_best_block_first(), 1); let mut txn = nodes[1].tx_broadcaster.txn_broadcast(); if nodes[1].connect_style.borrow().updates_best_block_first() { - assert_eq!(txn.len(), 2, "{txn:?}"); + assert_eq!(txn.len(), 3, "{txn:?}"); check_spends!(txn[0], funding_tx); + check_spends!(txn[1], txn[0]); // Anchor output spend. } else { assert_eq!(txn.len(), 1, "{txn:?}"); } diff --git a/lightning/src/ln/shutdown_tests.rs b/lightning/src/ln/shutdown_tests.rs index 6e0845dccb2..054842abd9b 100644 --- a/lightning/src/ln/shutdown_tests.rs +++ b/lightning/src/ln/shutdown_tests.rs @@ -12,7 +12,7 @@ use crate::chain::transaction::OutPoint; use crate::chain::ChannelMonitorUpdateStatus; -use crate::events::{ClosureReason, Event, HTLCHandlingFailureType}; +use crate::events::{ClosureReason, Event, HTLCHandlingFailureReason, HTLCHandlingFailureType}; use crate::ln::channel_state::{ChannelDetails, ChannelShutdownState}; use crate::ln::channelmanager::{self, PaymentId, RecipientOnionFields, Retry}; use crate::ln::msgs; @@ -370,11 +370,11 @@ fn expect_channel_shutdown_state_with_force_closure() { assert_eq!(node_txn.len(), 1); check_spends!(node_txn[0], chan_1.3); mine_transaction(&nodes[0], &node_txn[0]); + check_closed_broadcast!(nodes[0], true); check_added_monitors!(nodes[0], 1); assert!(nodes[0].node.list_channels().is_empty()); assert!(nodes[1].node.list_channels().is_empty()); - check_closed_broadcast!(nodes[0], true); check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed, [node_b_id], 100000); let reason_b = ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(true), message }; check_closed_event!(nodes[1], 1, reason_b, [node_a_id], 100000); @@ -1825,3 +1825,112 @@ fn test_force_closure_on_low_stale_fee() { }; check_closed_events(&nodes[1], &[ExpectedCloseEvent::from_id_reason(chan_id, false, reason)]); } + +#[test] +fn test_pending_htlcs_arent_lost_on_mon_delay() { + // Test that HTLCs which were queued to be sent to peers but which never made it out due to a + // pending, not-completed `ChannelMonitorUpdate` which got dropped with the `Channel`. This is + // only possible when the `ChannelMonitorUpdate` is blocked, as otherwise it will be queued in + // the `ChannelManager` and go out before any closure update. + let chanmon_cfgs = create_chanmon_cfgs(3); + let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); + + let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]); + let mut nodes = create_network(3, &node_cfgs, &node_chanmgrs); + + let node_a_id = nodes[0].node.get_our_node_id(); + let node_b_id = nodes[1].node.get_our_node_id(); + let node_c_id = nodes[2].node.get_our_node_id(); + + create_announced_chan_between_nodes(&nodes, 0, 1); + let (_, _, chan_id_bc, ..) = create_announced_chan_between_nodes(&nodes, 1, 2); + + // First route a payment from node B to C, which will allow us to block `ChannelMonitorUpdate`s + // by not processing the `PaymentSent` event upon claim. + let (preimage_a, payment_hash_a, ..) = route_payment(&nodes[1], &[&nodes[2]], 500_000); + + nodes[2].node.claim_funds(preimage_a); + check_added_monitors(&nodes[2], 1); + expect_payment_claimed!(nodes[2], payment_hash_a, 500_000); + + let mut claim = get_htlc_update_msgs(&nodes[2], &node_b_id); + nodes[1].node.handle_update_fulfill_htlc(node_c_id, claim.update_fulfill_htlcs.pop().unwrap()); + + // Now, while sitting on the `PaymentSent` event, move the B <-> C channel forward until B is + // just waiting on C's last RAA. + nodes[1].node.handle_commitment_signed_batch_test(node_c_id, &claim.commitment_signed); + check_added_monitors(&nodes[1], 1); + + let (raa, cs) = get_revoke_commit_msgs(&nodes[1], &node_c_id); + + nodes[2].node.handle_revoke_and_ack(node_b_id, &raa); + check_added_monitors(&nodes[2], 1); + nodes[2].node.handle_commitment_signed_batch_test(node_b_id, &cs); + check_added_monitors(&nodes[2], 1); + + let cs_last_raa = get_event_msg!(nodes[2], MessageSendEvent::SendRevokeAndACK, node_b_id); + + // Now, while still sitting on the `PaymentSent` event, send an HTLC which will be relayed the + // moment `cs_last_raa` is received by B. + let (route_b, payment_hash_b, _preimage, payment_secret_b) = + get_route_and_payment_hash!(&nodes[0], nodes[2], 900_000); + let onion = RecipientOnionFields::secret_only(payment_secret_b); + let id = PaymentId(payment_hash_b.0); + nodes[0].node.send_payment_with_route(route_b, payment_hash_b, onion, id).unwrap(); + check_added_monitors(&nodes[0], 1); + let as_send = get_htlc_update_msgs(&nodes[0], &node_b_id); + + nodes[1].node.handle_update_add_htlc(node_a_id, &as_send.update_add_htlcs[0]); + commitment_signed_dance!(nodes[1], nodes[0], as_send.commitment_signed, false); + + // Place the HTLC in the B <-> C channel holding cell for release upon RAA and finally deliver + // `cs_last_raa`. Because we're still waiting to handle the `PaymentSent` event, the + // `ChannelMonitorUpdate` and update messages will be held. + nodes[1].node.process_pending_htlc_forwards(); + assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty()); + check_added_monitors(&nodes[1], 0); + + nodes[1].node.handle_revoke_and_ack(node_c_id, &cs_last_raa); + check_added_monitors(&nodes[1], 0); + assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty()); + + // Now force-close the B <-> C channel, making sure that we (finally) see the `PaymentSent`, as + // well as the channel closure and, importantly, the HTLC fail-back to A. + let message = "".to_string(); + nodes[1].node.force_close_broadcasting_latest_txn(&chan_id_bc, &node_c_id, message).unwrap(); + check_closed_broadcast(&nodes[1], 1, true); + check_added_monitors(&nodes[1], 1); + + let events = nodes[1].node.get_and_clear_pending_events(); + assert_eq!(events.len(), 3, "{events:?}"); + assert!(events.iter().any(|ev| { + if let Event::PaymentSent { payment_preimage: ev_preimage, .. } = ev { + assert_eq!(*ev_preimage, preimage_a); + true + } else { + false + } + })); + assert!(events.iter().any(|ev| matches!(ev, Event::ChannelClosed { .. }))); + assert!(events.iter().any(|ev| { + if let Event::HTLCHandlingFailed { failure_type, failure_reason, .. } = ev { + assert!(matches!(failure_type, HTLCHandlingFailureType::Forward { .. })); + if let Some(HTLCHandlingFailureReason::Local { reason }) = failure_reason { + assert_eq!(*reason, LocalHTLCFailureReason::ChannelClosed); + } else { + panic!("Unexpected failure reason {failure_reason:?}"); + } + true + } else { + false + } + })); + + nodes[1].node.process_pending_htlc_forwards(); + check_added_monitors(&nodes[1], 1); + + let failures = get_htlc_update_msgs(&nodes[1], &node_a_id); + nodes[0].node.handle_update_fail_htlc(node_b_id, &failures.update_fail_htlcs[0]); + commitment_signed_dance!(nodes[0], nodes[1], failures.commitment_signed, false); + expect_payment_failed!(nodes[0], payment_hash_b, false); +} diff --git a/lightning/src/ln/splicing_tests.rs b/lightning/src/ln/splicing_tests.rs index a88a5f76c7e..2445a2d5fc2 100644 --- a/lightning/src/ln/splicing_tests.rs +++ b/lightning/src/ln/splicing_tests.rs @@ -8,9 +8,12 @@ // licenses. use crate::ln::functional_test_utils::*; +use crate::ln::funding::SpliceContribution; use crate::ln::msgs::{BaseMessageHandler, ChannelMessageHandler, MessageSendEvent}; use crate::util::errors::APIError; +use bitcoin::Amount; + /// Splicing test, simple splice-in flow. Starts with opening a V1 channel first. /// Builds on test_channel_open_simple() #[test] @@ -66,15 +69,20 @@ fn test_v1_splice_in() { &initiator_node, &[extra_splice_funding_input_sats], ); + + let contribution = SpliceContribution::SpliceIn { + value: Amount::from_sat(splice_in_sats), + inputs: funding_inputs, + change_script: None, + }; + // Initiate splice-in let _res = initiator_node .node .splice_channel( &channel_id, &acceptor_node.node.get_our_node_id(), - splice_in_sats as i64, - funding_inputs, - None, // change_script + contribution, funding_feerate_per_kw, None, // locktime ) @@ -154,7 +162,7 @@ fn test_v1_splice_in() { ); } else { // Input is the extra input - let prevtx_value = tx_add_input_msg.prevtx.as_ref().unwrap().as_transaction().output + let prevtx_value = tx_add_input_msg.prevtx.as_ref().unwrap().output [tx_add_input_msg.prevtx_out as usize] .value .to_sat(); @@ -182,7 +190,7 @@ fn test_v1_splice_in() { ); if !inputs_seen_in_reverse { // Input is the extra input - let prevtx_value = tx_add_input2_msg.prevtx.as_ref().unwrap().as_transaction().output + let prevtx_value = tx_add_input2_msg.prevtx.as_ref().unwrap().output [tx_add_input2_msg.prevtx_out as usize] .value .to_sat(); @@ -274,29 +282,7 @@ fn test_v1_splice_in() { _ => panic!("Unexpected event {:?}", events[1]), } - // TODO(splicing): Continue with commitment flow, new tx confirmation - - // === Close channel, cooperatively - initiator_node.node.close_channel(&channel_id, &acceptor_node.node.get_our_node_id()).unwrap(); - let node0_shutdown_message = get_event_msg!( - initiator_node, - MessageSendEvent::SendShutdown, - acceptor_node.node.get_our_node_id() - ); - acceptor_node - .node - .handle_shutdown(initiator_node.node.get_our_node_id(), &node0_shutdown_message); - let nodes_1_shutdown = get_event_msg!( - acceptor_node, - MessageSendEvent::SendShutdown, - initiator_node.node.get_our_node_id() - ); - initiator_node.node.handle_shutdown(acceptor_node.node.get_our_node_id(), &nodes_1_shutdown); - let _ = get_event_msg!( - initiator_node, - MessageSendEvent::SendClosingSigned, - acceptor_node.node.get_our_node_id() - ); + // TODO(splicing): Continue with commitment flow, new tx confirmation, and shutdown } #[test] @@ -317,19 +303,23 @@ fn test_v1_splice_in_negative_insufficient_inputs() { let funding_inputs = create_dual_funding_utxos_with_prev_txs(&nodes[0], &[extra_splice_funding_input_sats]); + let contribution = SpliceContribution::SpliceIn { + value: Amount::from_sat(splice_in_sats), + inputs: funding_inputs, + change_script: None, + }; + // Initiate splice-in, with insufficient input contribution let res = nodes[0].node.splice_channel( &channel_id, &nodes[1].node.get_our_node_id(), - splice_in_sats as i64, - funding_inputs, - None, // change_script + contribution, 1024, // funding_feerate_per_kw, None, // locktime ); match res { Err(APIError::APIMisuseError { err }) => { - assert!(err.contains("Insufficient inputs for splicing")) + assert!(err.contains("Need more inputs")) }, _ => panic!("Wrong error {:?}", res.err().unwrap()), } diff --git a/lightning/src/offers/async_receive_offer_cache.rs b/lightning/src/offers/async_receive_offer_cache.rs index 3a7857ffd0f..8c1887ad139 100644 --- a/lightning/src/offers/async_receive_offer_cache.rs +++ b/lightning/src/offers/async_receive_offer_cache.rs @@ -11,7 +11,7 @@ //! server as an async recipient. The static invoice server will serve the resulting invoices to //! payers on our behalf when we're offline. -use crate::blinded_path::message::BlindedMessagePath; +use crate::blinded_path::message::{AsyncPaymentsContext, BlindedMessagePath}; use crate::io; use crate::io::Read; use crate::ln::msgs::DecodeError; @@ -22,24 +22,24 @@ use crate::prelude::*; use crate::util::ser::{Readable, Writeable, Writer}; use core::time::Duration; -#[cfg(async_payments)] -use crate::blinded_path::message::AsyncPaymentsContext; - /// The status of this offer in the cache. #[derive(Clone, PartialEq)] enum OfferStatus { /// This offer has been returned to the user from the cache, so it needs to be stored until it /// expires and its invoice needs to be kept updated. - Used, + Used { + /// The creation time of the invoice that was last confirmed as persisted by the server. Useful + /// to know when the invoice needs refreshing. + invoice_created_at: Duration, + }, /// This offer has not yet been returned to the user, and is safe to replace to ensure we always /// have a maximally fresh offer. We always want to have at least 1 offer in this state, /// preferably a few so we can respond to user requests for new offers without returning the same /// one multiple times. Returning a new offer each time is better for privacy. Ready { - /// If this offer's invoice has been persisted for some time, it's safe to replace to ensure we - /// always have the freshest possible offer available when the user goes to pull an offer from - /// the cache. - invoice_confirmed_persisted_at: Duration, + /// The creation time of the invoice that was last confirmed as persisted by the server. Useful + /// to know when the invoice needs refreshing. + invoice_created_at: Duration, }, /// This offer's invoice is not yet confirmed as persisted by the static invoice server, so it is /// not yet ready to receive payments. @@ -49,6 +49,9 @@ enum OfferStatus { #[derive(Clone)] struct AsyncReceiveOffer { offer: Offer, + /// The time as duration since the Unix epoch at which this offer was created. Useful when + /// refreshing unused offers. + created_at: Duration, /// Whether this offer is used, ready for use, or pending invoice persistence with the static /// invoice server. status: OfferStatus, @@ -61,10 +64,24 @@ struct AsyncReceiveOffer { update_static_invoice_path: Responder, } +impl AsyncReceiveOffer { + /// An offer needs to be refreshed if it is unused and has been cached longer than + /// `OFFER_REFRESH_THRESHOLD`. + fn needs_refresh(&self, duration_since_epoch: Duration) -> bool { + let awhile_ago = duration_since_epoch.saturating_sub(OFFER_REFRESH_THRESHOLD); + match self.status { + OfferStatus::Ready { .. } => self.created_at < awhile_ago, + _ => false, + } + } +} + impl_writeable_tlv_based_enum!(OfferStatus, - (0, Used) => {}, + (0, Used) => { + (0, invoice_created_at, required), + }, (1, Ready) => { - (0, invoice_confirmed_persisted_at, required), + (0, invoice_created_at, required), }, (2, Pending) => {}, ); @@ -74,6 +91,7 @@ impl_writeable_tlv_based!(AsyncReceiveOffer, { (2, offer_nonce, required), (4, status, required), (6, update_static_invoice_path, required), + (8, created_at, required), }); /// If we are an often-offline recipient, we'll want to interactively build offers and static @@ -143,8 +161,8 @@ impl AsyncReceiveOfferCache { } } - pub(super) fn paths_to_static_invoice_server(&self) -> Vec { - self.paths_to_static_invoice_server.clone() + pub(super) fn paths_to_static_invoice_server(&self) -> &[BlindedMessagePath] { + &self.paths_to_static_invoice_server[..] } /// Sets the [`BlindedMessagePath`]s that we will use as an async recipient to interactively build @@ -152,8 +170,7 @@ impl AsyncReceiveOfferCache { /// on our behalf when we're offline. /// /// [`StaticInvoice`]: crate::offers::static_invoice::StaticInvoice - #[cfg(async_payments)] - pub fn set_paths_to_static_invoice_server( + pub(crate) fn set_paths_to_static_invoice_server( &mut self, paths_to_static_invoice_server: Vec, ) -> Result<(), ()> { if paths_to_static_invoice_server.is_empty() { @@ -172,66 +189,66 @@ impl AsyncReceiveOfferCache { // The target number of offers we want to have cached at any given time, to mitigate too much // reuse of the same offer while also limiting the amount of space our offers take up on the // server's end. -#[cfg(async_payments)] const MAX_CACHED_OFFERS_TARGET: usize = 10; // The max number of times we'll attempt to request offer paths per timer tick. -#[cfg(async_payments)] const MAX_UPDATE_ATTEMPTS: u8 = 3; -// If we have an offer that is replaceable and its invoice was confirmed as persisted more than 2 -// hours ago, we can go ahead and refresh it because we always want to have the freshest offer -// possible when a user goes to retrieve a cached offer. +// If we have an offer that is replaceable and is more than 2 hours old, we can go ahead and refresh +// it because we always want to have the freshest offer possible when a user goes to retrieve a +// cached offer. // // We avoid replacing unused offers too quickly -- this prevents the case where we send multiple // invoices from different offers competing for the same slot to the server, messages are received // delayed or out-of-order, and we end up providing an offer to the user that the server just // deleted and replaced. -#[cfg(async_payments)] const OFFER_REFRESH_THRESHOLD: Duration = Duration::from_secs(2 * 60 * 60); +/// Invoices stored with the static invoice server may become stale due to outdated channel and fee +/// info, so they should be updated regularly. +const INVOICE_REFRESH_THRESHOLD: Duration = Duration::from_secs(2 * 60 * 60); + // Require offer paths that we receive to last at least 3 months. -#[cfg(async_payments)] const MIN_OFFER_PATHS_RELATIVE_EXPIRY_SECS: u64 = 3 * 30 * 24 * 60 * 60; -#[cfg(all(test, async_payments))] +#[cfg(test)] pub(crate) const TEST_MAX_CACHED_OFFERS_TARGET: usize = MAX_CACHED_OFFERS_TARGET; -#[cfg(all(test, async_payments))] +#[cfg(test)] pub(crate) const TEST_MAX_UPDATE_ATTEMPTS: u8 = MAX_UPDATE_ATTEMPTS; -#[cfg(all(test, async_payments))] +#[cfg(test)] pub(crate) const TEST_OFFER_REFRESH_THRESHOLD: Duration = OFFER_REFRESH_THRESHOLD; -#[cfg(all(test, async_payments))] +#[cfg(test)] +pub(crate) const TEST_INVOICE_REFRESH_THRESHOLD: Duration = INVOICE_REFRESH_THRESHOLD; +#[cfg(test)] pub(crate) const TEST_MIN_OFFER_PATHS_RELATIVE_EXPIRY_SECS: u64 = MIN_OFFER_PATHS_RELATIVE_EXPIRY_SECS; -#[cfg(async_payments)] impl AsyncReceiveOfferCache { /// Retrieve a cached [`Offer`] for receiving async payments as an often-offline recipient, as /// well as returning a bool indicating whether the cache needs to be re-persisted. /// // We need to re-persist the cache if a fresh offer was just marked as used to ensure we continue // to keep this offer's invoice updated and don't replace it with the server. - pub fn get_async_receive_offer( + pub(crate) fn get_async_receive_offer( &mut self, duration_since_epoch: Duration, ) -> Result<(Offer, bool), ()> { self.prune_expired_offers(duration_since_epoch, false); - // Find the freshest unused offer, where "freshness" is based on when the invoice was confirmed - // persisted by the server. See `OfferStatus::Ready`. + // Find the freshest unused offer. See `OfferStatus::Ready`. let newest_unused_offer_opt = self - .unused_offers() - .max_by(|(_, _, persisted_at_a), (_, _, persisted_at_b)| { - persisted_at_a.cmp(&persisted_at_b) - }) - .map(|(idx, offer, _)| (idx, offer.offer.clone())); - if let Some((idx, newest_ready_offer)) = newest_unused_offer_opt { - self.offers[idx].as_mut().map(|offer| offer.status = OfferStatus::Used); + .unused_ready_offers() + .max_by(|(_, offer_a, _), (_, offer_b, _)| offer_a.created_at.cmp(&offer_b.created_at)) + .map(|(idx, offer, invoice_created_at)| (idx, offer.offer.clone(), invoice_created_at)); + if let Some((idx, newest_ready_offer, invoice_created_at)) = newest_unused_offer_opt { + self.offers[idx] + .as_mut() + .map(|offer| offer.status = OfferStatus::Used { invoice_created_at }); return Ok((newest_ready_offer, true)); } // If no unused offers are available, return the used offer with the latest absolute expiry self.offers_with_idx() - .filter(|(_, offer)| matches!(offer.status, OfferStatus::Used)) + .filter(|(_, offer)| matches!(offer.status, OfferStatus::Used { .. })) .max_by(|a, b| { let abs_expiry_a = a.1.offer.absolute_expiry().unwrap_or(Duration::MAX); let abs_expiry_b = b.1.offer.absolute_expiry().unwrap_or(Duration::MAX); @@ -241,10 +258,11 @@ impl AsyncReceiveOfferCache { .ok_or(()) } - /// Remove expired offers from the cache, returning whether new offers are needed. + /// Remove expired offers from the cache, returning the first slot number in the cache that needs + /// a new offer, if any exist. pub(super) fn prune_expired_offers( &mut self, duration_since_epoch: Duration, force_reset_request_attempts: bool, - ) -> bool { + ) -> Option { // Remove expired offers from the cache. let mut offer_was_removed = false; for offer_opt in self.offers.iter_mut() { @@ -263,17 +281,23 @@ impl AsyncReceiveOfferCache { self.reset_offer_paths_request_attempts() } - self.needs_new_offer_idx(duration_since_epoch).is_some() - && self.offer_paths_request_attempts < MAX_UPDATE_ATTEMPTS + if self.offer_paths_request_attempts >= MAX_UPDATE_ATTEMPTS { + return None; + } + + self.needs_new_offer_idx(duration_since_epoch).and_then(|idx| { + debug_assert!(idx < MAX_CACHED_OFFERS_TARGET); + idx.try_into().ok() + }) } /// Returns whether the new paths we've just received from the static invoice server should be used /// to build a new offer. pub(super) fn should_build_offer_with_paths( &self, offer_paths: &[BlindedMessagePath], offer_paths_absolute_expiry_secs: Option, - duration_since_epoch: Duration, + slot: u16, duration_since_epoch: Duration, ) -> bool { - if self.needs_new_offer_idx(duration_since_epoch).is_none() { + if !self.slot_needs_offer(slot, duration_since_epoch) { return false; } @@ -295,46 +319,62 @@ impl AsyncReceiveOfferCache { /// until it succeeds, see [`AsyncReceiveOfferCache`] docs. pub(super) fn cache_pending_offer( &mut self, offer: Offer, offer_paths_absolute_expiry_secs: Option, offer_nonce: Nonce, - update_static_invoice_path: Responder, duration_since_epoch: Duration, - ) -> Result { + update_static_invoice_path: Responder, duration_since_epoch: Duration, slot: u16, + ) -> Result<(), ()> { self.prune_expired_offers(duration_since_epoch, false); if !self.should_build_offer_with_paths( offer.paths(), offer_paths_absolute_expiry_secs, + slot, duration_since_epoch, ) { return Err(()); } - let idx = match self.needs_new_offer_idx(duration_since_epoch) { - Some(idx) => idx, - None => return Err(()), - }; - - match self.offers.get_mut(idx) { - Some(offer_opt) => { - *offer_opt = Some(AsyncReceiveOffer { + match self.offers.get_mut(slot as usize) { + Some(slot) => { + *slot = Some(AsyncReceiveOffer { offer, + created_at: duration_since_epoch, offer_nonce, status: OfferStatus::Pending, update_static_invoice_path, - }); + }) + }, + None => { + debug_assert!(false, "Slot in cache was invalid but should'be been checked above"); + return Err(()); }, - None => return Err(()), } - Ok(idx.try_into().map_err(|_| ())?) + Ok(()) + } + + fn slot_needs_offer(&self, slot: u16, duration_since_epoch: Duration) -> bool { + match self.offers.get(slot as usize) { + Some(Some(offer)) => offer.needs_refresh(duration_since_epoch), + // This slot in the cache was pre-allocated as needing an offer in + // `set_paths_to_static_invoice_server` and is currently vacant + Some(None) => true, + // `slot` is out-of-range. Note that the cache only has `MAX_CACHED_OFFERS_TARGET` slots + // total, so any slots outside of that range are invalid. + None => { + debug_assert!(false, "Got offer paths for a non-existent slot in the cache"); + false + }, + } } /// If we have any empty slots in the cache or offers that can and should be replaced with a fresh /// offer, here we return the index of the slot that needs a new offer. The index is used for - /// setting [`ServeStaticInvoice::invoice_slot`] when sending the corresponding new static invoice - /// to the server, so the server knows which existing persisted invoice is being replaced, if any. + /// setting [`OfferPathsRequest::invoice_slot`] when requesting offer paths from the server, so + /// the server can include the slot in the offer paths and reply paths that they create in + /// response. /// /// Returns `None` if the cache is full and no offers can currently be replaced. /// - /// [`ServeStaticInvoice::invoice_slot`]: crate::onion_message::async_payments::ServeStaticInvoice::invoice_slot + /// [`OfferPathsRequest::invoice_slot`]: crate::onion_message::async_payments::OfferPathsRequest::invoice_slot fn needs_new_offer_idx(&self, duration_since_epoch: Duration) -> Option { // If we have any empty offer slots, return the first one we find let empty_slot_idx_opt = self.offers.iter().position(|offer_opt| offer_opt.is_none()); @@ -343,9 +383,9 @@ impl AsyncReceiveOfferCache { } // If all of our offers are already used or pending, then none are available to be replaced - let no_replaceable_offers = self - .offers_with_idx() - .all(|(_, offer)| matches!(offer.status, OfferStatus::Used | OfferStatus::Pending)); + let no_replaceable_offers = self.offers_with_idx().all(|(_, offer)| { + matches!(offer.status, OfferStatus::Used { .. } | OfferStatus::Pending) + }); if no_replaceable_offers { return None; } @@ -355,7 +395,7 @@ impl AsyncReceiveOfferCache { let num_payable_offers = self .offers_with_idx() .filter(|(_, offer)| { - matches!(offer.status, OfferStatus::Used | OfferStatus::Ready { .. }) + matches!(offer.status, OfferStatus::Used { .. } | OfferStatus::Ready { .. }) }) .count(); if num_payable_offers <= 1 { @@ -364,16 +404,11 @@ impl AsyncReceiveOfferCache { // Filter for unused offers where longer than OFFER_REFRESH_THRESHOLD time has passed since they // were last updated, so they are stale enough to warrant replacement. - let awhile_ago = duration_since_epoch.saturating_sub(OFFER_REFRESH_THRESHOLD); - self.unused_offers() - .filter(|(_, _, invoice_confirmed_persisted_at)| { - *invoice_confirmed_persisted_at < awhile_ago - }) + self.offers_with_idx() + .filter(|(_, offer)| offer.needs_refresh(duration_since_epoch)) // Get the stalest offer and return its index - .min_by(|(_, _, persisted_at_a), (_, _, persisted_at_b)| { - persisted_at_a.cmp(&persisted_at_b) - }) - .map(|(idx, _, _)| idx) + .min_by(|(_, offer_a), (_, offer_b)| offer_a.created_at.cmp(&offer_b.created_at)) + .map(|(idx, _)| idx) } /// Returns an iterator over (offer_idx, offer) @@ -387,13 +422,11 @@ impl AsyncReceiveOfferCache { }) } - /// Returns an iterator over (offer_idx, offer, invoice_confirmed_persisted_at) - /// where all returned offers are [`OfferStatus::Ready`] - fn unused_offers(&self) -> impl Iterator { + /// Returns an iterator over (offer_idx, offer, invoice_created_at) where all returned offers are + /// [`OfferStatus::Ready`] + fn unused_ready_offers(&self) -> impl Iterator { self.offers_with_idx().filter_map(|(idx, offer)| match offer.status { - OfferStatus::Ready { invoice_confirmed_persisted_at } => { - Some((idx, offer, invoice_confirmed_persisted_at)) - }, + OfferStatus::Ready { invoice_created_at } => Some((idx, offer, invoice_created_at)), _ => None, }) } @@ -413,21 +446,23 @@ impl AsyncReceiveOfferCache { /// Returns an iterator over the list of cached offers where we need to send an updated invoice to /// the static invoice server. pub(super) fn offers_needing_invoice_refresh( - &self, - ) -> impl Iterator { + &self, duration_since_epoch: Duration, + ) -> impl Iterator { // For any offers which are either in use or pending confirmation by the server, we should send // them a fresh invoice on each timer tick. - self.offers_with_idx().filter_map(|(idx, offer)| { - let needs_invoice_update = - offer.status == OfferStatus::Used || offer.status == OfferStatus::Pending; + self.offers_with_idx().filter_map(move |(_, offer)| { + let needs_invoice_update = match offer.status { + OfferStatus::Used { invoice_created_at } => { + invoice_created_at.saturating_add(INVOICE_REFRESH_THRESHOLD) + < duration_since_epoch + }, + OfferStatus::Pending => true, + // Don't bother updating `Ready` offers' invoices on a timer because the offers themselves + // are regularly rotated anyway. + OfferStatus::Ready { .. } => false, + }; if needs_invoice_update { - let offer_slot = idx.try_into().unwrap_or(u16::MAX); - Some(( - &offer.offer, - offer.offer_nonce, - offer_slot, - &offer.update_static_invoice_path, - )) + Some((&offer.offer, offer.offer_nonce, &offer.update_static_invoice_path)) } else { None } @@ -442,15 +477,10 @@ impl AsyncReceiveOfferCache { /// is needed. /// /// [`StaticInvoicePersisted`]: crate::onion_message::async_payments::StaticInvoicePersisted - pub(super) fn static_invoice_persisted( - &mut self, context: AsyncPaymentsContext, duration_since_epoch: Duration, - ) -> bool { - let offer_id = match context { - AsyncPaymentsContext::StaticInvoicePersisted { path_absolute_expiry, offer_id } => { - if duration_since_epoch > path_absolute_expiry { - return false; - } - offer_id + pub(super) fn static_invoice_persisted(&mut self, context: AsyncPaymentsContext) -> bool { + let (invoice_created_at, offer_id) = match context { + AsyncPaymentsContext::StaticInvoicePersisted { invoice_created_at, offer_id } => { + (invoice_created_at, offer_id) }, _ => return false, }; @@ -458,14 +488,14 @@ impl AsyncReceiveOfferCache { let mut offers = self.offers.iter_mut(); let offer_entry = offers.find(|o| o.as_ref().map_or(false, |o| o.offer.id() == offer_id)); if let Some(Some(ref mut offer)) = offer_entry { - if offer.status == OfferStatus::Used { - // We succeeded in updating the invoice for a used offer, no re-persistence of the cache - // needed - return false; + match offer.status { + OfferStatus::Used { invoice_created_at: ref mut inv_created_at } + | OfferStatus::Ready { invoice_created_at: ref mut inv_created_at } => { + *inv_created_at = core::cmp::min(invoice_created_at, *inv_created_at); + }, + OfferStatus::Pending => offer.status = OfferStatus::Ready { invoice_created_at }, } - offer.status = - OfferStatus::Ready { invoice_confirmed_persisted_at: duration_since_epoch }; return true; } @@ -477,7 +507,7 @@ impl AsyncReceiveOfferCache { self.offers_with_idx() .filter_map(|(_, offer)| { if matches!(offer.status, OfferStatus::Ready { .. }) - || matches!(offer.status, OfferStatus::Used) + || matches!(offer.status, OfferStatus::Used { .. }) { Some(offer.offer.clone()) } else { diff --git a/lightning/src/offers/flow.rs b/lightning/src/offers/flow.rs index cd3f95e0841..38f472b2f5b 100644 --- a/lightning/src/offers/flow.rs +++ b/lightning/src/offers/flow.rs @@ -19,11 +19,11 @@ use bitcoin::constants::ChainHash; use bitcoin::secp256k1::{self, PublicKey, Secp256k1}; use crate::blinded_path::message::{ - BlindedMessagePath, MessageContext, MessageForwardNode, OffersContext, + AsyncPaymentsContext, BlindedMessagePath, MessageContext, MessageForwardNode, OffersContext, }; use crate::blinded_path::payment::{ - BlindedPaymentPath, Bolt12OfferContext, Bolt12RefundContext, PaymentConstraints, - PaymentContext, UnauthenticatedReceiveTlvs, + AsyncBolt12OfferContext, BlindedPaymentPath, Bolt12OfferContext, Bolt12RefundContext, + PaymentConstraints, PaymentContext, UnauthenticatedReceiveTlvs, }; use crate::chain::channelmonitor::LATENCY_GRACE_PERIOD_BLOCKS; @@ -44,32 +44,26 @@ use crate::offers::invoice_request::{ InvoiceRequest, InvoiceRequestBuilder, VerifiedInvoiceRequest, }; use crate::offers::nonce::Nonce; -use crate::offers::offer::{DerivedMetadata, Offer, OfferBuilder}; +use crate::offers::offer::{Amount, DerivedMetadata, Offer, OfferBuilder}; use crate::offers::parse::Bolt12SemanticError; use crate::offers::refund::{Refund, RefundBuilder}; -use crate::onion_message::async_payments::AsyncPaymentsMessage; -use crate::onion_message::messenger::{Destination, MessageRouter, MessageSendInstructions}; +use crate::onion_message::async_payments::{ + AsyncPaymentsMessage, HeldHtlcAvailable, OfferPaths, OfferPathsRequest, ServeStaticInvoice, + StaticInvoicePersisted, +}; +use crate::onion_message::messenger::{ + Destination, MessageRouter, MessageSendInstructions, Responder, +}; use crate::onion_message::offers::OffersMessage; use crate::onion_message::packet::OnionMessageContents; use crate::routing::router::Router; use crate::sign::{EntropySource, NodeSigner, ReceiveAuthKey}; + +use crate::offers::static_invoice::{StaticInvoice, StaticInvoiceBuilder}; use crate::sync::{Mutex, RwLock}; use crate::types::payment::{PaymentHash, PaymentSecret}; use crate::util::ser::Writeable; -#[cfg(async_payments)] -use { - crate::blinded_path::message::AsyncPaymentsContext, - crate::blinded_path::payment::AsyncBolt12OfferContext, - crate::offers::offer::Amount, - crate::offers::static_invoice::{StaticInvoice, StaticInvoiceBuilder}, - crate::onion_message::async_payments::{ - HeldHtlcAvailable, OfferPaths, OfferPathsRequest, ServeStaticInvoice, - StaticInvoicePersisted, - }, - crate::onion_message::messenger::Responder, -}; - #[cfg(feature = "dnssec")] use { crate::blinded_path::message::DNSResolverContext, @@ -104,9 +98,6 @@ where pending_async_payments_messages: Mutex>, async_receive_offer_cache: Mutex, - /// Blinded paths used to request offer paths from the static invoice server, if we are an async - /// recipient. - paths_to_static_invoice_server: Mutex>, #[cfg(feature = "dnssec")] pub(crate) hrn_resolver: OMNameResolver, @@ -146,19 +137,17 @@ where pending_dns_onion_messages: Mutex::new(Vec::new()), async_receive_offer_cache: Mutex::new(AsyncReceiveOfferCache::new()), - paths_to_static_invoice_server: Mutex::new(Vec::new()), } } /// If we are an async recipient, on startup we'll interactively build offers and static invoices /// with an always-online node that will serve static invoices on our behalf. Once the offer is /// built and the static invoice is confirmed as persisted by the server, the underlying - /// [`AsyncReceiveOfferCache`] should be persisted so we remember the offers we've built. - pub(crate) fn with_async_payments_offers_cache( + /// [`AsyncReceiveOfferCache`] should be persisted using + /// [`Self::writeable_async_receive_offer_cache`] so we remember the offers we've built. + pub fn with_async_payments_offers_cache( mut self, async_receive_offer_cache: AsyncReceiveOfferCache, ) -> Self { - self.paths_to_static_invoice_server = - Mutex::new(async_receive_offer_cache.paths_to_static_invoice_server()); self.async_receive_offer_cache = Mutex::new(async_receive_offer_cache); self } @@ -167,21 +156,24 @@ where /// [`Offer`]s with a static invoice server, so the server can serve [`StaticInvoice`]s to payers /// on our behalf when we're offline. /// + /// This method will also send out messages initiating async offer creation to the static invoice + /// server, if any peers are connected. + /// /// This method only needs to be called once when the server first takes on the recipient as a /// client, or when the paths change, e.g. if the paths are set to expire at a particular time. - #[cfg(async_payments)] - pub(crate) fn set_paths_to_static_invoice_server( + pub fn set_paths_to_static_invoice_server( &self, paths_to_static_invoice_server: Vec, + peers: Vec, ) -> Result<(), ()> { - // Store the paths in the async receive cache so they are persisted with the cache, but also - // store them in-memory in the `OffersMessageFlow` so the flow has access to them when building - // onion messages to send to the static invoice server, without introducing undesirable lock - // dependencies with the cache. - *self.paths_to_static_invoice_server.lock().unwrap() = - paths_to_static_invoice_server.clone(); - let mut cache = self.async_receive_offer_cache.lock().unwrap(); - cache.set_paths_to_static_invoice_server(paths_to_static_invoice_server) + cache.set_paths_to_static_invoice_server(paths_to_static_invoice_server.clone())?; + core::mem::drop(cache); + + // We'll only fail here if no peers are connected yet for us to create reply paths to outbound + // offer_paths_requests, so ignore the error. + let _ = self.check_refresh_async_offers(peers, false); + + Ok(()) } /// Gets the node_id held by this [`OffersMessageFlow`]` @@ -193,7 +185,6 @@ where self.receive_auth_key } - #[cfg(async_payments)] fn duration_since_epoch(&self) -> Duration { #[cfg(not(feature = "std"))] let now = Duration::from_secs(self.highest_seen_timestamp.load(Ordering::Acquire) as u64); @@ -243,7 +234,6 @@ where /// The maximum size of a received [`StaticInvoice`] before we'll fail verification in /// [`OffersMessageFlow::verify_serve_static_invoice_message]. -#[cfg(async_payments)] pub const MAX_STATIC_INVOICE_SIZE_BYTES: usize = 5 * 1024; /// Defines the maximum number of [`OffersMessage`] including different reply paths to be sent @@ -253,22 +243,20 @@ pub const MAX_STATIC_INVOICE_SIZE_BYTES: usize = 5 * 1024; /// even if multiple invoices are received. const OFFERS_MESSAGE_REQUEST_LIMIT: usize = 10; -#[cfg(all(async_payments, test))] +#[cfg(test)] pub(crate) const TEST_OFFERS_MESSAGE_REQUEST_LIMIT: usize = OFFERS_MESSAGE_REQUEST_LIMIT; /// The default relative expiry for reply paths where a quick response is expected and the reply /// path is single-use. -#[cfg(async_payments)] const TEMP_REPLY_PATH_RELATIVE_EXPIRY: Duration = Duration::from_secs(2 * 60 * 60); -#[cfg(all(async_payments, test))] +#[cfg(test)] pub(crate) const TEST_TEMP_REPLY_PATH_RELATIVE_EXPIRY: Duration = TEMP_REPLY_PATH_RELATIVE_EXPIRY; // Default to async receive offers and the paths used to update them lasting one year. -#[cfg(async_payments)] const DEFAULT_ASYNC_RECEIVE_OFFER_EXPIRY: Duration = Duration::from_secs(365 * 24 * 60 * 60); -#[cfg(all(async_payments, test))] +#[cfg(test)] pub(crate) const TEST_DEFAULT_ASYNC_RECEIVE_OFFER_EXPIRY: Duration = DEFAULT_ASYNC_RECEIVE_OFFER_EXPIRY; @@ -285,8 +273,7 @@ where /// [`Self::set_paths_to_static_invoice_server`]. /// /// Errors if blinded path creation fails or the provided `recipient_id` is larger than 1KiB. - #[cfg(async_payments)] - pub(crate) fn blinded_paths_for_async_recipient( + pub fn blinded_paths_for_async_recipient( &self, recipient_id: Vec, relative_expiry: Option, peers: Vec, ) -> Result, ()> { @@ -361,7 +348,7 @@ where ) } - #[cfg(all(test, async_payments))] + #[cfg(test)] /// Creates multi-hop blinded payment paths for the given `amount_msats` by delegating to /// [`Router::create_blinded_payment_paths`]. pub(crate) fn test_create_blinded_payment_paths( @@ -408,7 +395,7 @@ pub enum InvreqResponseInstructions { /// the invoice request since it is now verified. SendInvoice(VerifiedInvoiceRequest), /// We are a static invoice server and should respond to this invoice request by retrieving the - /// [`StaticInvoice`] corresponding to the `recipient_id` and `invoice_id` and calling + /// [`StaticInvoice`] corresponding to the `recipient_id` and `invoice_slot` and calling /// `OffersMessageFlow::enqueue_static_invoice`. /// /// [`StaticInvoice`]: crate::offers::static_invoice::StaticInvoice @@ -417,8 +404,8 @@ pub enum InvreqResponseInstructions { /// /// [`StaticInvoice`]: crate::offers::static_invoice::StaticInvoice recipient_id: Vec, - /// An identifier for the specific invoice being requested by the payer. - invoice_id: u128, + /// The slot number for the specific invoice being requested by the payer. + invoice_slot: u16, }, } @@ -446,10 +433,9 @@ where let nonce = match context { None if invoice_request.metadata().is_some() => None, Some(OffersContext::InvoiceRequest { nonce }) => Some(nonce), - #[cfg(async_payments)] Some(OffersContext::StaticInvoiceRequested { recipient_id, - invoice_id, + invoice_slot, path_absolute_expiry, }) => { if path_absolute_expiry < self.duration_since_epoch() { @@ -458,7 +444,7 @@ where return Ok(InvreqResponseInstructions::SendStaticInvoice { recipient_id, - invoice_id, + invoice_slot, }); }, _ => return Err(()), @@ -508,7 +494,6 @@ where /// /// Returns `Err(())` if: /// - The inbound payment context has expired. - #[cfg(async_payments)] pub fn verify_inbound_async_payment_context( &self, context: AsyncPaymentsContext, ) -> Result<(), ()> { @@ -525,7 +510,7 @@ where fn create_offer_builder_intern( &self, entropy_source: ES, make_paths: PF, - ) -> Result<(OfferBuilder, Nonce), Bolt12SemanticError> + ) -> Result<(OfferBuilder<'_, DerivedMetadata, secp256k1::All>, Nonce), Bolt12SemanticError> where ES::Target: EntropySource, PF: FnOnce( @@ -580,7 +565,7 @@ where /// [`DefaultMessageRouter`]: crate::onion_message::messenger::DefaultMessageRouter pub fn create_offer_builder( &self, entropy_source: ES, peers: Vec, - ) -> Result, Bolt12SemanticError> + ) -> Result, Bolt12SemanticError> where ES::Target: EntropySource, { @@ -601,7 +586,7 @@ where /// See [`Self::create_offer_builder`] for more details on usage. pub fn create_offer_builder_using_router( &self, router: ME, entropy_source: ES, peers: Vec, - ) -> Result, Bolt12SemanticError> + ) -> Result, Bolt12SemanticError> where ME::Target: MessageRouter, ES::Target: EntropySource, @@ -624,10 +609,9 @@ where /// 2. Use [`Self::create_static_invoice_builder`] to create a [`StaticInvoice`] from this /// [`Offer`] plus the returned [`Nonce`], and provide the static invoice to the /// aforementioned always-online node. - #[cfg(async_payments)] pub fn create_async_receive_offer_builder( &self, entropy_source: ES, message_paths_to_always_online_node: Vec, - ) -> Result<(OfferBuilder, Nonce), Bolt12SemanticError> + ) -> Result<(OfferBuilder<'_, DerivedMetadata, secp256k1::All>, Nonce), Bolt12SemanticError> where ES::Target: EntropySource, { @@ -639,7 +623,7 @@ where fn create_refund_builder_intern( &self, entropy_source: ES, make_paths: PF, amount_msats: u64, absolute_expiry: Duration, payment_id: PaymentId, - ) -> Result, Bolt12SemanticError> + ) -> Result, Bolt12SemanticError> where ES::Target: EntropySource, PF: FnOnce( @@ -712,7 +696,7 @@ where pub fn create_refund_builder( &self, entropy_source: ES, amount_msats: u64, absolute_expiry: Duration, payment_id: PaymentId, peers: Vec, - ) -> Result, Bolt12SemanticError> + ) -> Result, Bolt12SemanticError> where ES::Target: EntropySource, { @@ -751,7 +735,7 @@ where pub fn create_refund_builder_using_router( &self, router: ME, entropy_source: ES, amount_msats: u64, absolute_expiry: Duration, payment_id: PaymentId, peers: Vec, - ) -> Result, Bolt12SemanticError> + ) -> Result, Bolt12SemanticError> where ME::Target: MessageRouter, ES::Target: EntropySource, @@ -793,7 +777,6 @@ where /// Creates a [`StaticInvoiceBuilder`] from the corresponding [`Offer`] and [`Nonce`] that were /// created via [`Self::create_async_receive_offer_builder`]. - #[cfg(async_payments)] pub fn create_static_invoice_builder<'a, ES: Deref, R: Deref>( &self, router: &R, entropy_source: ES, offer: &'a Offer, offer_nonce: Nonce, payment_secret: PaymentSecret, relative_expiry_secs: u32, @@ -1113,9 +1096,10 @@ where Ok(()) } - /// Forwards a [`StaticInvoice`] over the provided `responder`. - #[cfg(async_payments)] - pub(crate) fn enqueue_static_invoice( + /// Forwards a [`StaticInvoice`] over the provided [`Responder`] in response to an + /// [`InvoiceRequest`] that we as a static invoice server received on behalf of an often-offline + /// recipient. + pub fn enqueue_static_invoice( &self, invoice: StaticInvoice, responder: Responder, ) -> Result<(), Bolt12SemanticError> { let duration_since_epoch = self.duration_since_epoch(); @@ -1143,7 +1127,6 @@ where /// /// [`ReleaseHeldHtlc`]: crate::onion_message::async_payments::ReleaseHeldHtlc /// [`supports_onion_messages`]: crate::types::features::Features::supports_onion_messages - #[cfg(async_payments)] pub fn enqueue_held_htlc_available( &self, invoice: &StaticInvoice, payment_id: PaymentId, peers: Vec, ) -> Result<(), Bolt12SemanticError> { @@ -1226,13 +1209,15 @@ where /// Retrieve an [`Offer`] for receiving async payments as an often-offline recipient. Will only /// return an offer if [`Self::set_paths_to_static_invoice_server`] was called and we succeeded in /// interactively building a [`StaticInvoice`] with the static invoice server. - #[cfg(async_payments)] - pub(crate) fn get_async_receive_offer(&self) -> Result<(Offer, bool), ()> { + /// + /// Returns the requested offer as well as a bool indicating whether the cache needs to be + /// persisted using [`Self::writeable_async_receive_offer_cache`]. + pub fn get_async_receive_offer(&self) -> Result<(Offer, bool), ()> { let mut cache = self.async_receive_offer_cache.lock().unwrap(); cache.get_async_receive_offer(self.duration_since_epoch()) } - #[cfg(all(test, async_payments))] + #[cfg(test)] pub(crate) fn test_get_async_receive_offers(&self) -> Vec { self.async_receive_offer_cache.lock().unwrap().test_get_payable_offers() } @@ -1248,8 +1233,7 @@ where /// the cache can self-regulate the number of messages sent out. /// /// Errors if we failed to create blinded reply paths when sending an [`OfferPathsRequest`] message. - #[cfg(async_payments)] - pub(crate) fn check_refresh_async_receive_offer_cache( + pub fn check_refresh_async_receive_offer_cache( &self, peers: Vec, usable_channels: Vec, entropy: ES, router: R, timer_tick_occurred: bool, ) -> Result<(), ()> @@ -1258,76 +1242,82 @@ where R::Target: Router, { // Terminate early if this node does not intend to receive async payments. - if self.paths_to_static_invoice_server.lock().unwrap().is_empty() { - return Ok(()); + { + let cache = self.async_receive_offer_cache.lock().unwrap(); + if cache.paths_to_static_invoice_server().is_empty() { + return Ok(()); + } } + self.check_refresh_async_offers(peers.clone(), timer_tick_occurred)?; + + if timer_tick_occurred { + self.check_refresh_static_invoices(peers, usable_channels, entropy, router); + } + + Ok(()) + } + + fn check_refresh_async_offers( + &self, peers: Vec, timer_tick_occurred: bool, + ) -> Result<(), ()> { let duration_since_epoch = self.duration_since_epoch(); + let mut cache = self.async_receive_offer_cache.lock().unwrap(); // Update the cache to remove expired offers, and check to see whether we need new offers to be // interactively built with the static invoice server. - let needs_new_offers = self - .async_receive_offer_cache - .lock() - .unwrap() - .prune_expired_offers(duration_since_epoch, timer_tick_occurred); + let needs_new_offer_slot = + match cache.prune_expired_offers(duration_since_epoch, timer_tick_occurred) { + Some(idx) => idx, + None => return Ok(()), + }; // If we need new offers, send out offer paths request messages to the static invoice server. - if needs_new_offers { - let context = MessageContext::AsyncPayments(AsyncPaymentsContext::OfferPaths { - path_absolute_expiry: duration_since_epoch - .saturating_add(TEMP_REPLY_PATH_RELATIVE_EXPIRY), - }); - let reply_paths = match self.create_blinded_paths(peers.clone(), context) { - Ok(paths) => paths, - Err(()) => { - return Err(()); - }, - }; + let context = MessageContext::AsyncPayments(AsyncPaymentsContext::OfferPaths { + path_absolute_expiry: duration_since_epoch + .saturating_add(TEMP_REPLY_PATH_RELATIVE_EXPIRY), + invoice_slot: needs_new_offer_slot, + }); + let reply_paths = match self.create_blinded_paths(peers, context) { + Ok(paths) => paths, + Err(()) => { + return Err(()); + }, + }; - // We can't fail past this point, so indicate to the cache that we've requested new offers. - self.async_receive_offer_cache.lock().unwrap().new_offers_requested(); + // We can't fail past this point, so indicate to the cache that we've requested new offers. + cache.new_offers_requested(); - let mut pending_async_payments_messages = - self.pending_async_payments_messages.lock().unwrap(); - let message = AsyncPaymentsMessage::OfferPathsRequest(OfferPathsRequest {}); - enqueue_onion_message_with_reply_paths( - message, - &self.paths_to_static_invoice_server.lock().unwrap()[..], - reply_paths, - &mut pending_async_payments_messages, - ); - } - - if timer_tick_occurred { - self.check_refresh_static_invoices( - peers, - usable_channels, - duration_since_epoch, - entropy, - router, - ); - } + let mut pending_async_payments_messages = + self.pending_async_payments_messages.lock().unwrap(); + let message = AsyncPaymentsMessage::OfferPathsRequest(OfferPathsRequest { + invoice_slot: needs_new_offer_slot, + }); + enqueue_onion_message_with_reply_paths( + message, + cache.paths_to_static_invoice_server(), + reply_paths, + &mut pending_async_payments_messages, + ); Ok(()) } /// Enqueue onion messages that will used to request invoice refresh from the static invoice /// server, based on the offers provided by the cache. - #[cfg(async_payments)] fn check_refresh_static_invoices( - &self, peers: Vec, usable_channels: Vec, - duration_since_epoch: Duration, entropy: ES, router: R, + &self, peers: Vec, usable_channels: Vec, entropy: ES, + router: R, ) where ES::Target: EntropySource, R::Target: Router, { let mut serve_static_invoice_msgs = Vec::new(); { + let duration_since_epoch = self.duration_since_epoch(); let cache = self.async_receive_offer_cache.lock().unwrap(); - for offer_and_metadata in cache.offers_needing_invoice_refresh() { - let (offer, offer_nonce, slot_number, update_static_invoice_path) = - offer_and_metadata; + for offer_and_metadata in cache.offers_needing_invoice_refresh(duration_since_epoch) { + let (offer, offer_nonce, update_static_invoice_path) = offer_and_metadata; let (invoice, forward_invreq_path) = match self.create_static_invoice_for_server( offer, @@ -1342,10 +1332,8 @@ where }; let reply_path_context = { - let path_absolute_expiry = - duration_since_epoch.saturating_add(TEMP_REPLY_PATH_RELATIVE_EXPIRY); MessageContext::AsyncPayments(AsyncPaymentsContext::StaticInvoicePersisted { - path_absolute_expiry, + invoice_created_at: invoice.created_at(), offer_id: offer.id(), }) }; @@ -1353,7 +1341,6 @@ where let serve_invoice_message = ServeStaticInvoice { invoice, forward_invoice_request_path: forward_invreq_path, - invoice_slot: slot_number, }; serve_static_invoice_msgs.push(( serve_invoice_message, @@ -1384,13 +1371,10 @@ where /// Handles an incoming [`OfferPathsRequest`] onion message from an often-offline recipient who /// wants us (the static invoice server) to serve [`StaticInvoice`]s to payers on their behalf. /// Sends out [`OfferPaths`] onion messages in response. - #[cfg(async_payments)] - pub(crate) fn handle_offer_paths_request( - &self, context: AsyncPaymentsContext, peers: Vec, entropy_source: ES, - ) -> Option<(OfferPaths, MessageContext)> - where - ES::Target: EntropySource, - { + pub fn handle_offer_paths_request( + &self, request: &OfferPathsRequest, context: AsyncPaymentsContext, + peers: Vec, + ) -> Option<(OfferPaths, MessageContext)> { let duration_since_epoch = self.duration_since_epoch(); let recipient_id = match context { @@ -1403,10 +1387,6 @@ where _ => return None, }; - let mut random_bytes = [0u8; 16]; - random_bytes.copy_from_slice(&entropy_source.get_secure_random_bytes()[..16]); - let invoice_id = u128::from_be_bytes(random_bytes); - // Create the blinded paths that will be included in the async recipient's offer. let (offer_paths, paths_expiry) = { let path_absolute_expiry = @@ -1414,7 +1394,7 @@ where let context = MessageContext::Offers(OffersContext::StaticInvoiceRequested { recipient_id: recipient_id.clone(), path_absolute_expiry, - invoice_id, + invoice_slot: request.invoice_slot, }); match self.create_blinded_paths(peers, context) { @@ -1431,7 +1411,7 @@ where duration_since_epoch.saturating_add(DEFAULT_ASYNC_RECEIVE_OFFER_EXPIRY); MessageContext::AsyncPayments(AsyncPaymentsContext::ServeStaticInvoice { recipient_id, - invoice_id, + invoice_slot: request.invoice_slot, path_absolute_expiry, }) }; @@ -1447,8 +1427,7 @@ where /// /// Returns `None` if we have enough offers cached already, verification of `message` fails, or we /// fail to create blinded paths. - #[cfg(async_payments)] - pub(crate) fn handle_offer_paths( + pub fn handle_offer_paths( &self, message: OfferPaths, context: AsyncPaymentsContext, responder: Responder, peers: Vec, usable_channels: Vec, entropy: ES, router: R, @@ -1458,14 +1437,15 @@ where R::Target: Router, { let duration_since_epoch = self.duration_since_epoch(); - match context { - AsyncPaymentsContext::OfferPaths { path_absolute_expiry } => { + let invoice_slot = match context { + AsyncPaymentsContext::OfferPaths { invoice_slot, path_absolute_expiry } => { if duration_since_epoch > path_absolute_expiry { return None; } + invoice_slot }, _ => return None, - } + }; { // Only respond with `ServeStaticInvoice` if we actually need a new offer built. @@ -1474,6 +1454,7 @@ where if !cache.should_build_offer_with_paths( &message.paths[..], message.paths_absolute_expiry, + invoice_slot, duration_since_epoch, ) { return None; @@ -1509,36 +1490,30 @@ where Err(()) => return None, }; - let res = self.async_receive_offer_cache.lock().unwrap().cache_pending_offer( + if let Err(()) = self.async_receive_offer_cache.lock().unwrap().cache_pending_offer( offer, message.paths_absolute_expiry, offer_nonce, responder, duration_since_epoch, - ); - - let invoice_slot = match res { - Ok(idx) => idx, - Err(()) => return None, - }; + invoice_slot, + ) { + return None; + } let reply_path_context = { - let path_absolute_expiry = - duration_since_epoch.saturating_add(TEMP_REPLY_PATH_RELATIVE_EXPIRY); MessageContext::AsyncPayments(AsyncPaymentsContext::StaticInvoicePersisted { offer_id, - path_absolute_expiry, + invoice_created_at: invoice.created_at(), }) }; - let serve_invoice_message = - ServeStaticInvoice { invoice, forward_invoice_request_path, invoice_slot }; + let serve_invoice_message = ServeStaticInvoice { invoice, forward_invoice_request_path }; Some((serve_invoice_message, reply_path_context)) } /// Creates a [`StaticInvoice`] and a blinded path for the server to forward invoice requests from /// payers to our node. - #[cfg(async_payments)] fn create_static_invoice_for_server( &self, offer: &Offer, offer_nonce: Nonce, peers: Vec, usable_channels: Vec, entropy: ES, router: R, @@ -1595,17 +1570,16 @@ where /// wants us as a static invoice server to serve the [`ServeStaticInvoice::invoice`] to payers on /// their behalf. /// - /// On success, returns `(recipient_id, invoice_id)` for use in persisting and later retrieving + /// On success, returns `(recipient_id, invoice_slot)` for use in persisting and later retrieving /// the static invoice from the database. /// /// Errors if the [`ServeStaticInvoice::invoice`] is expired or larger than - /// [`MAX_STATIC_INVOICE_SIZE_BYTES`], or if blinded path verification fails. + /// [`MAX_STATIC_INVOICE_SIZE_BYTES`]. /// /// [`ServeStaticInvoice::invoice`]: crate::onion_message::async_payments::ServeStaticInvoice::invoice - #[cfg(async_payments)] pub fn verify_serve_static_invoice_message( &self, message: &ServeStaticInvoice, context: AsyncPaymentsContext, - ) -> Result<(Vec, u128), ()> { + ) -> Result<(Vec, u16), ()> { if message.invoice.is_expired_no_std(self.duration_since_epoch()) { return Err(()); } @@ -1615,14 +1589,14 @@ where match context { AsyncPaymentsContext::ServeStaticInvoice { recipient_id, - invoice_id, + invoice_slot, path_absolute_expiry, } => { if self.duration_since_epoch() > path_absolute_expiry { return Err(()); } - return Ok((recipient_id, invoice_id)); + return Ok((recipient_id, invoice_slot)); }, _ => return Err(()), }; @@ -1632,7 +1606,6 @@ where /// to payers on behalf of an often-offline recipient. This method must be called after persisting /// a [`StaticInvoice`] to confirm to the recipient that their corresponding [`Offer`] is ready to /// receive async payments. - #[cfg(async_payments)] pub fn static_invoice_persisted(&self, responder: Responder) { let mut pending_async_payments_messages = self.pending_async_payments_messages.lock().unwrap(); @@ -1641,17 +1614,17 @@ where } /// Handles an incoming [`StaticInvoicePersisted`] onion message from the static invoice server. - /// Returns a bool indicating whether the async receive offer cache needs to be re-persisted. + /// Returns a bool indicating whether the async receive offer cache needs to be re-persisted using + /// [`Self::writeable_async_receive_offer_cache`]. /// /// [`StaticInvoicePersisted`]: crate::onion_message::async_payments::StaticInvoicePersisted - #[cfg(async_payments)] - pub(crate) fn handle_static_invoice_persisted(&self, context: AsyncPaymentsContext) -> bool { + pub fn handle_static_invoice_persisted(&self, context: AsyncPaymentsContext) -> bool { let mut cache = self.async_receive_offer_cache.lock().unwrap(); - cache.static_invoice_persisted(context, self.duration_since_epoch()) + cache.static_invoice_persisted(context) } - /// Get the `AsyncReceiveOfferCache` for persistence. - pub(crate) fn writeable_async_receive_offer_cache(&self) -> impl Writeable + '_ { - &self.async_receive_offer_cache + /// Get the encoded [`AsyncReceiveOfferCache`] for persistence. + pub fn writeable_async_receive_offer_cache(&self) -> Vec { + self.async_receive_offer_cache.encode() } } diff --git a/lightning/src/offers/invoice.rs b/lightning/src/offers/invoice.rs index 198e544fefc..9751f52b046 100644 --- a/lightning/src/offers/invoice.rs +++ b/lightning/src/offers/invoice.rs @@ -836,7 +836,7 @@ macro_rules! invoice_accessors { ($self: ident, $contents: expr) => { /// From [`Offer::description`] or [`Refund::description`]. /// /// [`Offer::description`]: crate::offers::offer::Offer::description - pub fn description(&$self) -> Option { + pub fn description(&$self) -> Option> { $contents.description() } @@ -854,7 +854,7 @@ macro_rules! invoice_accessors { ($self: ident, $contents: expr) => { /// From [`Offer::issuer`] or [`Refund::issuer`]. /// /// [`Offer::issuer`]: crate::offers::offer::Offer::issuer - pub fn issuer(&$self) -> Option { + pub fn issuer(&$self) -> Option> { $contents.issuer() } @@ -919,7 +919,7 @@ macro_rules! invoice_accessors { ($self: ident, $contents: expr) => { /// A payer-provided note reflected back in the invoice. /// /// From [`InvoiceRequest::payer_note`] or [`Refund::payer_note`]. - pub fn payer_note(&$self) -> Option { + pub fn payer_note(&$self) -> Option> { $contents.payer_note() } @@ -1019,7 +1019,7 @@ impl Bolt12Invoice { ) } - pub(crate) fn as_tlv_stream(&self) -> FullInvoiceTlvStreamRef { + pub(crate) fn as_tlv_stream(&self) -> FullInvoiceTlvStreamRef<'_> { let ( payer_tlv_stream, offer_tlv_stream, @@ -1127,7 +1127,7 @@ impl InvoiceContents { } } - fn description(&self) -> Option { + fn description(&self) -> Option> { match self { InvoiceContents::ForOffer { invoice_request, .. } => { invoice_request.inner.offer.description() @@ -1154,7 +1154,7 @@ impl InvoiceContents { } } - fn issuer(&self) -> Option { + fn issuer(&self) -> Option> { match self { InvoiceContents::ForOffer { invoice_request, .. } => { invoice_request.inner.offer.issuer() @@ -1220,7 +1220,7 @@ impl InvoiceContents { } } - fn payer_note(&self) -> Option { + fn payer_note(&self) -> Option> { match self { InvoiceContents::ForOffer { invoice_request, .. } => invoice_request.payer_note(), InvoiceContents::ForRefund { refund, .. } => refund.payer_note(), @@ -1315,7 +1315,7 @@ impl InvoiceContents { ) } - fn as_tlv_stream(&self) -> PartialInvoiceTlvStreamRef { + fn as_tlv_stream(&self) -> PartialInvoiceTlvStreamRef<'_> { let (payer, offer, invoice_request, experimental_offer, experimental_invoice_request) = match self { InvoiceContents::ForOffer { invoice_request, .. } => { @@ -1379,7 +1379,7 @@ pub(super) fn filter_fallbacks(chain: ChainHash, fallbacks: &Vec (InvoiceTlvStreamRef, ExperimentalInvoiceTlvStreamRef) { + fn as_tlv_stream(&self) -> (InvoiceTlvStreamRef<'_>, ExperimentalInvoiceTlvStreamRef) { let features = { if self.features == Bolt12InvoiceFeatures::empty() { None diff --git a/lightning/src/offers/invoice_request.rs b/lightning/src/offers/invoice_request.rs index dedbb27c674..27f32bcc1d2 100644 --- a/lightning/src/offers/invoice_request.rs +++ b/lightning/src/offers/invoice_request.rs @@ -688,7 +688,7 @@ macro_rules! invoice_request_accessors { ($self: ident, $contents: expr) => { /// A payer-provided note which will be seen by the recipient and reflected back in the invoice /// response. - pub fn payer_note(&$self) -> Option { + pub fn payer_note(&$self) -> Option> { $contents.payer_note() } @@ -854,7 +854,7 @@ impl InvoiceRequest { invoice_request_respond_with_explicit_signing_pubkey_methods!( self, self, - InvoiceBuilder + InvoiceBuilder<'_, ExplicitSigningPubkey> ); invoice_request_verify_method!(self, Self); @@ -889,7 +889,7 @@ impl InvoiceRequest { self.signature } - pub(crate) fn as_tlv_stream(&self) -> FullInvoiceRequestTlvStreamRef { + pub(crate) fn as_tlv_stream(&self) -> FullInvoiceRequestTlvStreamRef<'_> { let ( payer_tlv_stream, offer_tlv_stream, @@ -968,7 +968,7 @@ impl VerifiedInvoiceRequest { invoice_request_respond_with_explicit_signing_pubkey_methods!( self, self.inner, - InvoiceBuilder + InvoiceBuilder<'_, ExplicitSigningPubkey> ); #[cfg(c_bindings)] invoice_request_respond_with_explicit_signing_pubkey_methods!( @@ -980,7 +980,7 @@ impl VerifiedInvoiceRequest { invoice_request_respond_with_derived_signing_pubkey_methods!( self, self.inner, - InvoiceBuilder + InvoiceBuilder<'_, DerivedSigningPubkey> ); #[cfg(c_bindings)] invoice_request_respond_with_derived_signing_pubkey_methods!( @@ -1074,7 +1074,7 @@ impl InvoiceRequestContents { self.payer_signing_pubkey } - pub(super) fn payer_note(&self) -> Option { + pub(super) fn payer_note(&self) -> Option> { self.inner.payer_note.as_ref().map(|payer_note| PrintableString(payer_note.as_str())) } @@ -1082,7 +1082,7 @@ impl InvoiceRequestContents { &self.inner.offer_from_hrn } - pub(super) fn as_tlv_stream(&self) -> PartialInvoiceRequestTlvStreamRef { + pub(super) fn as_tlv_stream(&self) -> PartialInvoiceRequestTlvStreamRef<'_> { let (payer, offer, mut invoice_request, experimental_offer, experimental_invoice_request) = self.inner.as_tlv_stream(); invoice_request.payer_id = Some(&self.payer_signing_pubkey); @@ -1103,7 +1103,7 @@ impl InvoiceRequestContentsWithoutPayerSigningPubkey { self.amount_msats } - pub(super) fn as_tlv_stream(&self) -> PartialInvoiceRequestTlvStreamRef { + pub(super) fn as_tlv_stream(&self) -> PartialInvoiceRequestTlvStreamRef<'_> { let payer = PayerTlvStreamRef { metadata: self.payer.0.as_bytes() }; let (offer, experimental_offer) = self.offer.as_tlv_stream(); diff --git a/lightning/src/offers/mod.rs b/lightning/src/offers/mod.rs index b603deecd60..5b5cf6cdc78 100644 --- a/lightning/src/offers/mod.rs +++ b/lightning/src/offers/mod.rs @@ -16,7 +16,7 @@ pub mod offer; pub mod flow; -pub(crate) mod async_receive_offer_cache; +pub mod async_receive_offer_cache; pub mod invoice; pub mod invoice_error; mod invoice_macros; diff --git a/lightning/src/offers/offer.rs b/lightning/src/offers/offer.rs index bdc9e7c8b87..7fd2c4e60e2 100644 --- a/lightning/src/offers/offer.rs +++ b/lightning/src/offers/offer.rs @@ -646,7 +646,7 @@ macro_rules! offer_accessors { ($self: ident, $contents: expr) => { /// A complete description of the purpose of the payment. Intended to be displayed to the user /// but with the caveat that it has not been verified in any way. - pub fn description(&$self) -> Option<$crate::types::string::PrintableString> { + pub fn description(&$self) -> Option<$crate::types::string::PrintableString<'_>> { $contents.description() } @@ -664,7 +664,7 @@ macro_rules! offer_accessors { ($self: ident, $contents: expr) => { /// The issuer of the offer, possibly beginning with `user@domain` or `domain`. Intended to be /// displayed to the user but with the caveat that it has not been verified in any way. - pub fn issuer(&$self) -> Option<$crate::types::string::PrintableString> { + pub fn issuer(&$self) -> Option<$crate::types::string::PrintableString<'_>> { $contents.issuer() } @@ -802,7 +802,7 @@ impl Offer { #[cfg(test)] impl Offer { - pub(super) fn as_tlv_stream(&self) -> FullOfferTlvStreamRef { + pub(super) fn as_tlv_stream(&self) -> FullOfferTlvStreamRef<'_> { self.contents.as_tlv_stream() } } @@ -848,7 +848,7 @@ impl OfferContents { self.amount } - pub fn description(&self) -> Option { + pub fn description(&self) -> Option> { self.description.as_ref().map(|description| PrintableString(description)) } @@ -874,7 +874,7 @@ impl OfferContents { .unwrap_or(false) } - pub fn issuer(&self) -> Option { + pub fn issuer(&self) -> Option> { self.issuer.as_ref().map(|issuer| PrintableString(issuer.as_str())) } @@ -995,7 +995,7 @@ impl OfferContents { } } - pub(super) fn as_tlv_stream(&self) -> FullOfferTlvStreamRef { + pub(super) fn as_tlv_stream(&self) -> FullOfferTlvStreamRef<'_> { let (currency, amount) = match &self.amount { None => (None, None), Some(Amount::Bitcoin { amount_msats }) => (None, Some(*amount_msats)), @@ -1287,7 +1287,9 @@ impl TryFrom for OfferContents { let (issuer_signing_pubkey, paths) = match (issuer_id, paths) { (None, None) => return Err(Bolt12SemanticError::MissingIssuerSigningPubkey), - (_, Some(paths)) if paths.is_empty() => return Err(Bolt12SemanticError::MissingPaths), + (None, Some(paths)) if paths.is_empty() => { + return Err(Bolt12SemanticError::MissingPaths) + }, (issuer_id, paths) => (issuer_id, paths), }; @@ -2001,6 +2003,7 @@ mod tests { } let mut builder = OfferBuilder::new(pubkey(42)); + builder.offer.issuer_signing_pubkey = None; builder.offer.paths = Some(vec![]); let offer = builder.build().unwrap(); diff --git a/lightning/src/offers/parse.rs b/lightning/src/offers/parse.rs index 38e69e26304..8e8d01b4e50 100644 --- a/lightning/src/offers/parse.rs +++ b/lightning/src/offers/parse.rs @@ -43,7 +43,20 @@ mod sealed { // Offer encoding may be split by '+' followed by optional whitespace. let encoded = match s.split('+').skip(1).next() { Some(_) => { - for chunk in s.split('+') { + let mut chunks = s.split('+'); + + // Check first chunk without trimming + if let Some(first_chunk) = chunks.next() { + if first_chunk.contains(char::is_whitespace) { + return Err(Bolt12ParseError::InvalidLeadingWhitespace); + } + if first_chunk.is_empty() { + return Err(Bolt12ParseError::InvalidContinuation); + } + } + + // Check remaining chunks + for chunk in chunks { let chunk = chunk.trim_start(); if chunk.is_empty() || chunk.contains(char::is_whitespace) { return Err(Bolt12ParseError::InvalidContinuation); @@ -123,6 +136,8 @@ pub enum Bolt12ParseError { /// The bech32 encoding does not conform to the BOLT 12 requirements for continuing messages /// across multiple parts (i.e., '+' followed by whitespace). InvalidContinuation, + /// The bech32 string starts with whitespace, which violates BOLT 12 encoding requirements. + InvalidLeadingWhitespace, /// The bech32 encoding's human-readable part does not match what was expected for the message /// being parsed. InvalidBech32Hrp, @@ -322,6 +337,15 @@ mod tests { } } + #[test] + fn fails_parsing_bech32_encoded_offer_with_leading_whitespace() { + let encoded_offer = "\u{b}lno1pqpzwyq2p32x2um5ypmx2cm5dae8x93pqthvwfzadd7jejes8q9lhc4rvjxd022zv5l44g6qah+\u{b}\u{b}\u{b}\u{b}82ru5rdpnpj"; + match encoded_offer.parse::() { + Ok(_) => panic!("Valid offer: {}", encoded_offer), + Err(e) => assert_eq!(e, Bolt12ParseError::InvalidLeadingWhitespace), + } + } + #[test] fn fails_parsing_bech32_encoded_offer_with_invalid_bech32_data() { let encoded_offer = "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxo"; diff --git a/lightning/src/offers/refund.rs b/lightning/src/offers/refund.rs index 68f80177e1e..87d7a845b53 100644 --- a/lightning/src/offers/refund.rs +++ b/lightning/src/offers/refund.rs @@ -480,7 +480,7 @@ pub(super) struct RefundContents { impl Refund { /// A complete description of the purpose of the refund. Intended to be displayed to the user /// but with the caveat that it has not been verified in any way. - pub fn description(&self) -> PrintableString { + pub fn description(&self) -> PrintableString<'_> { self.contents.description() } @@ -504,7 +504,7 @@ impl Refund { /// The issuer of the refund, possibly beginning with `user@domain` or `domain`. Intended to be /// displayed to the user but with the caveat that it has not been verified in any way. - pub fn issuer(&self) -> Option { + pub fn issuer(&self) -> Option> { self.contents.issuer() } @@ -553,7 +553,7 @@ impl Refund { } /// Payer provided note to include in the invoice. - pub fn payer_note(&self) -> Option { + pub fn payer_note(&self) -> Option> { self.contents.payer_note() } } @@ -667,8 +667,8 @@ macro_rules! respond_with_derived_signing_pubkey_methods { ($self: ident, $build #[cfg(not(c_bindings))] impl Refund { - respond_with_explicit_signing_pubkey_methods!(self, InvoiceBuilder); - respond_with_derived_signing_pubkey_methods!(self, InvoiceBuilder); + respond_with_explicit_signing_pubkey_methods!(self, InvoiceBuilder<'_, ExplicitSigningPubkey>); + respond_with_derived_signing_pubkey_methods!(self, InvoiceBuilder<'_, DerivedSigningPubkey>); } #[cfg(c_bindings)] @@ -679,7 +679,7 @@ impl Refund { #[cfg(test)] impl Refund { - fn as_tlv_stream(&self) -> RefundTlvStreamRef { + fn as_tlv_stream(&self) -> RefundTlvStreamRef<'_> { self.contents.as_tlv_stream() } } @@ -705,7 +705,7 @@ impl Hash for Refund { } impl RefundContents { - pub fn description(&self) -> PrintableString { + pub fn description(&self) -> PrintableString<'_> { PrintableString(&self.description) } @@ -727,7 +727,7 @@ impl RefundContents { .unwrap_or(false) } - pub fn issuer(&self) -> Option { + pub fn issuer(&self) -> Option> { self.issuer.as_ref().map(|issuer| PrintableString(issuer.as_str())) } @@ -770,11 +770,11 @@ impl RefundContents { } /// Payer provided note to include in the invoice. - pub fn payer_note(&self) -> Option { + pub fn payer_note(&self) -> Option> { self.payer_note.as_ref().map(|payer_note| PrintableString(payer_note.as_str())) } - pub(super) fn as_tlv_stream(&self) -> RefundTlvStreamRef { + pub(super) fn as_tlv_stream(&self) -> RefundTlvStreamRef<'_> { let payer = PayerTlvStreamRef { metadata: self.payer.0.as_bytes() }; let offer = OfferTlvStreamRef { diff --git a/lightning/src/offers/static_invoice.rs b/lightning/src/offers/static_invoice.rs index 805a2ffe9f8..77f486a6a06 100644 --- a/lightning/src/offers/static_invoice.rs +++ b/lightning/src/offers/static_invoice.rs @@ -235,7 +235,7 @@ macro_rules! invoice_accessors { ($self: ident, $contents: expr) => { /// A complete description of the purpose of the originating offer, from [`Offer::description`]. /// /// [`Offer::description`]: crate::offers::offer::Offer::description - pub fn description(&$self) -> Option { + pub fn description(&$self) -> Option> { $contents.description() } @@ -250,7 +250,7 @@ macro_rules! invoice_accessors { ($self: ident, $contents: expr) => { /// The issuer of the offer, from [`Offer::issuer`]. /// /// [`Offer::issuer`]: crate::offers::offer::Offer::issuer - pub fn issuer(&$self) -> Option { + pub fn issuer(&$self) -> Option> { $contents.issuer() } @@ -454,7 +454,7 @@ impl InvoiceContents { } } - fn as_tlv_stream(&self) -> PartialInvoiceTlvStreamRef { + fn as_tlv_stream(&self) -> PartialInvoiceTlvStreamRef<'_> { let features = { if self.features == Bolt12InvoiceFeatures::empty() { None @@ -503,7 +503,7 @@ impl InvoiceContents { self.offer.features() } - fn description(&self) -> Option { + fn description(&self) -> Option> { self.offer.description() } @@ -511,7 +511,7 @@ impl InvoiceContents { self.offer.absolute_expiry() } - fn issuer(&self) -> Option { + fn issuer(&self) -> Option> { self.offer.issuer() } @@ -763,7 +763,7 @@ mod tests { ); impl StaticInvoice { - fn as_tlv_stream(&self) -> FullInvoiceTlvStreamRef { + fn as_tlv_stream(&self) -> FullInvoiceTlvStreamRef<'_> { let ( offer_tlv_stream, invoice_tlv_stream, diff --git a/lightning/src/onion_message/async_payments.rs b/lightning/src/onion_message/async_payments.rs index 1f13e68f3d8..877af435bb4 100644 --- a/lightning/src/onion_message/async_payments.rs +++ b/lightning/src/onion_message/async_payments.rs @@ -19,10 +19,10 @@ use crate::prelude::*; use crate::util::ser::{Readable, ReadableArgs, Writeable, Writer}; // TLV record types for the `onionmsg_tlv` TLV stream as defined in BOLT 4. -const OFFER_PATHS_REQ_TLV_TYPE: u64 = 65538; -const OFFER_PATHS_TLV_TYPE: u64 = 65540; -const SERVE_INVOICE_TLV_TYPE: u64 = 65542; -const INVOICE_PERSISTED_TLV_TYPE: u64 = 65544; +const OFFER_PATHS_REQ_TLV_TYPE: u64 = 75540; +const OFFER_PATHS_TLV_TYPE: u64 = 75542; +const SERVE_INVOICE_TLV_TYPE: u64 = 75544; +const INVOICE_PERSISTED_TLV_TYPE: u64 = 75546; const HELD_HTLC_AVAILABLE_TLV_TYPE: u64 = 72; const RELEASE_HELD_HTLC_TLV_TYPE: u64 = 74; @@ -131,7 +131,13 @@ pub enum AsyncPaymentsMessage { /// /// [`Offer::paths`]: crate::offers::offer::Offer::paths #[derive(Clone, Debug)] -pub struct OfferPathsRequest {} +pub struct OfferPathsRequest { + /// The "slot" in the static invoice server's database that this invoice should go into. This + /// allows us as the recipient to replace a specific invoice that is stored by the server, which + /// is useful for limiting the number of invoices stored by the server while also keeping all the + /// invoices persisted with the server fresh. + pub invoice_slot: u16, +} /// [`BlindedMessagePath`]s to be included in an async recipient's [`Offer::paths`], sent by a /// static invoice server in response to an [`OfferPathsRequest`]. @@ -166,11 +172,6 @@ pub struct ServeStaticInvoice { /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice pub forward_invoice_request_path: BlindedMessagePath, - /// The "slot" in the static invoice server's database that this invoice should go into. This - /// allows recipients to replace a specific invoice that is stored by the server, which is useful - /// for limiting the number of invoices stored by the server while also keeping all the invoices - /// persisted with the server fresh. - pub invoice_slot: u16, } /// Confirmation from a static invoice server that a [`StaticInvoice`] was persisted and the @@ -233,7 +234,9 @@ impl OnionMessageContents for ReleaseHeldHtlc { } } -impl_writeable_tlv_based!(OfferPathsRequest, {}); +impl_writeable_tlv_based!(OfferPathsRequest, { + (0, invoice_slot, required), +}); impl_writeable_tlv_based!(OfferPaths, { (0, paths, required_vec), @@ -243,7 +246,6 @@ impl_writeable_tlv_based!(OfferPaths, { impl_writeable_tlv_based!(ServeStaticInvoice, { (0, invoice, required), (2, forward_invoice_request_path, required), - (4, invoice_slot, required), }); impl_writeable_tlv_based!(StaticInvoicePersisted, {}); diff --git a/lightning/src/onion_message/messenger.rs b/lightning/src/onion_message/messenger.rs index 38c8cd304d9..553977dfe18 100644 --- a/lightning/src/onion_message/messenger.rs +++ b/lightning/src/onion_message/messenger.rs @@ -15,9 +15,7 @@ use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::hashes::{Hash, HashEngine}; use bitcoin::secp256k1::{self, PublicKey, Scalar, Secp256k1, SecretKey}; -#[cfg(async_payments)] -use super::async_payments::AsyncPaymentsMessage; -use super::async_payments::AsyncPaymentsMessageHandler; +use super::async_payments::{AsyncPaymentsMessage, AsyncPaymentsMessageHandler}; use super::dns_resolution::{DNSResolverMessage, DNSResolverMessageHandler}; use super::offers::{OffersMessage, OffersMessageHandler}; use super::packet::OnionMessageContents; @@ -26,11 +24,9 @@ use super::packet::{ ForwardControlTlvs, Packet, Payload, ReceiveControlTlvs, BIG_PACKET_HOP_DATA_LEN, SMALL_PACKET_HOP_DATA_LEN, }; -#[cfg(async_payments)] -use crate::blinded_path::message::AsyncPaymentsContext; use crate::blinded_path::message::{ - BlindedMessagePath, DNSResolverContext, ForwardTlvs, MessageContext, MessageForwardNode, - NextMessageHop, OffersContext, ReceiveTlvs, + AsyncPaymentsContext, BlindedMessagePath, DNSResolverContext, ForwardTlvs, MessageContext, + MessageForwardNode, NextMessageHop, OffersContext, ReceiveTlvs, }; use crate::blinded_path::utils; use crate::blinded_path::{IntroductionNode, NodeIdLookUp}; @@ -440,7 +436,6 @@ impl Responder { } /// Converts a [`Responder`] into its inner [`BlindedMessagePath`]. - #[cfg(async_payments)] pub(crate) fn into_blinded_path(self) -> BlindedMessagePath { self.reply_path } @@ -977,7 +972,6 @@ pub enum PeeledOnion { /// Received offers onion message, with decrypted contents, context, and reply path Offers(OffersMessage, Option, Option), /// Received async payments onion message, with decrypted contents, context, and reply path - #[cfg(async_payments)] AsyncPayments(AsyncPaymentsMessage, AsyncPaymentsContext, Option), /// Received DNS resolver onion message, with decrypted contents, context, and reply path DNSResolver(DNSResolverMessage, Option, Option), @@ -1181,7 +1175,6 @@ where (ParsedOnionMessageContents::Offers(msg), None) => { Ok(PeeledOnion::Offers(msg, None, reply_path)) }, - #[cfg(async_payments)] ( ParsedOnionMessageContents::AsyncPayments(msg), Some(MessageContext::AsyncPayments(ctx)), @@ -1678,15 +1671,12 @@ where ); } - #[cfg(async_payments)] - { - for (message, instructions) in self.async_payments_handler.release_pending_messages() { - let _ = self.send_onion_message_internal( - message, - instructions, - format_args!("when sending AsyncPaymentsMessage"), - ); - } + for (message, instructions) in self.async_payments_handler.release_pending_messages() { + let _ = self.send_onion_message_internal( + message, + instructions, + format_args!("when sending AsyncPaymentsMessage"), + ); } // Enqueue any initiating `DNSResolverMessage`s to send. @@ -2098,7 +2088,6 @@ where let _ = self.handle_onion_message_response(msg, instructions); } }, - #[cfg(async_payments)] Ok(PeeledOnion::AsyncPayments(message, context, reply_path)) => { log_receive!(message, reply_path.is_some()); let responder = reply_path.map(Responder::new); diff --git a/lightning/src/onion_message/offers.rs b/lightning/src/onion_message/offers.rs index c4c7774a112..06988d4db8f 100644 --- a/lightning/src/onion_message/offers.rs +++ b/lightning/src/onion_message/offers.rs @@ -16,7 +16,6 @@ use crate::offers::invoice::Bolt12Invoice; use crate::offers::invoice_error::InvoiceError; use crate::offers::invoice_request::InvoiceRequest; use crate::offers::parse::Bolt12ParseError; -#[cfg(async_payments)] use crate::offers::static_invoice::StaticInvoice; use crate::onion_message::messenger::{MessageSendInstructions, Responder, ResponseInstruction}; use crate::onion_message::packet::OnionMessageContents; @@ -30,7 +29,7 @@ use crate::prelude::*; const INVOICE_REQUEST_TLV_TYPE: u64 = 64; const INVOICE_TLV_TYPE: u64 = 66; const INVOICE_ERROR_TLV_TYPE: u64 = 68; -#[cfg(async_payments)] +// Spec'd in https://github.com/lightning/bolts/pull/1149. const STATIC_INVOICE_TLV_TYPE: u64 = 70; /// A handler for an [`OnionMessage`] containing a BOLT 12 Offers message as its payload. @@ -79,7 +78,6 @@ pub enum OffersMessage { /// [`Refund`]: crate::offers::refund::Refund Invoice(Bolt12Invoice), - #[cfg(async_payments)] /// A [`StaticInvoice`] sent in response to an [`InvoiceRequest`]. StaticInvoice(StaticInvoice), @@ -91,9 +89,10 @@ impl OffersMessage { /// Returns whether `tlv_type` corresponds to a TLV record for Offers. pub fn is_known_type(tlv_type: u64) -> bool { match tlv_type { - INVOICE_REQUEST_TLV_TYPE | INVOICE_TLV_TYPE | INVOICE_ERROR_TLV_TYPE => true, - #[cfg(async_payments)] - STATIC_INVOICE_TLV_TYPE => true, + INVOICE_REQUEST_TLV_TYPE + | INVOICE_TLV_TYPE + | INVOICE_ERROR_TLV_TYPE + | STATIC_INVOICE_TLV_TYPE => true, _ => false, } } @@ -102,7 +101,6 @@ impl OffersMessage { match tlv_type { INVOICE_REQUEST_TLV_TYPE => Ok(Self::InvoiceRequest(InvoiceRequest::try_from(bytes)?)), INVOICE_TLV_TYPE => Ok(Self::Invoice(Bolt12Invoice::try_from(bytes)?)), - #[cfg(async_payments)] STATIC_INVOICE_TLV_TYPE => Ok(Self::StaticInvoice(StaticInvoice::try_from(bytes)?)), _ => Err(Bolt12ParseError::Decode(DecodeError::InvalidValue)), } @@ -112,7 +110,6 @@ impl OffersMessage { match &self { OffersMessage::InvoiceRequest(_) => "Invoice Request", OffersMessage::Invoice(_) => "Invoice", - #[cfg(async_payments)] OffersMessage::StaticInvoice(_) => "Static Invoice", OffersMessage::InvoiceError(_) => "Invoice Error", } @@ -128,7 +125,6 @@ impl fmt::Debug for OffersMessage { OffersMessage::Invoice(message) => { write!(f, "{:?}", message.as_tlv_stream()) }, - #[cfg(async_payments)] OffersMessage::StaticInvoice(message) => { write!(f, "{:?}", message) }, @@ -144,7 +140,6 @@ impl OnionMessageContents for OffersMessage { match self { OffersMessage::InvoiceRequest(_) => INVOICE_REQUEST_TLV_TYPE, OffersMessage::Invoice(_) => INVOICE_TLV_TYPE, - #[cfg(async_payments)] OffersMessage::StaticInvoice(_) => STATIC_INVOICE_TLV_TYPE, OffersMessage::InvoiceError(_) => INVOICE_ERROR_TLV_TYPE, } @@ -164,7 +159,6 @@ impl Writeable for OffersMessage { match self { OffersMessage::InvoiceRequest(message) => message.write(w), OffersMessage::Invoice(message) => message.write(w), - #[cfg(async_payments)] OffersMessage::StaticInvoice(message) => message.write(w), OffersMessage::InvoiceError(message) => message.write(w), } diff --git a/lightning/src/onion_message/packet.rs b/lightning/src/onion_message/packet.rs index 301473fba6a..ee41ee98572 100644 --- a/lightning/src/onion_message/packet.rs +++ b/lightning/src/onion_message/packet.rs @@ -12,7 +12,6 @@ use bitcoin::secp256k1::ecdh::SharedSecret; use bitcoin::secp256k1::PublicKey; -#[cfg(async_payments)] use super::async_payments::AsyncPaymentsMessage; use super::dns_resolution::DNSResolverMessage; use super::messenger::CustomOnionMessageHandler; @@ -131,7 +130,6 @@ pub enum ParsedOnionMessageContents { /// A message related to BOLT 12 Offers. Offers(OffersMessage), /// A message related to async payments. - #[cfg(async_payments)] AsyncPayments(AsyncPaymentsMessage), /// A message requesting or providing a DNSSEC proof DNSResolver(DNSResolverMessage), @@ -146,7 +144,6 @@ impl OnionMessageContents for ParsedOnionMessageContent fn tlv_type(&self) -> u64 { match self { &ParsedOnionMessageContents::Offers(ref msg) => msg.tlv_type(), - #[cfg(async_payments)] &ParsedOnionMessageContents::AsyncPayments(ref msg) => msg.tlv_type(), &ParsedOnionMessageContents::DNSResolver(ref msg) => msg.tlv_type(), &ParsedOnionMessageContents::Custom(ref msg) => msg.tlv_type(), @@ -156,7 +153,6 @@ impl OnionMessageContents for ParsedOnionMessageContent fn msg_type(&self) -> String { match self { ParsedOnionMessageContents::Offers(ref msg) => msg.msg_type(), - #[cfg(async_payments)] ParsedOnionMessageContents::AsyncPayments(ref msg) => msg.msg_type(), ParsedOnionMessageContents::DNSResolver(ref msg) => msg.msg_type(), ParsedOnionMessageContents::Custom(ref msg) => msg.msg_type(), @@ -166,7 +162,6 @@ impl OnionMessageContents for ParsedOnionMessageContent fn msg_type(&self) -> &'static str { match self { ParsedOnionMessageContents::Offers(ref msg) => msg.msg_type(), - #[cfg(async_payments)] ParsedOnionMessageContents::AsyncPayments(ref msg) => msg.msg_type(), ParsedOnionMessageContents::DNSResolver(ref msg) => msg.msg_type(), ParsedOnionMessageContents::Custom(ref msg) => msg.msg_type(), @@ -178,7 +173,6 @@ impl Writeable for ParsedOnionMessageContents { fn write(&self, w: &mut W) -> Result<(), io::Error> { match self { ParsedOnionMessageContents::Offers(msg) => msg.write(w), - #[cfg(async_payments)] ParsedOnionMessageContents::AsyncPayments(msg) => msg.write(w), ParsedOnionMessageContents::DNSResolver(msg) => msg.write(w), ParsedOnionMessageContents::Custom(msg) => msg.write(w), @@ -293,7 +287,6 @@ impl message = Some(ParsedOnionMessageContents::Offers(msg)); Ok(true) }, - #[cfg(async_payments)] tlv_type if AsyncPaymentsMessage::is_known_type(tlv_type) => { let msg = AsyncPaymentsMessage::read(msg_reader, tlv_type)?; message = Some(ParsedOnionMessageContents::AsyncPayments(msg)); diff --git a/lightning/src/routing/gossip.rs b/lightning/src/routing/gossip.rs index a9f45f13d4b..5be09d7932b 100644 --- a/lightning/src/routing/gossip.rs +++ b/lightning/src/routing/gossip.rs @@ -1054,7 +1054,7 @@ impl PartialEq for ChannelInfo { impl ChannelInfo { /// Returns a [`DirectedChannelInfo`] for the channel directed to the given `target` from a /// returned `source`, or `None` if `target` is not one of the channel's counterparties. - pub fn as_directed_to(&self, target: &NodeId) -> Option<(DirectedChannelInfo, &NodeId)> { + pub fn as_directed_to(&self, target: &NodeId) -> Option<(DirectedChannelInfo<'_>, &NodeId)> { if self.one_to_two.is_none() || self.two_to_one.is_none() { return None; } @@ -1073,7 +1073,7 @@ impl ChannelInfo { /// Returns a [`DirectedChannelInfo`] for the channel directed from the given `source` to a /// returned `target`, or `None` if `source` is not one of the channel's counterparties. - pub fn as_directed_from(&self, source: &NodeId) -> Option<(DirectedChannelInfo, &NodeId)> { + pub fn as_directed_from(&self, source: &NodeId) -> Option<(DirectedChannelInfo<'_>, &NodeId)> { if self.one_to_two.is_none() || self.two_to_one.is_none() { return None; } diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs index d56d5747e09..e3443b5e45a 100644 --- a/lightning/src/routing/router.rs +++ b/lightning/src/routing/router.rs @@ -23,7 +23,6 @@ use crate::ln::channelmanager::{PaymentId, RecipientOnionFields, MIN_FINAL_CLTV_ use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT}; use crate::ln::onion_utils; use crate::offers::invoice::Bolt12Invoice; -#[cfg(async_payments)] use crate::offers::static_invoice::StaticInvoice; use crate::routing::gossip::{ DirectedChannelInfo, EffectiveCapacity, NetworkGraph, NodeId, ReadOnlyNetworkGraph, @@ -1010,7 +1009,6 @@ impl PaymentParameters { /// Creates parameters for paying to a blinded payee from the provided invoice. Sets /// [`Payee::Blinded::route_hints`], [`Payee::Blinded::features`], and /// [`PaymentParameters::expiry_time`]. - #[cfg(async_payments)] #[rustfmt::skip] pub fn from_static_invoice(invoice: &StaticInvoice) -> Self { Self::blinded(invoice.payment_paths().to_vec()) @@ -1284,7 +1282,7 @@ impl Payee { Self::Blinded { features, .. } => features.as_ref().map_or(false, |f| f.supports_basic_mpp()), } } - fn features(&self) -> Option { + fn features(&self) -> Option> { match self { Self::Clear { features, .. } => features.as_ref().map(|f| FeaturesRef::Bolt11(f)), Self::Blinded { features, .. } => features.as_ref().map(|f| FeaturesRef::Bolt12(f)), diff --git a/lightning/src/routing/scoring.rs b/lightning/src/routing/scoring.rs index 02efb88dbb9..6c111ab475b 100644 --- a/lightning/src/routing/scoring.rs +++ b/lightning/src/routing/scoring.rs @@ -544,7 +544,7 @@ impl ChannelLiquidities { self.0.iter() } - fn entry(&mut self, short_channel_id: u64) -> Entry { + fn entry(&mut self, short_channel_id: u64) -> Entry<'_, u64, ChannelLiquidity, RandomState> { self.0.entry(short_channel_id) } diff --git a/lightning/src/sign/tx_builder.rs b/lightning/src/sign/tx_builder.rs index 6e623d1a7db..cd16b543f4b 100644 --- a/lightning/src/sign/tx_builder.rs +++ b/lightning/src/sign/tx_builder.rs @@ -1,19 +1,129 @@ //! Defines the `TxBuilder` trait, and the `SpecTxBuilder` type +#![allow(dead_code)] +use core::cmp; use core::ops::Deref; use bitcoin::secp256k1::{self, PublicKey, Secp256k1}; use crate::ln::chan_utils::{ - commit_tx_fee_sat, htlc_success_tx_weight, htlc_timeout_tx_weight, - ChannelTransactionParameters, CommitmentTransaction, HTLCOutputInCommitment, + commit_tx_fee_sat, htlc_success_tx_weight, htlc_timeout_tx_weight, htlc_tx_fees_sat, + second_stage_tx_fees_sat, ChannelTransactionParameters, CommitmentTransaction, + HTLCOutputInCommitment, }; use crate::ln::channel::{CommitmentStats, ANCHOR_OUTPUT_VALUE_SATOSHI}; use crate::prelude::*; use crate::types::features::ChannelTypeFeatures; use crate::util::logger::Logger; +pub(crate) struct HTLCAmountDirection { + pub outbound: bool, + pub amount_msat: u64, +} + +impl HTLCAmountDirection { + fn is_dust( + &self, local: bool, feerate_per_kw: u32, broadcaster_dust_limit_satoshis: u64, + channel_type: &ChannelTypeFeatures, + ) -> bool { + let (success_tx_fee_sat, timeout_tx_fee_sat) = + second_stage_tx_fees_sat(channel_type, feerate_per_kw); + let htlc_tx_fee_sat = + if self.outbound == local { timeout_tx_fee_sat } else { success_tx_fee_sat }; + self.amount_msat / 1000 < broadcaster_dust_limit_satoshis + htlc_tx_fee_sat + } +} + +pub(crate) struct NextCommitmentStats { + pub inbound_htlcs_count: usize, + pub inbound_htlcs_value_msat: u64, + pub holder_balance_before_fee_msat: Option, + pub counterparty_balance_before_fee_msat: Option, + pub nondust_htlc_count: usize, + pub commit_tx_fee_sat: u64, + pub dust_exposure_msat: u64, + // If the counterparty sets a feerate on the channel in excess of our dust_exposure_limiting_feerate, + // this should be set to the dust exposure that would result from us adding an additional nondust outbound + // htlc on the counterparty's commitment transaction. + pub extra_nondust_htlc_on_counterparty_tx_dust_exposure_msat: Option, +} + +#[rustfmt::skip] +fn excess_fees_on_counterparty_tx_dust_exposure_msat( + next_commitment_htlcs: &[HTLCAmountDirection], dust_buffer_feerate: u32, + excess_feerate: u32, counterparty_dust_limit_satoshis: u64, dust_htlc_exposure_msat: u64, + channel_type: &ChannelTypeFeatures, +) -> (u64, u64) { + + let on_counterparty_tx_accepted_nondust_htlcs = next_commitment_htlcs.iter().filter(|htlc| htlc.outbound && !htlc.is_dust(false, dust_buffer_feerate, counterparty_dust_limit_satoshis, channel_type)).count(); + let on_counterparty_tx_offered_nondust_htlcs = next_commitment_htlcs.iter().filter(|htlc| !htlc.outbound && !htlc.is_dust(false, dust_buffer_feerate, counterparty_dust_limit_satoshis, channel_type)).count(); + + let commitment_fee_sat = commit_tx_fee_sat(excess_feerate, on_counterparty_tx_accepted_nondust_htlcs + on_counterparty_tx_offered_nondust_htlcs, channel_type); + let second_stage_fees_sat = htlc_tx_fees_sat(excess_feerate, on_counterparty_tx_accepted_nondust_htlcs, on_counterparty_tx_offered_nondust_htlcs, channel_type); + let on_counterparty_tx_dust_exposure_msat = dust_htlc_exposure_msat + (commitment_fee_sat + second_stage_fees_sat) * 1000; + + let extra_htlc_commitment_fee_sat = commit_tx_fee_sat(excess_feerate, on_counterparty_tx_accepted_nondust_htlcs + 1 + on_counterparty_tx_offered_nondust_htlcs, channel_type); + let extra_htlc_second_stage_fees_sat = htlc_tx_fees_sat(excess_feerate, on_counterparty_tx_accepted_nondust_htlcs + 1, on_counterparty_tx_offered_nondust_htlcs, channel_type); + let extra_htlc_dust_exposure_msat = dust_htlc_exposure_msat + (extra_htlc_commitment_fee_sat + extra_htlc_second_stage_fees_sat) * 1000; + + ( + on_counterparty_tx_dust_exposure_msat, + extra_htlc_dust_exposure_msat, + ) +} + +fn subtract_addl_outputs( + is_outbound_from_holder: bool, value_to_self_after_htlcs_msat: Option, + value_to_remote_after_htlcs_msat: Option, channel_type: &ChannelTypeFeatures, +) -> (Option, Option) { + let total_anchors_sat = if channel_type.supports_anchors_zero_fee_htlc_tx() { + ANCHOR_OUTPUT_VALUE_SATOSHI * 2 + } else { + 0 + }; + + // We MUST use checked subs here, as the funder's balance is not guaranteed to be greater + // than or equal to `total_anchors_sat`. + // + // This is because when the remote party sends an `update_fee` message, we build the new + // commitment transaction *before* checking whether the remote party's balance is enough to + // cover the total anchor sum. + + let local_balance_before_fee_msat = if is_outbound_from_holder { + value_to_self_after_htlcs_msat + .and_then(|balance_msat| balance_msat.checked_sub(total_anchors_sat * 1000)) + } else { + value_to_self_after_htlcs_msat + }; + + let remote_balance_before_fee_msat = if !is_outbound_from_holder { + value_to_remote_after_htlcs_msat + .and_then(|balance_msat| balance_msat.checked_sub(total_anchors_sat * 1000)) + } else { + value_to_remote_after_htlcs_msat + }; + + (local_balance_before_fee_msat, remote_balance_before_fee_msat) +} + +fn get_dust_buffer_feerate(feerate_per_kw: u32) -> u32 { + // When calculating our exposure to dust HTLCs, we assume that the channel feerate + // may, at any point, increase by at least 10 sat/vB (i.e 2530 sat/kWU) or 25%, + // whichever is higher. This ensures that we aren't suddenly exposed to significantly + // more dust balance if the feerate increases when we have several HTLCs pending + // which are near the dust limit. + let feerate_plus_quarter = feerate_per_kw.checked_mul(1250).map(|v| v / 1000); + cmp::max(feerate_per_kw.saturating_add(2530), feerate_plus_quarter.unwrap_or(u32::MAX)) +} + pub(crate) trait TxBuilder { + fn get_next_commitment_stats( + &self, local: bool, is_outbound_from_holder: bool, channel_value_satoshis: u64, + value_to_holder_msat: u64, next_commitment_htlcs: &[HTLCAmountDirection], + addl_nondust_htlc_count: usize, feerate_per_kw: u32, + dust_exposure_limiting_feerate: Option, broadcaster_dust_limit_satoshis: u64, + channel_type: &ChannelTypeFeatures, + ) -> NextCommitmentStats; fn commit_tx_fee_sat( &self, feerate_per_kw: u32, nondust_htlc_count: usize, channel_type: &ChannelTypeFeatures, ) -> u64; @@ -25,7 +135,7 @@ pub(crate) trait TxBuilder { &self, local: bool, commitment_number: u64, per_commitment_point: &PublicKey, channel_parameters: &ChannelTransactionParameters, secp_ctx: &Secp256k1, value_to_self_msat: u64, htlcs_in_tx: Vec, feerate_per_kw: u32, - broadcaster_dust_limit_sat: u64, logger: &L, + broadcaster_dust_limit_satoshis: u64, logger: &L, ) -> (CommitmentTransaction, CommitmentStats) where L::Target: Logger; @@ -34,6 +144,115 @@ pub(crate) trait TxBuilder { pub(crate) struct SpecTxBuilder {} impl TxBuilder for SpecTxBuilder { + fn get_next_commitment_stats( + &self, local: bool, is_outbound_from_holder: bool, channel_value_satoshis: u64, + value_to_holder_msat: u64, next_commitment_htlcs: &[HTLCAmountDirection], + addl_nondust_htlc_count: usize, feerate_per_kw: u32, + dust_exposure_limiting_feerate: Option, broadcaster_dust_limit_satoshis: u64, + channel_type: &ChannelTypeFeatures, + ) -> NextCommitmentStats { + let excess_feerate_opt = + feerate_per_kw.checked_sub(dust_exposure_limiting_feerate.unwrap_or(0)); + // Dust exposure is only decoupled from feerate for zero fee commitment channels. + let is_zero_fee_comm = channel_type.supports_anchor_zero_fee_commitments(); + debug_assert_eq!(is_zero_fee_comm, dust_exposure_limiting_feerate.is_none()); + if is_zero_fee_comm { + debug_assert_eq!(feerate_per_kw, 0); + debug_assert_eq!(excess_feerate_opt, Some(0)); + debug_assert_eq!(addl_nondust_htlc_count, 0); + } + + // Calculate inbound htlc count + let inbound_htlcs_count = + next_commitment_htlcs.iter().filter(|htlc| !htlc.outbound).count(); + + // Calculate balances after htlcs + let value_to_counterparty_msat = (channel_value_satoshis * 1000) + .checked_sub(value_to_holder_msat) + .expect("value_to_holder_msat outgrew the value of the channel!"); + let outbound_htlcs_value_msat: u64 = next_commitment_htlcs + .iter() + .filter_map(|htlc| htlc.outbound.then_some(htlc.amount_msat)) + .sum(); + let inbound_htlcs_value_msat: u64 = next_commitment_htlcs + .iter() + .filter_map(|htlc| (!htlc.outbound).then_some(htlc.amount_msat)) + .sum(); + // Note there is no guarantee that the subtractions of the HTLC amounts don't + // overflow, so we do not panic. Instead, we return `None` to signal an overflow + // to channel, and let channel take the appropriate action. + let value_to_holder_after_htlcs_msat = + value_to_holder_msat.checked_sub(outbound_htlcs_value_msat); + let value_to_counterparty_after_htlcs_msat = + value_to_counterparty_msat.checked_sub(inbound_htlcs_value_msat); + + // Subtract the anchors from the channel funder + let (holder_balance_before_fee_msat, counterparty_balance_before_fee_msat) = + subtract_addl_outputs( + is_outbound_from_holder, + value_to_holder_after_htlcs_msat, + value_to_counterparty_after_htlcs_msat, + channel_type, + ); + + // Increment the feerate by a buffer to calculate dust exposure + let dust_buffer_feerate = get_dust_buffer_feerate(feerate_per_kw); + + // Calculate fees on commitment transaction + let nondust_htlc_count = next_commitment_htlcs + .iter() + .filter(|htlc| { + !htlc.is_dust(local, feerate_per_kw, broadcaster_dust_limit_satoshis, channel_type) + }) + .count(); + let commit_tx_fee_sat = commit_tx_fee_sat( + feerate_per_kw, + nondust_htlc_count + addl_nondust_htlc_count, + channel_type, + ); + + // Calculate dust exposure on commitment transaction + let dust_exposure_msat = next_commitment_htlcs + .iter() + .filter_map(|htlc| { + htlc.is_dust( + local, + dust_buffer_feerate, + broadcaster_dust_limit_satoshis, + channel_type, + ) + .then_some(htlc.amount_msat) + }) + .sum(); + + // Count the excess fees on the counterparty's transaction as dust + let (dust_exposure_msat, extra_nondust_htlc_on_counterparty_tx_dust_exposure_msat) = + if let (Some(excess_feerate), false) = (excess_feerate_opt, local) { + let (dust_exposure_msat, extra_nondust_htlc_exposure_msat) = + excess_fees_on_counterparty_tx_dust_exposure_msat( + &next_commitment_htlcs, + dust_buffer_feerate, + excess_feerate, + broadcaster_dust_limit_satoshis, + dust_exposure_msat, + channel_type, + ); + (dust_exposure_msat, Some(extra_nondust_htlc_exposure_msat)) + } else { + (dust_exposure_msat, None) + }; + + NextCommitmentStats { + inbound_htlcs_count, + inbound_htlcs_value_msat, + holder_balance_before_fee_msat, + counterparty_balance_before_fee_msat, + nondust_htlc_count: nondust_htlc_count + addl_nondust_htlc_count, + commit_tx_fee_sat, + dust_exposure_msat, + extra_nondust_htlc_on_counterparty_tx_dust_exposure_msat, + } + } fn commit_tx_fee_sat( &self, feerate_per_kw: u32, nondust_htlc_count: usize, channel_type: &ChannelTypeFeatures, ) -> u64 { @@ -74,7 +293,7 @@ impl TxBuilder for SpecTxBuilder { &self, local: bool, commitment_number: u64, per_commitment_point: &PublicKey, channel_parameters: &ChannelTransactionParameters, secp_ctx: &Secp256k1, value_to_self_msat: u64, mut htlcs_in_tx: Vec, feerate_per_kw: u32, - broadcaster_dust_limit_sat: u64, logger: &L, + broadcaster_dust_limit_satoshis: u64, logger: &L, ) -> (CommitmentTransaction, CommitmentStats) where L::Target: Logger, @@ -95,7 +314,7 @@ impl TxBuilder for SpecTxBuilder { // As required by the spec, round down feerate_per_kw as u64 * htlc_tx_weight / 1000 }; - amount_msat / 1000 < broadcaster_dust_limit_sat + htlc_tx_fee_sat + amount_msat / 1000 < broadcaster_dust_limit_satoshis + htlc_tx_fee_sat }; // Trim dust htlcs @@ -107,7 +326,7 @@ impl TxBuilder for SpecTxBuilder { remote_htlc_total_msat += htlc.amount_msat; } if is_dust(htlc.offered, htlc.amount_msat) { - log_trace!(logger, " ...trimming {} HTLC with value {}sat, hash {}, due to dust limit {}", if htlc.offered == local { "outbound" } else { "inbound" }, htlc.amount_msat / 1000, htlc.payment_hash, broadcaster_dust_limit_sat); + log_trace!(logger, " ...trimming {} HTLC with value {}sat, hash {}, due to dust limit {}", if htlc.offered == local { "outbound" } else { "inbound" }, htlc.amount_msat / 1000, htlc.payment_hash, broadcaster_dust_limit_satoshis); false } else { true @@ -142,13 +361,13 @@ impl TxBuilder for SpecTxBuilder { let mut to_broadcaster_value_sat = if local { value_to_self } else { value_to_remote }; let mut to_countersignatory_value_sat = if local { value_to_remote } else { value_to_self }; - if to_broadcaster_value_sat >= broadcaster_dust_limit_sat { + if to_broadcaster_value_sat >= broadcaster_dust_limit_satoshis { log_trace!(logger, " ...including {} output with value {}", if local { "to_local" } else { "to_remote" }, to_broadcaster_value_sat); } else { to_broadcaster_value_sat = 0; } - if to_countersignatory_value_sat >= broadcaster_dust_limit_sat { + if to_countersignatory_value_sat >= broadcaster_dust_limit_satoshis { log_trace!(logger, " ...including {} output with value {}", if local { "to_remote" } else { "to_local" }, to_countersignatory_value_sat); } else { to_countersignatory_value_sat = 0; diff --git a/lightning/src/sync/fairrwlock.rs b/lightning/src/sync/fairrwlock.rs index d97370090cd..6ebb196810d 100644 --- a/lightning/src/sync/fairrwlock.rs +++ b/lightning/src/sync/fairrwlock.rs @@ -26,14 +26,14 @@ impl FairRwLock { // Note that all atomic accesses are relaxed, as we do not rely on the atomics here for any // ordering at all, instead relying on the underlying RwLock to provide ordering of unrelated // memory. - pub fn write(&self) -> LockResult> { + pub fn write(&self) -> LockResult> { self.waiting_writers.fetch_add(1, Ordering::Relaxed); let res = self.lock.write(); self.waiting_writers.fetch_sub(1, Ordering::Relaxed); res } - pub fn read(&self) -> LockResult> { + pub fn read(&self) -> LockResult> { if self.waiting_writers.load(Ordering::Relaxed) != 0 { let _write_queue_lock = self.lock.write(); } diff --git a/lightning/src/util/indexed_map.rs b/lightning/src/util/indexed_map.rs index 34860e3d68a..3f8b3578086 100644 --- a/lightning/src/util/indexed_map.rs +++ b/lightning/src/util/indexed_map.rs @@ -111,7 +111,7 @@ impl IndexedMap { } /// Returns an iterator which iterates over the `key`/`value` pairs in a given range. - pub fn range>(&mut self, range: R) -> Range { + pub fn range>(&mut self, range: R) -> Range<'_, K, V> { self.keys.sort_unstable(); let start = match range.start_bound() { Bound::Unbounded => 0, diff --git a/lightning/src/util/ser.rs b/lightning/src/util/ser.rs index ac2b529f0bd..b2521d93109 100644 --- a/lightning/src/util/ser.rs +++ b/lightning/src/util/ser.rs @@ -1084,6 +1084,7 @@ impl_for_vec!((A, B), A, B); impl_for_vec!(SerialId); impl_for_vec!(NegotiatedTxInput); impl_for_vec!(InteractiveTxOutput); +impl_for_vec!(crate::ln::our_peer_storage::PeerStorageMonitorHolder); impl_writeable_for_vec!(&crate::routing::router::BlindedTail); impl_readable_for_vec!(crate::routing::router::BlindedTail); impl_for_vec!(crate::routing::router::TrampolineHop); @@ -1676,63 +1677,6 @@ impl Readable for Duration { } } -/// A wrapper for a `Transaction` which can only be constructed with [`TransactionU16LenLimited::new`] -/// if the `Transaction`'s consensus-serialized length is <= u16::MAX. -/// -/// Use [`TransactionU16LenLimited::into_transaction`] to convert into the contained `Transaction`. -#[derive(Clone, Debug, Hash, PartialEq, Eq)] -pub struct TransactionU16LenLimited(Transaction); - -impl TransactionU16LenLimited { - /// Constructs a new `TransactionU16LenLimited` from a `Transaction` only if it's consensus- - /// serialized length is <= u16::MAX. - pub fn new(transaction: Transaction) -> Result { - if transaction.serialized_length() > (u16::MAX as usize) { - Err(()) - } else { - Ok(Self(transaction)) - } - } - - /// Consumes this `TransactionU16LenLimited` and returns its contained `Transaction`. - pub fn into_transaction(self) -> Transaction { - self.0 - } - - /// Returns a reference to the contained `Transaction` - pub fn as_transaction(&self) -> &Transaction { - &self.0 - } -} - -impl Writeable for Option { - fn write(&self, w: &mut W) -> Result<(), io::Error> { - match self { - Some(tx) => { - (tx.0.serialized_length() as u16).write(w)?; - tx.0.write(w) - }, - None => 0u16.write(w), - } - } -} - -impl Readable for Option { - fn read(r: &mut R) -> Result { - let len = ::read(r)?; - if len == 0 { - return Ok(None); - } - let mut tx_reader = FixedLengthReader::new(r, len as u64); - let tx: Transaction = Readable::read(&mut tx_reader)?; - if tx_reader.bytes_remain() { - Err(DecodeError::BadLengthDescriptor) - } else { - Ok(Some(TransactionU16LenLimited(tx))) - } - } -} - impl Writeable for ClaimId { fn write(&self, writer: &mut W) -> Result<(), io::Error> { self.0.write(writer) diff --git a/lightning/src/util/ser_macros.rs b/lightning/src/util/ser_macros.rs index c3cf2044deb..ea7a3e8a2a2 100644 --- a/lightning/src/util/ser_macros.rs +++ b/lightning/src/util/ser_macros.rs @@ -1677,9 +1677,9 @@ mod tests { } // TLVs from the BOLT test cases which should not decode as either n1 or n2 - do_test!(concat!("fd01"), ShortRead); + do_test!("fd01", ShortRead); do_test!(concat!("fd0001", "00"), InvalidValue); - do_test!(concat!("fd0101"), ShortRead); + do_test!("fd0101", ShortRead); do_test!(concat!("0f", "fd"), ShortRead); do_test!(concat!("0f", "fd26"), ShortRead); do_test!(concat!("0f", "fd2602"), ShortRead); @@ -1763,7 +1763,7 @@ mod tests { }; } - do_test!(concat!(""), None, None, None, None); + do_test!("", None, None, None, None); do_test!(concat!("21", "00"), None, None, None, None); do_test!(concat!("fd0201", "00"), None, None, None, None); do_test!(concat!("fd00fd", "00"), None, None, None, None); diff --git a/lightning/src/util/test_channel_signer.rs b/lightning/src/util/test_channel_signer.rs index e619069af50..04b24825bd4 100644 --- a/lightning/src/util/test_channel_signer.rs +++ b/lightning/src/util/test_channel_signer.rs @@ -145,7 +145,7 @@ impl TestChannelSigner { } #[cfg(any(test, feature = "_test_utils"))] - pub fn get_enforcement_state(&self) -> MutexGuard { + pub fn get_enforcement_state(&self) -> MutexGuard<'_, EnforcementState> { self.state.lock().unwrap() }