shellcheck ci/ci-tests.sh
- name: Run CI script
shell: bash # Default on Winblows is powershell
- run: ./ci/ci-tests.sh
+ run: CI_MINIMIZE_DISK_USAGE=1 ./ci/ci-tests.sh
coverage:
strategy:
+# 0.0.118 - Oct 23, 2023 - "Just the Twelve Sinks"
+
+## API Updates
+ * BOLT12 sending and receiving is now supported as an alpha feature. You may
+ run into unexpected issues and will need to have a direct connection with
+ the offer's blinded path introduction points as messages are not yet routed.
+ We are seeking feedback from early testers (#2578, #2039).
+ * `ConfirmationTarget` has been rewritten to provide information about the
+ specific use LDK needs the feerate estimate for, rather than the generic
+ low-, medium-, and high-priority estimates. This allows LDK users to more
+ accurately target their feerate estimates (#2660). For those wishing to
+ retain their existing behavior, see the table below for conversion.
+ * `ChainHash` is now used in place of `BlockHash` where it represents the
+ genesis block (#2662).
+ * `lightning-invoice` payment utilities now take a `Deref` to
+ `AChannelManager` (#2652).
+ * `peel_onion` is provided to statelessly decode an `OnionMessage` (#2599).
+ * `ToSocketAddrs` + `Display` are now impl'd for `SocketAddress` (#2636, #2670)
+ * `Display` is now implemented for `OutPoint` (#2649).
+ * `Features::from_be_bytes` is now provided (#2640).
+
+For those moving to the new `ConfirmationTarget`, the new variants in terms of
+the old mempool/low/medium/high priorities are as follows:
+ * `OnChainSweep` = `HighPriority`
+ * `MaxAllowedNonAnchorChannelRemoteFee` = `max(25 * 250, HighPriority * 10)`
+ * `MinAllowedAnchorChannelRemoteFee` = `MempoolMinimum`
+ * `MinAllowedNonAnchorChannelRemoteFee` = `Background - 250`
+ * `AnchorChannelFee` = `Background`
+ * `NonAnchorChannelFee` = `Normal`
+ * `ChannelCloseMinimum` = `Background`
+
+## Bug Fixes
+ * Calling `ChannelManager::close_channel[_with_feerate_and_script]` on a
+ channel which did not exist would immediately hang holding several key
+ `ChannelManager`-internal locks (#2657).
+ * Channel information updates received from a failing HTLC are no longer
+ applied to our `NetworkGraph`. This prevents a node which we attempted to
+ route a payment through from being able to learn the sender of the payment.
+ In some rare cases, this may result in marginally reduced payment success
+ rates (#2666).
+ * Anchor outputs are now properly considered when calculating the amount
+ available to send in HTLCs. This can prevent force-closes in anchor channels
+ when sending payments which overflow the available balance (#2674).
+ * A peer that sends an `update_fulfill_htlc` message for a forwarded HTLC,
+ then reconnects prior to sending a `commitment_signed` (thus retransmitting
+ their `update_fulfill_htlc`) may result in the channel stalling and being
+ unable to make progress (#2661).
+ * In exceedingly rare circumstances, messages intended to be sent to a peer
+ prior to reconnection can be sent after reconnection. This could result in
+ undefined channel state and force-closes (#2663).
+
+## Backwards Compatibility
+
+ * Creating a blinded path to receive a payment then downgrading to LDK prior to
+ 0.0.117 may result in failure to receive the payment (#2413).
+ * Calling `ChannelManager::pay_for_offer` or
+ `ChannelManager::create_refund_builder` may prevent downgrading to LDK prior
+ to 0.0.118 until the payment times out and has been removed (#2039).
+
+## Node Compatibility
+ * LDK now sends a bogus `channel_reestablish` message to peers when they ask to
+ resume an unknown channel. This should cause LND nodes to force-close and
+ broadcast the latest channel state to the chain. In order to trigger this
+ when we wish to force-close a channel, LDK now disconnects immediately after
+ sending a channel-closing `error` message. This should result in cooperative
+ peers also working to confirm the latest commitment transaction when we wish
+ to force-close (#2658).
+
+## Security
+0.0.118 expands mitigations against transaction cycling attacks to non-anchor
+channels, though note that no mitigations which exist today are considered robust
+to prevent the class of attacks.
+ * In order to mitigate against transaction cycling attacks, non-anchor HTLC
+ transactions are now properly re-signed before broadcasting (#2667).
+
+In total, this release features 61 files changed, 3470 insertions, 1503
+deletions in 85 commits from 12 authors, in alphabetical order:
+ * Antonio Yang
+ * Elias Rohrer
+ * Evan Feenstra
+ * Fedeparma74
+ * Gursharan Singh
+ * Jeffrey Czyz
+ * Matt Corallo
+ * Sergi Delgado Segura
+ * Vladimir Fomene
+ * Wilmer Paulino
+ * benthecarman
+ * slanesuke
+
+
# 0.0.117 - Oct 3, 2023 - "Everything but the Twelve Sinks"
## API Updates
echo -e "\n\nTest Custom Message Macros"
pushd lightning-custom-message
cargo test --verbose --color always
+ [ "$CI_MINIMIZE_DISK_USAGE" != "" ] && cargo clean
popd
fi
echo -e "\n\nTesting no-std flags in various combinations"
for DIR in lightning lightning-invoice lightning-rapid-gossip-sync; do
- pushd $DIR
- cargo test --verbose --color always --no-default-features --features no-std
+ [ "$RUSTC_MINOR_VERSION" -gt 50 ] && cargo test -p $DIR --verbose --color always --no-default-features --features no-std
# check if there is a conflict between no-std and the default std feature
- cargo test --verbose --color always --features no-std
+ [ "$RUSTC_MINOR_VERSION" -gt 50 ] && cargo test -p $DIR --verbose --color always --features no-std
+done
+for DIR in lightning lightning-invoice lightning-rapid-gossip-sync; do
# check if there is a conflict between no-std and the c_bindings cfg
- RUSTFLAGS="--cfg=c_bindings" cargo test --verbose --color always --no-default-features --features=no-std
- popd
+ [ "$RUSTC_MINOR_VERSION" -gt 50 ] && RUSTFLAGS="--cfg=c_bindings" cargo test -p $DIR --verbose --color always --no-default-features --features=no-std
done
+RUSTFLAGS="--cfg=c_bindings" cargo test --verbose --color always
+
# Note that outbound_commitment_test only runs in this mode because of hardcoded signature values
pushd lightning
cargo test --verbose --color always --no-default-features --features=std,_test_vectors
[ "$RUSTC_MINOR_VERSION" -lt 60 ] && cargo update -p memchr --precise "2.5.0" --verbose
cargo check --verbose --color always
fi
+[ "$CI_MINIMIZE_DISK_USAGE" != "" ] && cargo clean
popd
# Test that we can build downstream code with only the "release pins".
pushd msrv-no-dev-deps-check
PIN_RELEASE_DEPS
cargo check
+[ "$CI_MINIMIZE_DISK_USAGE" != "" ] && cargo clean
popd
if [ -f "$(which arm-none-eabi-gcc)" ]; then
pushd no-std-check
cargo build --target=thumbv7m-none-eabi
+ [ "$CI_MINIMIZE_DISK_USAGE" != "" ] && cargo clean
popd
fi
// always return a HighPriority feerate here which is >= the maximum Normal feerate and a
// Background feerate which is <= the minimum Normal feerate.
match conf_target {
- ConfirmationTarget::HighPriority => MAX_FEE,
- ConfirmationTarget::Background|ConfirmationTarget::MempoolMinimum => 253,
- ConfirmationTarget::Normal => cmp::min(self.ret_val.load(atomic::Ordering::Acquire), MAX_FEE),
+ ConfirmationTarget::MaxAllowedNonAnchorChannelRemoteFee => MAX_FEE * 10,
+ ConfirmationTarget::OnChainSweep => MAX_FEE,
+ ConfirmationTarget::ChannelCloseMinimum|ConfirmationTarget::AnchorChannelFee|ConfirmationTarget::MinAllowedAnchorChannelRemoteFee|ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee => 253,
+ ConfirmationTarget::NonAnchorChannelFee => cmp::min(self.ret_val.load(atomic::Ordering::Acquire), MAX_FEE),
}
}
}
}
#[inline]
-pub fn do_test<Out: Output>(data: &[u8], underlying_out: Out) {
+pub fn do_test<Out: Output>(data: &[u8], underlying_out: Out, anchors: bool) {
let out = SearchingOutput::new(underlying_out);
let broadcast = Arc::new(TestBroadcaster{});
let router = FuzzRouter {};
let mut config = UserConfig::default();
config.channel_config.forwarding_fee_proportional_millionths = 0;
config.channel_handshake_config.announced_channel = true;
+ if anchors {
+ config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;
+ config.manually_accept_inbound_channels = true;
+ }
let network = Network::Bitcoin;
let best_block_timestamp = genesis_block(network).header.time;
let params = ChainParameters {
let mut config = UserConfig::default();
config.channel_config.forwarding_fee_proportional_millionths = 0;
config.channel_handshake_config.announced_channel = true;
+ if anchors {
+ config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;
+ config.manually_accept_inbound_channels = true;
+ }
let mut monitors = HashMap::new();
let mut old_monitors = $old_monitors.latest_monitors.lock().unwrap();
let mut channel_txn = Vec::new();
macro_rules! make_channel {
- ($source: expr, $dest: expr, $chan_id: expr) => { {
+ ($source: expr, $dest: expr, $dest_keys_manager: expr, $chan_id: expr) => { {
$source.peer_connected(&$dest.get_our_node_id(), &Init {
features: $dest.init_features(), networks: None, remote_network_address: None
}, true).unwrap();
$dest.handle_open_channel(&$source.get_our_node_id(), &open_channel);
let accept_channel = {
+ if anchors {
+ let events = $dest.get_and_clear_pending_events();
+ assert_eq!(events.len(), 1);
+ if let events::Event::OpenChannelRequest {
+ ref temporary_channel_id, ref counterparty_node_id, ..
+ } = events[0] {
+ let mut random_bytes = [0u8; 16];
+ random_bytes.copy_from_slice(&$dest_keys_manager.get_secure_random_bytes()[..16]);
+ let user_channel_id = u128::from_be_bytes(random_bytes);
+ $dest.accept_inbound_channel(
+ temporary_channel_id,
+ counterparty_node_id,
+ user_channel_id,
+ ).unwrap();
+ } else { panic!("Wrong event type"); }
+ }
let events = $dest.get_and_clear_pending_msg_events();
assert_eq!(events.len(), 1);
if let events::MessageSendEvent::SendAcceptChannel { ref msg, .. } = events[0] {
let mut nodes = [node_a, node_b, node_c];
- let chan_1_funding = make_channel!(nodes[0], nodes[1], 0);
- let chan_2_funding = make_channel!(nodes[1], nodes[2], 1);
+ let chan_1_funding = make_channel!(nodes[0], nodes[1], keys_manager_b, 0);
+ let chan_2_funding = make_channel!(nodes[1], nodes[2], keys_manager_c, 1);
for node in nodes.iter() {
confirm_txn!(node);
0x6d => { send_hop_payment(&nodes[2], &nodes[1], chan_b, &nodes[0], chan_a, 1, &mut payment_id, &mut payment_idx); },
0x80 => {
- let max_feerate = last_htlc_clear_fee_a * FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE as u32;
+ let mut max_feerate = last_htlc_clear_fee_a;
+ if !anchors {
+ max_feerate *= FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE as u32;
+ }
if fee_est_a.ret_val.fetch_add(250, atomic::Ordering::AcqRel) + 250 > max_feerate {
fee_est_a.ret_val.store(max_feerate, atomic::Ordering::Release);
}
0x81 => { fee_est_a.ret_val.store(253, atomic::Ordering::Release); nodes[0].maybe_update_chan_fees(); },
0x84 => {
- let max_feerate = last_htlc_clear_fee_b * FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE as u32;
+ let mut max_feerate = last_htlc_clear_fee_b;
+ if !anchors {
+ max_feerate *= FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE as u32;
+ }
if fee_est_b.ret_val.fetch_add(250, atomic::Ordering::AcqRel) + 250 > max_feerate {
fee_est_b.ret_val.store(max_feerate, atomic::Ordering::Release);
}
0x85 => { fee_est_b.ret_val.store(253, atomic::Ordering::Release); nodes[1].maybe_update_chan_fees(); },
0x88 => {
- let max_feerate = last_htlc_clear_fee_c * FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE as u32;
+ let mut max_feerate = last_htlc_clear_fee_c;
+ if !anchors {
+ max_feerate *= FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE as u32;
+ }
if fee_est_c.ret_val.fetch_add(250, atomic::Ordering::AcqRel) + 250 > max_feerate {
fee_est_c.ret_val.store(max_feerate, atomic::Ordering::Release);
}
}
pub fn chanmon_consistency_test<Out: Output>(data: &[u8], out: Out) {
- do_test(data, out);
+ do_test(data, out.clone(), false);
+ do_test(data, out, true);
}
#[no_mangle]
pub extern "C" fn chanmon_consistency_run(data: *const u8, datalen: usize) {
- do_test(unsafe { std::slice::from_raw_parts(data, datalen) }, test_logger::DevNull{});
+ do_test(unsafe { std::slice::from_raw_parts(data, datalen) }, test_logger::DevNull{}, false);
+ do_test(unsafe { std::slice::from_raw_parts(data, datalen) }, test_logger::DevNull{}, true);
}
// 030012 - inbound read from peer id 0 of len 18
// 0147 03000000000000000000000000000000 - message header indicating message length 327
// 0300fe - inbound read from peer id 0 of len 254
- // 0020 7500000000000000000000000000000000000000000000000000000000000000 ff4f00f805273c1b203bb5ebf8436bfde57b3be8c2f5e95d9491dbb181909679 000000000000c350 0000000000000000 0000000000000162 ffffffffffffffff 0000000000000222 0000000000000000 000000fd 0006 01e3 030000000000000000000000000000000000000000000000000000000000000001 030000000000000000000000000000000000000000000000000000000000000002 030000000000000000000000000000000000000000000000000000000000000003 030000000000000000000000000000000000000000000000000000000000000004 - beginning of open_channel message
+ // 0020 6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000 ff4f00f805273c1b203bb5ebf8436bfde57b3be8c2f5e95d9491dbb181909679 000000000000c350 0000000000000000 0000000000000162 ffffffffffffffff 0000000000000222 0000000000000000 000000fd 0006 01e3 030000000000000000000000000000000000000000000000000000000000000001 030000000000000000000000000000000000000000000000000000000000000002 030000000000000000000000000000000000000000000000000000000000000003 030000000000000000000000000000000000000000000000000000000000000004 - beginning of open_channel message
// 030059 - inbound read from peer id 0 of len 89
// 030000000000000000000000000000000000000000000000000000000000000005 020900000000000000000000000000000000000000000000000000000000000000 01 0000 01021000 03000000000000000000000000000000 - rest of open_channel and mac
//
// - client now fails the HTLC backwards as it was unable to extract the payment preimage (CHECK 9 duplicate and CHECK 10)
let logger = Arc::new(TrackingLogger { lines: Mutex::new(HashMap::new()) });
- super::do_test(&::hex::decode("01000000000000000000000000000000000000000000000000000000000000000000000001000300000000000000000000000000000000000000000000000000000000000000020300320003000000000000000000000000000000000000000000000000000000000000000203000000000000000000000000000000030012001003000000000000000000000000000000030020001000021aaa0008aaaaaaaaaaaa9aaa030000000000000000000000000000000300120147030000000000000000000000000000000300fe00207500000000000000000000000000000000000000000000000000000000000000ff4f00f805273c1b203bb5ebf8436bfde57b3be8c2f5e95d9491dbb181909679000000000000c35000000000000000000000000000000162ffffffffffffffff00000000000002220000000000000000000000fd000601e3030000000000000000000000000000000000000000000000000000000000000001030000000000000000000000000000000000000000000000000000000000000002030000000000000000000000000000000000000000000000000000000000000003030000000000000000000000000000000000000000000000000000000000000004030059030000000000000000000000000000000000000000000000000000000000000005020900000000000000000000000000000000000000000000000000000000000000010000010210000300000000000000000000000000000000fd00fd0300120084030000000000000000000000000000000300940022ff4f00f805273c1b203bb5ebf8436bfde57b3be8c2f5e95d9491dbb1819096793d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000210100000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000c005e020000000100000000000000000000000000000000000000000000000000000000000000000000000000ffffffff0150c3000000000000220020ae00000000000000000000000000000000000000000000000000000000000000000000000c00000c00000c00000c00000c00000c00000c00000c00000c00000c00000c00000c000003001200430300000000000000000000000000000003005300243d0000000000000000000000000000000000000000000000000000000000000002080000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000010301320003000000000000000000000000000000000000000000000000000000000000000703000000000000000000000000000000030142000302000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003000000000000000000000000000000030112001001000000000000000000000000000000030120001000021aaa0008aaaaaaaaaaaa9aaa01000000000000000000000000000000050103020000000000000000000000000000000000000000000000000000000000000000c3500003e800fd0301120112010000000000000000000000000000000301ff00210000000000000000000000000000000000000000000000000000000000000e05000000000000016200000000004c4b4000000000000003e800000000000003e80000000203f000050300000000000000000000000000000000000000000000000000000000000001000300000000000000000000000000000000000000000000000000000000000002000300000000000000000000000000000000000000000000000000000000000003000300000000000000000000000000000000000000000000000000000000000004000300000000000000000000000000000000000000000000000000000000000005000266000000000000000000000000000003012300000000000000000000000000000000000000010000000000000000000000000000000a00fd00fd03011200620100000000000000000000000000000003017200233a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007c0001000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000b03011200430100000000000000000000000000000003015300243a000000000000000000000000000000000000000000000000000000000000000267000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003001205ac030000000000000000000000000000000300ff00803d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e80ff00000000000000000000000000000000000000000000000000000000000000000003f00003000000000000000000000000000000000000000000000000000000000000055511020203e80401a0060800000e00000100000a00000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300c1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffab000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000fd03001200640300000000000000000000000000000003007400843d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030010000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000900000000000000000000000000000000000000000000000000000000000000020b00000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000700fd00fd03011200640100000000000000000000000000000003017400843a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006a000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003011200630100000000000000000000000000000003017300853a00000000000000000000000000000000000000000000000000000000000000660000000000000000000000000000000000000000000000000000000000000002640000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000030112004a0100000000000000000000000000000003015a00823a000000000000000000000000000000000000000000000000000000000000000000000000000000ff008888888888888888888888888888888888888888888888888888888888880100000000000000000000000000000003011200640100000000000000000000000000000003017400843a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003011200630100000000000000000000000000000003017300853a0000000000000000000000000000000000000000000000000000000000000067000000000000000000000000000000000000000000000000000000000000000265000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003001205ac030000000000000000000000000000000300ff00803d0000000000000000000000000000000000000000000000000000000000000000000000000000010000000000003e80ff00000000000000000000000000000000000000000000000000000000000000000003f00003000000000000000000000000000000000000000000000000000000000000055511020203e80401a0060800000e00000100000a00000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300c1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffab000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000fd03001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000020a000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001200640300000000000000000000000000000003007400843d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c3010000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000b00000000000000000000000000000000000000000000000000000000000000020d00000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000700fd00fd03011200640100000000000000000000000000000003017400843a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000039000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003011200630100000000000000000000000000000003017300853a00000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000002700000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000030112002c0100000000000000000000000000000003013c00833a00000000000000000000000000000000000000000000000000000000000000000000000000000100000100000000000000000000000000000003011200640100000000000000000000000000000003017400843a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000039000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003011200630100000000000000000000000000000003017300853a000000000000000000000000000000000000000000000000000000000000006500000000000000000000000000000000000000000000000000000000000000027100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000703001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000020c000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001200640300000000000000000000000000000003007400843d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000032010000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001205ac030000000000000000000000000000000300ff00803d00000000000000000000000000000000000000000000000000000000000000000000000000000200000000000b0838ff00000000000000000000000000000000000000000000000000000000000000000003f0000300000000000000000000000000000000000000000000000000000000000005551202030927c00401a0060800000e00000100000a00000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300c1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff53000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000fd03001200a4030000000000000000000000000000000300b400843d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007501000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006705000000000000000000000000000000000000000000000000000000000000060300000000000000000000000000000003001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000d00000000000000000000000000000000000000000000000000000000000000020f00000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000700fd00fd0c007d02000000013a000000000000000000000000000000000000000000000000000000000000000000000000000000800258020000000000002200204b0000000000000000000000000000000000000000000000000000000000000014c00000000000001600142800000000000000000000000000000000000000050000200c005e0200000001730000000000000000000000000000000000000000000000000000000000000000000000000000000001a701000000000000220020b200000000000000000000000000000000000000000000000000000000000000000000000c00000c00000c00000c00000c000007").unwrap(), &(Arc::clone(&logger) as Arc<dyn Logger>));
+ super::do_test(&::hex::decode("01000000000000000000000000000000000000000000000000000000000000000000000001000300000000000000000000000000000000000000000000000000000000000000020300320003000000000000000000000000000000000000000000000000000000000000000203000000000000000000000000000000030012001003000000000000000000000000000000030020001000021aaa0008aaaaaaaaaaaa9aaa030000000000000000000000000000000300120147030000000000000000000000000000000300fe00206fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000ff4f00f805273c1b203bb5ebf8436bfde57b3be8c2f5e95d9491dbb181909679000000000000c35000000000000000000000000000000162ffffffffffffffff00000000000002220000000000000000000000fd000601e3030000000000000000000000000000000000000000000000000000000000000001030000000000000000000000000000000000000000000000000000000000000002030000000000000000000000000000000000000000000000000000000000000003030000000000000000000000000000000000000000000000000000000000000004030059030000000000000000000000000000000000000000000000000000000000000005020900000000000000000000000000000000000000000000000000000000000000010000010210000300000000000000000000000000000000fd00fd0300120084030000000000000000000000000000000300940022ff4f00f805273c1b203bb5ebf8436bfde57b3be8c2f5e95d9491dbb1819096793d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000210100000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000c005e020000000100000000000000000000000000000000000000000000000000000000000000000000000000ffffffff0150c3000000000000220020ae00000000000000000000000000000000000000000000000000000000000000000000000c00000c00000c00000c00000c00000c00000c00000c00000c00000c00000c00000c000003001200430300000000000000000000000000000003005300243d0000000000000000000000000000000000000000000000000000000000000002080000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000010301320003000000000000000000000000000000000000000000000000000000000000000703000000000000000000000000000000030142000302000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003000000000000000000000000000000030112001001000000000000000000000000000000030120001000021aaa0008aaaaaaaaaaaa9aaa01000000000000000000000000000000050103020000000000000000000000000000000000000000000000000000000000000000c3500003e800fd0301120112010000000000000000000000000000000301ff00210000000000000000000000000000000000000000000000000000000000000e05000000000000016200000000004c4b4000000000000003e800000000000003e80000000203f000050300000000000000000000000000000000000000000000000000000000000001000300000000000000000000000000000000000000000000000000000000000002000300000000000000000000000000000000000000000000000000000000000003000300000000000000000000000000000000000000000000000000000000000004000300000000000000000000000000000000000000000000000000000000000005000266000000000000000000000000000003012300000000000000000000000000000000000000010000000000000000000000000000000a00fd00fd03011200620100000000000000000000000000000003017200233a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007c0001000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000b03011200430100000000000000000000000000000003015300243a000000000000000000000000000000000000000000000000000000000000000267000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003001205ac030000000000000000000000000000000300ff00803d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e80ff00000000000000000000000000000000000000000000000000000000000000000003f00003000000000000000000000000000000000000000000000000000000000000055511020203e80401a0060800000e00000100000a00000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300c1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffab000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000fd03001200640300000000000000000000000000000003007400843d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030010000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000900000000000000000000000000000000000000000000000000000000000000020b00000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000700fd00fd03011200640100000000000000000000000000000003017400843a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006a000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003011200630100000000000000000000000000000003017300853a00000000000000000000000000000000000000000000000000000000000000660000000000000000000000000000000000000000000000000000000000000002640000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000030112004a0100000000000000000000000000000003015a00823a000000000000000000000000000000000000000000000000000000000000000000000000000000ff008888888888888888888888888888888888888888888888888888888888880100000000000000000000000000000003011200640100000000000000000000000000000003017400843a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003011200630100000000000000000000000000000003017300853a0000000000000000000000000000000000000000000000000000000000000067000000000000000000000000000000000000000000000000000000000000000265000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003001205ac030000000000000000000000000000000300ff00803d0000000000000000000000000000000000000000000000000000000000000000000000000000010000000000003e80ff00000000000000000000000000000000000000000000000000000000000000000003f00003000000000000000000000000000000000000000000000000000000000000055511020203e80401a0060800000e00000100000a00000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300c1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffab000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000fd03001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000020a000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001200640300000000000000000000000000000003007400843d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c3010000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000b00000000000000000000000000000000000000000000000000000000000000020d00000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000700fd00fd03011200640100000000000000000000000000000003017400843a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000039000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003011200630100000000000000000000000000000003017300853a00000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000002700000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000030112002c0100000000000000000000000000000003013c00833a00000000000000000000000000000000000000000000000000000000000000000000000000000100000100000000000000000000000000000003011200640100000000000000000000000000000003017400843a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000039000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003011200630100000000000000000000000000000003017300853a000000000000000000000000000000000000000000000000000000000000006500000000000000000000000000000000000000000000000000000000000000027100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000703001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000020c000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001200640300000000000000000000000000000003007400843d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000032010000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001205ac030000000000000000000000000000000300ff00803d00000000000000000000000000000000000000000000000000000000000000000000000000000200000000000b0838ff00000000000000000000000000000000000000000000000000000000000000000003f0000300000000000000000000000000000000000000000000000000000000000005551202030927c00401a0060800000e00000100000a00000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300c1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff53000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000fd03001200a4030000000000000000000000000000000300b400843d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007501000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006705000000000000000000000000000000000000000000000000000000000000060300000000000000000000000000000003001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000d00000000000000000000000000000000000000000000000000000000000000020f00000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000700fd00fd0c007d02000000013a000000000000000000000000000000000000000000000000000000000000000000000000000000800258020000000000002200204b0000000000000000000000000000000000000000000000000000000000000014c00000000000001600142800000000000000000000000000000000000000050000200c005e0200000001730000000000000000000000000000000000000000000000000000000000000000000000000000000001a701000000000000220020b200000000000000000000000000000000000000000000000000000000000000000000000c00000c00000c00000c00000c000007").unwrap(), &(Arc::clone(&logger) as Arc<dyn Logger>));
let log_entries = logger.lines.lock().unwrap();
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)); // 1
use bitcoin::secp256k1::schnorr;
use lightning::sign::{Recipient, KeyMaterial, EntropySource, NodeSigner, SignerProvider};
+use lightning::ln::features::InitFeatures;
use lightning::ln::msgs::{self, DecodeError, OnionMessageHandler};
use lightning::ln::script::ShutdownScript;
use lightning::offers::invoice::UnsignedBolt12Invoice;
use lightning::util::test_channel_signer::TestChannelSigner;
use lightning::util::logger::Logger;
use lightning::util::ser::{Readable, Writeable, Writer};
-use lightning::onion_message::{CustomOnionMessageContents, CustomOnionMessageHandler, Destination, MessageRouter, OffersMessage, OffersMessageHandler, OnionMessagePath, OnionMessenger};
+use lightning::onion_message::{CustomOnionMessageHandler, Destination, MessageRouter, OffersMessage, OffersMessageHandler, OnionMessageContents, OnionMessagePath, OnionMessenger, PendingOnionMessage};
use crate::utils::test_logger;
&keys_manager, &keys_manager, logger, &message_router, &offers_msg_handler,
&custom_msg_handler
);
- let mut pk = [2; 33]; pk[1] = 0xff;
- let peer_node_id_not_used = PublicKey::from_slice(&pk).unwrap();
- onion_messenger.handle_onion_message(&peer_node_id_not_used, &msg);
+
+ let peer_node_id = {
+ let mut secret_bytes = [0; 32];
+ secret_bytes[31] = 2;
+ let secret = SecretKey::from_slice(&secret_bytes).unwrap();
+ PublicKey::from_secret_key(&Secp256k1::signing_only(), &secret)
+ };
+
+ let mut features = InitFeatures::empty();
+ features.set_onion_messages_optional();
+ let init = msgs::Init { features, networks: None, remote_network_address: None };
+
+ onion_messenger.peer_connected(&peer_node_id, &init, false).unwrap();
+ onion_messenger.handle_onion_message(&peer_node_id, &msg);
}
}
const CUSTOM_MESSAGE_TYPE: u64 = 4242;
const CUSTOM_MESSAGE_CONTENTS: [u8; 32] = [42; 32];
-impl CustomOnionMessageContents for TestCustomMessage {
+impl OnionMessageContents for TestCustomMessage {
fn tlv_type(&self) -> u64 {
CUSTOM_MESSAGE_TYPE
}
buffer.read_to_end(&mut buf)?;
return Ok(Some(TestCustomMessage {}))
}
+ fn release_pending_custom_messages(&self) -> Vec<PendingOnionMessage<Self::CustomMessage>> {
+ vec![]
+ }
}
pub struct VecWriter(pub Vec<u8>);
#[test]
fn test_no_onion_message_breakage() {
- let one_hop_om = "020000000000000000000000000000000000000000000000000000000000000e01055600020000000000000000000000000000000000000000000000000000000000000e01ae0276020000000000000000000000000000000000000000000000000000000000000002020000000000000000000000000000000000000000000000000000000000000e0101022a0000000000000000000000000000014551231950b75fc4402da1732fc9bebf00109500000000000000000000000000000004106d000000000000000000000000000000fd1092202a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005600000000000000000000000000000000000000000000000000000000000000";
+ let one_hop_om = "\
+ 020000000000000000000000000000000000000000000000000000000000000e01055600020000000000000\
+ 000000000000000000000000000000000000000000000000e01ae0276020000000000000000000000000000\
+ 000000000000000000000000000000000002020000000000000000000000000000000000000000000000000\
+ 000000000000e0101022a0000000000000000000000000000014551231950b75fc4402da1732fc9bebf0010\
+ 9500000000000000000000000000000004106d000000000000000000000000000000fd1092202a2a2a2a2a2\
+ a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a0000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000005600000000000000000000000000000000000000000000\
+ 000000000000000000";
let logger = TrackingLogger { lines: Mutex::new(HashMap::new()) };
super::do_test(&::hex::decode(one_hop_om).unwrap(), &logger);
{
assert_eq!(log_entries.get(&("lightning::onion_message::messenger".to_string(),
"Received an onion message with path_id None and a reply_path".to_string())), Some(&1));
assert_eq!(log_entries.get(&("lightning::onion_message::messenger".to_string(),
- "Responding to onion message with path_id None".to_string())), Some(&1));
- assert_eq!(log_entries.get(&("lightning::onion_message::messenger".to_string(),
- "Failed responding to onion message with path_id None: TooFewBlindedHops".to_string())), Some(&1));
+ "Sending onion message when responding to Custom onion message with path_id None".to_string())), Some(&1));
}
- let two_unblinded_hops_om = "020000000000000000000000000000000000000000000000000000000000000e01055600020000000000000000000000000000000000000000000000000000000000000e0135043304210202020202020202020202020202020202020202020202020202020202020202026d000000000000000000000000000000eb0000000000000000000000000000000000000000000000000000000000000036041096000000000000000000000000000000fd1092202a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004800000000000000000000000000000000000000000000000000000000000000";
+ let two_unblinded_hops_om = "\
+ 020000000000000000000000000000000000000000000000000000000000000e01055600020000000000000\
+ 000000000000000000000000000000000000000000000000e01350433042102020202020202020202020202\
+ 02020202020202020202020202020202020202026d000000000000000000000000000000eb0000000000000\
+ 000000000000000000000000000000000000000000000000036041096000000000000000000000000000000\
+ fd1092202a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000004800000000000000000000000000000000000000000000\
+ 000000000000000000";
let logger = TrackingLogger { lines: Mutex::new(HashMap::new()) };
super::do_test(&::hex::decode(two_unblinded_hops_om).unwrap(), &logger);
{
assert_eq!(log_entries.get(&("lightning::onion_message::messenger".to_string(), "Forwarding an onion message to peer 020202020202020202020202020202020202020202020202020202020202020202".to_string())), Some(&1));
}
- let two_unblinded_two_blinded_om = "020000000000000000000000000000000000000000000000000000000000000e01055600020000000000000000000000000000000000000000000000000000000000000e0135043304210202020202020202020202020202020202020202020202020202020202020202026d0000000000000000000000000000009e0000000000000000000000000000000000000000000000000000000000000058045604210203030303030303030303030303030303030303030303030303030303030303020821020000000000000000000000000000000000000000000000000000000000000e0196000000000000000000000000000000e9000000000000000000000000000000000000000000000000000000000000003504330421020404040404040404040404040404040404040404040404040404040404040402ca00000000000000000000000000000042000000000000000000000000000000000000000000000000000000000000003604103f000000000000000000000000000000fd1092202a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004800000000000000000000000000000000000000000000000000000000000000";
+ let two_unblinded_two_blinded_om = "\
+ 020000000000000000000000000000000000000000000000000000000000000e01055600020000000000000\
+ 000000000000000000000000000000000000000000000000e01350433042102020202020202020202020202\
+ 02020202020202020202020202020202020202026d0000000000000000000000000000009e0000000000000\
+ 000000000000000000000000000000000000000000000000058045604210203030303030303030303030303\
+ 030303030303030303030303030303030303020821020000000000000000000000000000000000000000000\
+ 000000000000000000e0196000000000000000000000000000000e900000000000000000000000000000000\
+ 000000000000000000000000000000350433042102040404040404040404040404040404040404040404040\
+ 4040404040404040402ca000000000000000000000000000000420000000000000000000000000000000000\
+ 00000000000000000000000000003604103f000000000000000000000000000000fd1092202a2a2a2a2a2a2\
+ a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000004800000000000000000000000000000000000000000000\
+ 000000000000000000";
let logger = TrackingLogger { lines: Mutex::new(HashMap::new()) };
super::do_test(&::hex::decode(two_unblinded_two_blinded_om).unwrap(), &logger);
{
assert_eq!(log_entries.get(&("lightning::onion_message::messenger".to_string(), "Forwarding an onion message to peer 020202020202020202020202020202020202020202020202020202020202020202".to_string())), Some(&1));
}
- let three_blinded_om = "020000000000000000000000000000000000000000000000000000000000000e01055600020000000000000000000000000000000000000000000000000000000000000e0135043304210202020202020202020202020202020202020202020202020202020202020202026d000000000000000000000000000000b20000000000000000000000000000000000000000000000000000000000000035043304210203030303030303030303030303030303030303030303030303030303030303029600000000000000000000000000000033000000000000000000000000000000000000000000000000000000000000003604104e000000000000000000000000000000fd1092202a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004800000000000000000000000000000000000000000000000000000000000000";
+ let three_blinded_om = "\
+ 020000000000000000000000000000000000000000000000000000000000000e01055600020000000000000\
+ 000000000000000000000000000000000000000000000000e01350433042102020202020202020202020202\
+ 02020202020202020202020202020202020202026d000000000000000000000000000000b20000000000000\
+ 000000000000000000000000000000000000000000000000035043304210203030303030303030303030303\
+ 030303030303030303030303030303030303029600000000000000000000000000000033000000000000000\
+ 000000000000000000000000000000000000000000000003604104e000000000000000000000000000000fd\
+ 1092202a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a00000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
+ 000000000000000000000000000000000000000004800000000000000000000000000000000000000000000\
+ 000000000000000000";
let logger = TrackingLogger { lines: Mutex::new(HashMap::new()) };
super::do_test(&::hex::decode(three_blinded_om).unwrap(), &logger);
{
// You may not use this file except in accordance with one or both of these
// licenses.
+use bitcoin::blockdata::constants::ChainHash;
use bitcoin::blockdata::script::Builder;
use bitcoin::blockdata::transaction::TxOut;
-use bitcoin::hash_types::BlockHash;
use lightning::blinded_path::{BlindedHop, BlindedPath};
use lightning::chain::transaction::OutPoint;
net_graph: &'a NetworkGraph<&'b test_logger::TestLogger<Out>>,
}
impl<Out: test_logger::Output> UtxoLookup for FuzzChainSource<'_, '_, Out> {
- fn get_utxo(&self, _genesis_hash: &BlockHash, _short_channel_id: u64) -> UtxoResult {
+ fn get_utxo(&self, _chain_hash: &ChainHash, _short_channel_id: u64) -> UtxoResult {
let input_slice = self.input.get_slice(2);
if input_slice.is_none() { return UtxoResult::Sync(Err(UtxoLookupError::UnknownTx)); }
let input_slice = input_slice.unwrap();
[package]
name = "lightning-background-processor"
-version = "0.0.117"
+version = "0.0.118"
authors = ["Valentine Wallace <vwallace@protonmail.com>"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning"
[dependencies]
bitcoin = { version = "0.29.0", default-features = false }
-lightning = { version = "0.0.117", path = "../lightning", default-features = false }
-lightning-rapid-gossip-sync = { version = "0.0.117", path = "../lightning-rapid-gossip-sync", default-features = false }
+lightning = { version = "0.0.118", path = "../lightning", default-features = false }
+lightning-rapid-gossip-sync = { version = "0.0.118", path = "../lightning-rapid-gossip-sync", default-features = false }
[dev-dependencies]
tokio = { version = "1.14", features = [ "macros", "rt", "rt-multi-thread", "sync", "time" ] }
-lightning = { version = "0.0.117", path = "../lightning", features = ["_test_utils"] }
-lightning-invoice = { version = "0.25.0", path = "../lightning-invoice" }
-lightning-persister = { version = "0.0.117", path = "../lightning-persister" }
+lightning = { version = "0.0.118", path = "../lightning", features = ["_test_utils"] }
+lightning-invoice = { version = "0.26.0", path = "../lightning-invoice" }
+lightning-persister = { version = "0.0.118", path = "../lightning-persister" }
use lightning::ln::peer_handler::{PeerManager, MessageHandler, SocketDescriptor, IgnoringMessageHandler};
use lightning::routing::gossip::{NetworkGraph, NodeId, P2PGossipSync};
use lightning::routing::router::{DefaultRouter, Path, RouteHop};
- use lightning::routing::scoring::{ChannelUsage, ScoreUpdate, ScoreLookUp};
+ use lightning::routing::scoring::{ChannelUsage, ScoreUpdate, ScoreLookUp, LockableScore};
use lightning::util::config::UserConfig;
use lightning::util::ser::Writeable;
use lightning::util::test_utils;
fn disconnect_socket(&mut self) {}
}
+ #[cfg(c_bindings)]
+ type LockingWrapper<T> = lightning::routing::scoring::MultiThreadedLockableScore<T>;
+ #[cfg(not(c_bindings))]
+ type LockingWrapper<T> = Mutex<T>;
+
type ChannelManager =
channelmanager::ChannelManager<
Arc<ChainMonitor>,
Arc<DefaultRouter<
Arc<NetworkGraph<Arc<test_utils::TestLogger>>>,
Arc<test_utils::TestLogger>,
- Arc<Mutex<TestScorer>>,
+ Arc<LockingWrapper<TestScorer>>,
(),
TestScorer>
>,
network_graph: Arc<NetworkGraph<Arc<test_utils::TestLogger>>>,
logger: Arc<test_utils::TestLogger>,
best_block: BestBlock,
- scorer: Arc<Mutex<TestScorer>>,
+ scorer: Arc<LockingWrapper<TestScorer>>,
}
impl Node {
}
}
+ #[cfg(c_bindings)]
+ impl lightning::routing::scoring::Score for TestScorer {}
+
impl Drop for TestScorer {
fn drop(&mut self) {
if std::thread::panicking() {
let logger = Arc::new(test_utils::TestLogger::with_id(format!("node {}", i)));
let genesis_block = genesis_block(network);
let network_graph = Arc::new(NetworkGraph::new(network, logger.clone()));
- let scorer = Arc::new(Mutex::new(TestScorer::new()));
+ let scorer = Arc::new(LockingWrapper::new(TestScorer::new()));
let seed = [i as u8; 32];
let router = Arc::new(DefaultRouter::new(network_graph.clone(), logger.clone(), seed, scorer.clone(), Default::default()));
let chain_source = Arc::new(test_utils::TestChainSource::new(Network::Bitcoin));
maybe_announced_channel: true,
}], blinded_tail: None };
- $nodes[0].scorer.lock().unwrap().expect(TestResult::PaymentFailure { path: path.clone(), short_channel_id: scored_scid });
+ $nodes[0].scorer.write_lock().expect(TestResult::PaymentFailure { path: path.clone(), short_channel_id: scored_scid });
$nodes[0].node.push_pending_event(Event::PaymentPathFailed {
payment_id: None,
payment_hash: PaymentHash([42; 32]),
// Ensure we'll score payments that were explicitly failed back by the destination as
// ProbeSuccess.
- $nodes[0].scorer.lock().unwrap().expect(TestResult::ProbeSuccess { path: path.clone() });
+ $nodes[0].scorer.write_lock().expect(TestResult::ProbeSuccess { path: path.clone() });
$nodes[0].node.push_pending_event(Event::PaymentPathFailed {
payment_id: None,
payment_hash: PaymentHash([42; 32]),
_ => panic!("Unexpected event"),
}
- $nodes[0].scorer.lock().unwrap().expect(TestResult::PaymentSuccess { path: path.clone() });
+ $nodes[0].scorer.write_lock().expect(TestResult::PaymentSuccess { path: path.clone() });
$nodes[0].node.push_pending_event(Event::PaymentPathSuccessful {
payment_id: PaymentId([42; 32]),
payment_hash: None,
_ => panic!("Unexpected event"),
}
- $nodes[0].scorer.lock().unwrap().expect(TestResult::ProbeSuccess { path: path.clone() });
+ $nodes[0].scorer.write_lock().expect(TestResult::ProbeSuccess { path: path.clone() });
$nodes[0].node.push_pending_event(Event::ProbeSuccessful {
payment_id: PaymentId([42; 32]),
payment_hash: PaymentHash([42; 32]),
_ => panic!("Unexpected event"),
}
- $nodes[0].scorer.lock().unwrap().expect(TestResult::ProbeFailure { path: path.clone() });
+ $nodes[0].scorer.write_lock().expect(TestResult::ProbeFailure { path: path.clone() });
$nodes[0].node.push_pending_event(Event::ProbeFailed {
payment_id: PaymentId([42; 32]),
payment_hash: PaymentHash([42; 32]),
[package]
name = "lightning-block-sync"
-version = "0.0.117"
+version = "0.0.118"
authors = ["Jeffrey Czyz", "Matt Corallo"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning"
[dependencies]
bitcoin = "0.29.0"
-lightning = { version = "0.0.117", path = "../lightning" }
+lightning = { version = "0.0.118", path = "../lightning" }
tokio = { version = "1.0", features = [ "io-util", "net", "time" ], optional = true }
serde_json = { version = "1.0", optional = true }
chunked_transfer = { version = "1.4", optional = true }
[dev-dependencies]
-lightning = { version = "0.0.117", path = "../lightning", features = ["_test_utils"] }
+lightning = { version = "0.0.118", path = "../lightning", features = ["_test_utils"] }
tokio = { version = "1.14", features = [ "macros", "rt" ] }
use crate::{AsyncBlockSourceResult, BlockData, BlockSource, BlockSourceError};
use bitcoin::blockdata::block::Block;
+use bitcoin::blockdata::constants::ChainHash;
use bitcoin::blockdata::transaction::{TxOut, OutPoint};
use bitcoin::hash_types::BlockHash;
CMH::Target: CustomMessageHandler,
NS::Target: NodeSigner,
{
- fn get_utxo(&self, _genesis_hash: &BlockHash, short_channel_id: u64) -> UtxoResult {
+ fn get_utxo(&self, _chain_hash: &ChainHash, short_channel_id: u64) -> UtxoResult {
let res = UtxoFuture::new();
let fut = res.clone();
let source = self.source.clone();
[package]
name = "lightning-custom-message"
-version = "0.0.117"
+version = "0.0.118"
authors = ["Jeffrey Czyz"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning"
[dependencies]
bitcoin = "0.29.0"
-lightning = { version = "0.0.117", path = "../lightning" }
+lightning = { version = "0.0.118", path = "../lightning" }
[package]
name = "lightning-invoice"
description = "Data structures to parse and serialize BOLT11 lightning invoices"
-version = "0.25.0"
+version = "0.26.0"
authors = ["Sebastian Geisler <sgeisler@wh2.tu-dresden.de>"]
documentation = "https://docs.rs/lightning-invoice/"
license = "MIT OR Apache-2.0"
[dependencies]
bech32 = { version = "0.9.0", default-features = false }
-lightning = { version = "0.0.117", path = "../lightning", default-features = false }
+lightning = { version = "0.0.118", path = "../lightning", default-features = false }
secp256k1 = { version = "0.24.0", default-features = false, features = ["recovery", "alloc"] }
num-traits = { version = "0.2.8", default-features = false }
bitcoin_hashes = { version = "0.11", default-features = false }
bitcoin = { version = "0.29.0", default-features = false }
[dev-dependencies]
-lightning = { version = "0.0.117", path = "../lightning", default-features = false, features = ["_test_utils"] }
+lightning = { version = "0.0.118", path = "../lightning", default-features = false, features = ["_test_utils"] }
hex = "0.4"
serde_json = { version = "1"}
[package]
name = "lightning-net-tokio"
-version = "0.0.117"
+version = "0.0.118"
authors = ["Matt Corallo"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning/"
[dependencies]
bitcoin = "0.29.0"
-lightning = { version = "0.0.117", path = "../lightning" }
+lightning = { version = "0.0.118", path = "../lightning" }
tokio = { version = "1.0", features = [ "rt", "sync", "net", "time" ] }
[dev-dependencies]
tokio = { version = "1.14", features = [ "macros", "rt", "rt-multi-thread", "sync", "net", "time" ] }
-lightning = { version = "0.0.117", path = "../lightning", features = ["_test_utils"] }
+lightning = { version = "0.0.118", path = "../lightning", features = ["_test_utils"] }
fn handle_error(&self, _their_node_id: &PublicKey, _msg: &ErrorMessage) {}
fn provided_node_features(&self) -> NodeFeatures { NodeFeatures::empty() }
fn provided_init_features(&self, _their_node_id: &PublicKey) -> InitFeatures { InitFeatures::empty() }
- fn get_genesis_hashes(&self) -> Option<Vec<ChainHash>> {
+ fn get_chain_hashes(&self) -> Option<Vec<ChainHash>> {
Some(vec![ChainHash::using_genesis_block(Network::Testnet)])
}
}
[package]
name = "lightning-persister"
-version = "0.0.117"
+version = "0.0.118"
authors = ["Valentine Wallace", "Matt Corallo"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning"
[dependencies]
bitcoin = "0.29.0"
-lightning = { version = "0.0.117", path = "../lightning" }
+lightning = { version = "0.0.118", path = "../lightning" }
[target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.48.0", default-features = false, features = ["Win32_Storage_FileSystem", "Win32_Foundation"] }
criterion = { version = "0.4", optional = true, default-features = false }
[dev-dependencies]
-lightning = { version = "0.0.117", path = "../lightning", features = ["_test_utils"] }
+lightning = { version = "0.0.118", path = "../lightning", features = ["_test_utils"] }
bitcoin = { version = "0.29.0", default-features = false }
[package]
name = "lightning-rapid-gossip-sync"
-version = "0.0.117"
+version = "0.0.118"
authors = ["Arik Sosman <git@arik.io>"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning"
std = ["lightning/std"]
[dependencies]
-lightning = { version = "0.0.117", path = "../lightning", default-features = false }
+lightning = { version = "0.0.118", path = "../lightning", default-features = false }
bitcoin = { version = "0.29.0", default-features = false }
[target.'cfg(ldk_bench)'.dependencies]
criterion = { version = "0.4", optional = true, default-features = false }
[dev-dependencies]
-lightning = { version = "0.0.117", path = "../lightning", features = ["_test_utils"] }
+lightning = { version = "0.0.118", path = "../lightning", features = ["_test_utils"] }
use core::ops::Deref;
use core::sync::atomic::Ordering;
-use bitcoin::BlockHash;
+use bitcoin::blockdata::constants::ChainHash;
use bitcoin::secp256k1::PublicKey;
use lightning::ln::msgs::{
return Err(DecodeError::UnknownVersion.into());
}
- let chain_hash: BlockHash = Readable::read(read_cursor)?;
- let ng_genesis_hash = self.network_graph.get_genesis_hash();
- if chain_hash != ng_genesis_hash {
+ let chain_hash: ChainHash = Readable::read(read_cursor)?;
+ let ng_chain_hash = self.network_graph.get_chain_hash();
+ if chain_hash != ng_chain_hash {
return Err(
LightningError {
err: "Rapid Gossip Sync data's chain hash does not match the network graph's".to_owned(),
[package]
name = "lightning-transaction-sync"
-version = "0.0.117"
+version = "0.0.118"
authors = ["Elias Rohrer"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning"
async-interface = []
[dependencies]
-lightning = { version = "0.0.117", path = "../lightning", default-features = false }
+lightning = { version = "0.0.118", path = "../lightning", default-features = false }
bitcoin = { version = "0.29.0", default-features = false }
bdk-macros = "0.6"
futures = { version = "0.3", optional = true }
reqwest = { version = "0.11", optional = true, default-features = false, features = ["json"] }
[dev-dependencies]
-lightning = { version = "0.0.117", path = "../lightning", features = ["std"] }
+lightning = { version = "0.0.118", path = "../lightning", features = ["std"] }
electrsd = { version = "0.22.0", features = ["legacy", "esplora_a33e97e1", "bitcoind_23_0"] }
electrum-client = "0.12.0"
tokio = { version = "1.14.0", features = ["full"] }
[package]
name = "lightning"
-version = "0.0.117"
+version = "0.0.118"
authors = ["Matt Corallo"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning/"
}
impl BlindedPath {
+ /// Create a one-hop blinded path for a message.
+ pub fn one_hop_for_message<ES: EntropySource + ?Sized, T: secp256k1::Signing + secp256k1::Verification>(
+ recipient_node_id: PublicKey, entropy_source: &ES, secp_ctx: &Secp256k1<T>
+ ) -> Result<Self, ()> {
+ Self::new_for_message(&[recipient_node_id], entropy_source, secp_ctx)
+ }
+
/// Create a blinded path for an onion message, to be forwarded along `node_pks`. The last node
/// pubkey in `node_pks` will be the destination node.
///
- /// Errors if less than two hops are provided or if `node_pk`(s) are invalid.
+ /// Errors if no hops are provided or if `node_pk`(s) are invalid.
// TODO: make all payloads the same size with padding + add dummy hops
- pub fn new_for_message<ES: EntropySource, T: secp256k1::Signing + secp256k1::Verification>
- (node_pks: &[PublicKey], entropy_source: &ES, secp_ctx: &Secp256k1<T>) -> Result<Self, ()>
- {
- if node_pks.len() < 2 { return Err(()) }
+ pub fn new_for_message<ES: EntropySource + ?Sized, T: secp256k1::Signing + secp256k1::Verification>(
+ node_pks: &[PublicKey], entropy_source: &ES, secp_ctx: &Secp256k1<T>
+ ) -> Result<Self, ()> {
+ if node_pks.is_empty() { return Err(()) }
let blinding_secret_bytes = entropy_source.get_secure_random_bytes();
let blinding_secret = SecretKey::from_slice(&blinding_secret_bytes[..]).expect("RNG is busted");
let introduction_node_id = node_pks[0];
}
/// Create a one-hop blinded path for a payment.
- pub fn one_hop_for_payment<ES: EntropySource, T: secp256k1::Signing + secp256k1::Verification>(
+ pub fn one_hop_for_payment<ES: EntropySource + ?Sized, T: secp256k1::Signing + secp256k1::Verification>(
payee_node_id: PublicKey, payee_tlvs: payment::ReceiveTlvs, entropy_source: &ES,
secp_ctx: &Secp256k1<T>
) -> Result<(BlindedPayInfo, Self), ()> {
///
/// [`ForwardTlvs`]: crate::blinded_path::payment::ForwardTlvs
// TODO: make all payloads the same size with padding + add dummy hops
- pub(crate) fn new_for_payment<ES: EntropySource, T: secp256k1::Signing + secp256k1::Verification>(
+ pub(crate) fn new_for_payment<ES: EntropySource + ?Sized, T: secp256k1::Signing + secp256k1::Verification>(
intermediate_nodes: &[payment::ForwardNode], payee_node_id: PublicKey,
payee_tlvs: payment::ReceiveTlvs, htlc_maximum_msat: u64, entropy_source: &ES,
secp_ctx: &Secp256k1<T>
/// [`BlindedHop`]: crate::blinded_path::BlindedHop
#[derive(Clone, Debug)]
pub struct PaymentConstraints {
- /// The maximum total CLTV delta that is acceptable when relaying a payment over this
- /// [`BlindedHop`].
+ /// The maximum total CLTV that is acceptable when relaying a payment over this [`BlindedHop`].
pub max_cltv_expiry: u32,
/// The minimum value, in msat, that may be accepted by the node corresponding to this
/// [`BlindedHop`].
/// estimation.
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub enum ConfirmationTarget {
- /// We'd like a transaction to confirm in the future, but don't want to commit most of the fees
- /// required to do so yet. The remaining fees will come via a Child-Pays-For-Parent (CPFP) fee
- /// bump of the transaction.
- ///
- /// The feerate returned should be the absolute minimum feerate required to enter most node
- /// mempools across the network. Note that if you are not able to obtain this feerate estimate,
- /// you should likely use the furthest-out estimate allowed by your fee estimator.
- MempoolMinimum,
- /// We are happy with a transaction confirming slowly, at least within a day or so worth of
- /// blocks.
- Background,
- /// We'd like a transaction to confirm without major delayed, i.e., within the next 12-24 blocks.
- Normal,
- /// We'd like a transaction to confirm in the next few blocks.
- HighPriority,
+ /// We have some funds available on chain which we need to spend prior to some expiry time at
+ /// which point our counterparty may be able to steal them. Generally we have in the high tens
+ /// to low hundreds of blocks to get our transaction on-chain, but we shouldn't risk too low a
+ /// fee - this should be a relatively high priority feerate.
+ OnChainSweep,
+ /// The highest feerate we will allow our channel counterparty to have in a non-anchor channel.
+ ///
+ /// This is the feerate on the transaction which we (or our counterparty) will broadcast in
+ /// order to close the channel unilaterally. Because our counterparty must ensure they can
+ /// always broadcast the latest state, this value being too low will cause immediate
+ /// force-closures.
+ ///
+ /// Allowing this value to be too high can allow our counterparty to burn our HTLC outputs to
+ /// dust, which can result in HTLCs failing or force-closures (when the dust HTLCs exceed
+ /// [`ChannelConfig::max_dust_htlc_exposure`]).
+ ///
+ /// Because most nodes use a feerate estimate which is based on a relatively high priority
+ /// transaction entering the current mempool, setting this to a small multiple of your current
+ /// high priority feerate estimate should suffice.
+ ///
+ /// [`ChannelConfig::max_dust_htlc_exposure`]: crate::util::config::ChannelConfig::max_dust_htlc_exposure
+ MaxAllowedNonAnchorChannelRemoteFee,
+ /// This is the lowest feerate we will allow our channel counterparty to have in an anchor
+ /// channel in order to close the channel if a channel party goes away.
+ ///
+ /// This needs to be sufficient to get into the mempool when the channel needs to
+ /// be force-closed. Setting too high may result in force-closures if our counterparty attempts
+ /// to use a lower feerate. Because this is for anchor channels, we can always bump the feerate
+ /// later; the feerate here only needs to be sufficient to enter the mempool.
+ ///
+ /// A good estimate is the expected mempool minimum at the time of force-closure. Obviously this
+ /// is not an estimate which is very easy to calculate because we do not know the future. Using
+ /// a simple long-term fee estimate or tracking of the mempool minimum is a good approach to
+ /// ensure you can always close the channel. A future change to Bitcoin's P2P network
+ /// (package relay) may obviate the need for this entirely.
+ MinAllowedAnchorChannelRemoteFee,
+ /// The lowest feerate we will allow our channel counterparty to have in a non-anchor channel.
+ ///
+ /// This is the feerate on the transaction which we (or our counterparty) will broadcast in
+ /// order to close the channel if a channel party goes away. Setting this value too high will
+ /// cause immediate force-closures in order to avoid having an unbroadcastable state.
+ ///
+ /// This feerate represents the fee we pick now, which must be sufficient to enter a block at an
+ /// arbitrary time in the future. Obviously this is not an estimate which is very easy to
+ /// calculate. This can leave channels subject to being unable to close if feerates rise, and in
+ /// general you should prefer anchor channels to ensure you can increase the feerate when the
+ /// transactions need broadcasting.
+ ///
+ /// Do note some fee estimators round up to the next full sat/vbyte (ie 250 sats per kw),
+ /// causing occasional issues with feerate disagreements between an initiator that wants a
+ /// feerate of 1.1 sat/vbyte and a receiver that wants 1.1 rounded up to 2. If your fee
+ /// estimator rounds subtracting 250 to your desired feerate here can help avoid this issue.
+ ///
+ /// [`ChannelConfig::max_dust_htlc_exposure`]: crate::util::config::ChannelConfig::max_dust_htlc_exposure
+ MinAllowedNonAnchorChannelRemoteFee,
+ /// This is the feerate on the transaction which we (or our counterparty) will broadcast in
+ /// order to close the channel if a channel party goes away.
+ ///
+ /// This needs to be sufficient to get into the mempool when the channel needs to
+ /// be force-closed. Setting too low may result in force-closures. Because this is for anchor
+ /// channels, it can be a low value as we can always bump the feerate later.
+ ///
+ /// A good estimate is the expected mempool minimum at the time of force-closure. Obviously this
+ /// is not an estimate which is very easy to calculate because we do not know the future. Using
+ /// a simple long-term fee estimate or tracking of the mempool minimum is a good approach to
+ /// ensure you can always close the channel. A future change to Bitcoin's P2P network
+ /// (package relay) may obviate the need for this entirely.
+ AnchorChannelFee,
+ /// Lightning is built around the ability to broadcast a transaction in the future to close our
+ /// channel and claim all pending funds. In order to do so, non-anchor channels are built with
+ /// transactions which we need to be able to broadcast at some point in the future.
+ ///
+ /// This feerate represents the fee we pick now, which must be sufficient to enter a block at an
+ /// arbitrary time in the future. Obviously this is not an estimate which is very easy to
+ /// calculate, so most lightning nodes use some relatively high-priority feerate using the
+ /// current mempool. This leaves channels subject to being unable to close if feerates rise, and
+ /// in general you should prefer anchor channels to ensure you can increase the feerate when the
+ /// transactions need broadcasting.
+ ///
+ /// Since this should represent the feerate of a channel close that does not need fee
+ /// bumping, this is also used as an upper bound for our attempted feerate when doing cooperative
+ /// closure of any channel.
+ NonAnchorChannelFee,
+ /// When cooperatively closing a channel, this is the minimum feerate we will accept.
+ /// Recommended at least within a day or so worth of blocks.
+ ///
+ /// This will also be used when initiating a cooperative close of a channel. When closing a
+ /// channel you can override this fee by using
+ /// [`ChannelManager::close_channel_with_feerate_and_script`].
+ ///
+ /// [`ChannelManager::close_channel_with_feerate_and_script`]: crate::ln::channelmanager::ChannelManager::close_channel_with_feerate_and_script
+ ChannelCloseMinimum,
}
/// A trait which should be implemented to provide feerate information on a number of time
let test_fee_estimator = &TestFeeEstimator { sat_per_kw };
let fee_estimator = LowerBoundedFeeEstimator::new(test_fee_estimator);
- assert_eq!(fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::Background), FEERATE_FLOOR_SATS_PER_KW);
+ assert_eq!(fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::AnchorChannelFee), FEERATE_FLOOR_SATS_PER_KW);
}
#[test]
let test_fee_estimator = &TestFeeEstimator { sat_per_kw };
let fee_estimator = LowerBoundedFeeEstimator::new(test_fee_estimator);
- assert_eq!(fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::Background), sat_per_kw);
+ assert_eq!(fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::AnchorChannelFee), sat_per_kw);
}
}
let mut pending_monitor_events = self.pending_monitor_events.lock().unwrap().split_off(0);
for monitor_state in self.monitors.read().unwrap().values() {
let is_pending_monitor_update = monitor_state.has_pending_chainsync_updates(&monitor_state.pending_monitor_updates.lock().unwrap());
- if is_pending_monitor_update &&
- monitor_state.last_chain_persist_height.load(Ordering::Acquire) + LATENCY_GRACE_PERIOD_BLOCKS as usize
- > self.highest_chain_height.load(Ordering::Acquire)
- {
- log_debug!(self.logger, "A Channel Monitor sync is still in progress, refusing to provide monitor events!");
- } else {
+ if !is_pending_monitor_update || monitor_state.last_chain_persist_height.load(Ordering::Acquire) + LATENCY_GRACE_PERIOD_BLOCKS as usize <= self.highest_chain_height.load(Ordering::Acquire) {
if is_pending_monitor_update {
log_error!(self.logger, "A ChannelMonitor sync took longer than {} blocks to complete.", LATENCY_GRACE_PERIOD_BLOCKS);
log_error!(self.logger, " To avoid funds-loss, we are allowing monitor updates to be released.");
use crate::chain::{BestBlock, WatchedOutput};
use crate::chain::chaininterface::{BroadcasterInterface, FeeEstimator, LowerBoundedFeeEstimator};
use crate::chain::transaction::{OutPoint, TransactionData};
-use crate::sign::{SpendableOutputDescriptor, StaticPaymentOutputDescriptor, DelayedPaymentOutputDescriptor, WriteableEcdsaChannelSigner, SignerProvider, EntropySource};
+use crate::sign::{ChannelDerivationParameters, HTLCDescriptor, SpendableOutputDescriptor, StaticPaymentOutputDescriptor, DelayedPaymentOutputDescriptor, WriteableEcdsaChannelSigner, SignerProvider, EntropySource};
use crate::chain::onchaintx::{ClaimEvent, OnchainTxHandler};
use crate::chain::package::{CounterpartyOfferedHTLCOutput, CounterpartyReceivedHTLCOutput, HolderFundingOutput, HolderHTLCOutput, PackageSolvingData, PackageTemplate, RevokedOutput, RevokedHTLCOutput};
use crate::chain::Filter;
use crate::util::ser::{Readable, ReadableArgs, RequiredWrapper, MaybeReadable, UpgradableRequired, Writer, Writeable, U48};
use crate::util::byte_utils;
use crate::events::{Event, EventHandler};
-use crate::events::bump_transaction::{ChannelDerivationParameters, AnchorDescriptor, HTLCDescriptor, BumpTransactionEvent};
+use crate::events::bump_transaction::{AnchorDescriptor, BumpTransactionEvent};
use crate::prelude::*;
use core::{cmp, mem};
/// A monitor event containing an HTLCUpdate.
HTLCEvent(HTLCUpdate),
- /// A monitor event that the Channel's commitment transaction was confirmed.
+ /// Indicates we broadcasted the channel's latest commitment transaction and thus closed the
+ /// channel.
HolderForceClosed(OutPoint),
/// Indicates a [`ChannelMonitor`] update has completed. See
(holder_commitment_tx, trusted_tx.commitment_number())
};
- let onchain_tx_handler =
- OnchainTxHandler::new(destination_script.clone(), keys,
- channel_parameters.clone(), initial_holder_commitment_tx, secp_ctx);
+ let onchain_tx_handler = OnchainTxHandler::new(
+ channel_value_satoshis, channel_keys_id, destination_script.clone(), keys,
+ channel_parameters.clone(), initial_holder_commitment_tx, secp_ctx
+ );
let mut outputs_to_watch = HashMap::new();
outputs_to_watch.insert(funding_info.0.txid, vec![(funding_info.0.index as u32, funding_info.1.clone())]);
per_commitment_point: self.onchain_tx_handler.signer.get_per_commitment_point(
htlc.per_commitment_number, &self.onchain_tx_handler.secp_ctx,
),
+ feerate_per_kw: 0,
htlc: htlc.htlc,
preimage: htlc.preimage,
counterparty_sig: htlc.counterparty_sig,
continue;
}
} else { None };
- if let Some(htlc_tx) = self.onchain_tx_handler.unsafe_get_fully_signed_htlc_tx(
+ if let Some(htlc_tx) = self.onchain_tx_handler.get_fully_signed_htlc_tx(
&::bitcoin::OutPoint { txid, vout }, &preimage) {
holder_transactions.push(htlc_tx);
}
use bitcoin::secp256k1;
use crate::chain::chaininterface::compute_feerate_sat_per_1000_weight;
-use crate::sign::{ChannelSigner, EntropySource, SignerProvider};
+use crate::sign::{ChannelDerivationParameters, HTLCDescriptor, ChannelSigner, EntropySource, SignerProvider, WriteableEcdsaChannelSigner};
use crate::ln::msgs::DecodeError;
use crate::ln::PaymentPreimage;
use crate::ln::chan_utils::{self, ChannelTransactionParameters, HTLCOutputInCommitment, HolderCommitmentTransaction};
use crate::chain::ClaimId;
use crate::chain::chaininterface::{ConfirmationTarget, FeeEstimator, BroadcasterInterface, LowerBoundedFeeEstimator};
use crate::chain::channelmonitor::{ANTI_REORG_DELAY, CLTV_SHARED_CLAIM_BUFFER};
-use crate::sign::WriteableEcdsaChannelSigner;
use crate::chain::package::{PackageSolvingData, PackageTemplate};
use crate::util::logger::Logger;
use crate::util::ser::{Readable, ReadableArgs, MaybeReadable, UpgradableRequired, Writer, Writeable, VecWriter};
/// do RBF bumping if possible.
#[derive(Clone)]
pub struct OnchainTxHandler<ChannelSigner: WriteableEcdsaChannelSigner> {
+ channel_value_satoshis: u64,
+ channel_keys_id: [u8; 32],
destination_script: Script,
holder_commitment: HolderCommitmentTransaction,
- // holder_htlc_sigs and prev_holder_htlc_sigs are in the order as they appear in the commitment
- // transaction outputs (hence the Option<>s inside the Vec). The first usize is the index in
- // the set of HTLCs in the HolderCommitmentTransaction.
- holder_htlc_sigs: Option<Vec<Option<(usize, Signature)>>>,
prev_holder_commitment: Option<HolderCommitmentTransaction>,
- prev_holder_htlc_sigs: Option<Vec<Option<(usize, Signature)>>>,
pub(super) signer: ChannelSigner,
pub(crate) channel_transaction_parameters: ChannelTransactionParameters,
impl<ChannelSigner: WriteableEcdsaChannelSigner> PartialEq for OnchainTxHandler<ChannelSigner> {
fn eq(&self, other: &Self) -> bool {
// `signer`, `secp_ctx`, and `pending_claim_events` are excluded on purpose.
- self.destination_script == other.destination_script &&
+ self.channel_value_satoshis == other.channel_value_satoshis &&
+ self.channel_keys_id == other.channel_keys_id &&
+ self.destination_script == other.destination_script &&
self.holder_commitment == other.holder_commitment &&
- self.holder_htlc_sigs == other.holder_htlc_sigs &&
self.prev_holder_commitment == other.prev_holder_commitment &&
- self.prev_holder_htlc_sigs == other.prev_holder_htlc_sigs &&
self.channel_transaction_parameters == other.channel_transaction_parameters &&
self.pending_claim_requests == other.pending_claim_requests &&
self.claimable_outpoints == other.claimable_outpoints &&
self.destination_script.write(writer)?;
self.holder_commitment.write(writer)?;
- self.holder_htlc_sigs.write(writer)?;
+ None::<Option<Vec<Option<(usize, Signature)>>>>.write(writer)?; // holder_htlc_sigs
self.prev_holder_commitment.write(writer)?;
- self.prev_holder_htlc_sigs.write(writer)?;
+ None::<Option<Vec<Option<(usize, Signature)>>>>.write(writer)?; // prev_holder_htlc_sigs
self.channel_transaction_parameters.write(writer)?;
let destination_script = Readable::read(reader)?;
let holder_commitment = Readable::read(reader)?;
- let holder_htlc_sigs = Readable::read(reader)?;
+ let _holder_htlc_sigs: Option<Vec<Option<(usize, Signature)>>> = Readable::read(reader)?;
let prev_holder_commitment = Readable::read(reader)?;
- let prev_holder_htlc_sigs = Readable::read(reader)?;
+ let _prev_holder_htlc_sigs: Option<Vec<Option<(usize, Signature)>>> = Readable::read(reader)?;
let channel_parameters = Readable::read(reader)?;
secp_ctx.seeded_randomize(&entropy_source.get_secure_random_bytes());
Ok(OnchainTxHandler {
+ channel_value_satoshis,
+ channel_keys_id,
destination_script,
holder_commitment,
- holder_htlc_sigs,
prev_holder_commitment,
- prev_holder_htlc_sigs,
signer,
channel_transaction_parameters: channel_parameters,
claimable_outpoints,
}
impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner> {
- pub(crate) fn new(destination_script: Script, signer: ChannelSigner, channel_parameters: ChannelTransactionParameters, holder_commitment: HolderCommitmentTransaction, secp_ctx: Secp256k1<secp256k1::All>) -> Self {
+ pub(crate) fn new(
+ channel_value_satoshis: u64, channel_keys_id: [u8; 32], destination_script: Script,
+ signer: ChannelSigner, channel_parameters: ChannelTransactionParameters,
+ holder_commitment: HolderCommitmentTransaction, secp_ctx: Secp256k1<secp256k1::All>
+ ) -> Self {
OnchainTxHandler {
+ channel_value_satoshis,
+ channel_keys_id,
destination_script,
holder_commitment,
- holder_htlc_sigs: None,
prev_holder_commitment: None,
- prev_holder_htlc_sigs: None,
signer,
channel_transaction_parameters: channel_parameters,
pending_claim_requests: HashMap::new(),
if cached_request.is_malleable() {
if cached_request.requires_external_funding() {
let target_feerate_sat_per_1000_weight = cached_request.compute_package_feerate(
- fee_estimator, ConfirmationTarget::HighPriority, force_feerate_bump
+ fee_estimator, ConfirmationTarget::OnChainSweep, force_feerate_bump
);
if let Some(htlcs) = cached_request.construct_malleable_package_with_external_funding(self) {
return Some((
debug_assert_eq!(tx.txid(), self.holder_commitment.trust().txid(),
"Holder commitment transaction mismatch");
- let conf_target = ConfirmationTarget::HighPriority;
+ let conf_target = ConfirmationTarget::OnChainSweep;
let package_target_feerate_sat_per_1000_weight = cached_request
.compute_package_feerate(fee_estimator, conf_target, force_feerate_bump);
if let Some(input_amount_sat) = output.funding_amount {
pub(crate) fn provide_latest_holder_tx(&mut self, tx: HolderCommitmentTransaction) {
self.prev_holder_commitment = Some(replace(&mut self.holder_commitment, tx));
- self.holder_htlc_sigs = None;
- }
-
- // Normally holder HTLCs are signed at the same time as the holder commitment tx. However,
- // in some configurations, the holder commitment tx has been signed and broadcast by a
- // ChannelMonitor replica, so we handle that case here.
- fn sign_latest_holder_htlcs(&mut self) {
- if self.holder_htlc_sigs.is_none() {
- let (_sig, sigs) = self.signer.sign_holder_commitment_and_htlcs(&self.holder_commitment, &self.secp_ctx).expect("sign holder commitment");
- self.holder_htlc_sigs = Some(Self::extract_holder_sigs(&self.holder_commitment, sigs));
- }
- }
-
- // Normally only the latest commitment tx and HTLCs need to be signed. However, in some
- // configurations we may have updated our holder commitment but a replica of the ChannelMonitor
- // broadcast the previous one before we sync with it. We handle that case here.
- fn sign_prev_holder_htlcs(&mut self) {
- if self.prev_holder_htlc_sigs.is_none() {
- if let Some(ref holder_commitment) = self.prev_holder_commitment {
- let (_sig, sigs) = self.signer.sign_holder_commitment_and_htlcs(holder_commitment, &self.secp_ctx).expect("sign previous holder commitment");
- self.prev_holder_htlc_sigs = Some(Self::extract_holder_sigs(holder_commitment, sigs));
- }
- }
- }
-
- fn extract_holder_sigs(holder_commitment: &HolderCommitmentTransaction, sigs: Vec<Signature>) -> Vec<Option<(usize, Signature)>> {
- let mut ret = Vec::new();
- for (htlc_idx, (holder_sig, htlc)) in sigs.iter().zip(holder_commitment.htlcs().iter()).enumerate() {
- let tx_idx = htlc.transaction_output_index.unwrap();
- if ret.len() <= tx_idx as usize { ret.resize(tx_idx as usize + 1, None); }
- ret[tx_idx as usize] = Some((htlc_idx, holder_sig.clone()));
- }
- ret
}
pub(crate) fn get_unsigned_holder_commitment_tx(&self) -> &Transaction {
// before providing a initial commitment transaction. For outbound channel, init ChannelMonitor at Channel::funding_signed, there is nothing
// to monitor before.
pub(crate) fn get_fully_signed_holder_tx(&mut self, funding_redeemscript: &Script) -> Transaction {
- let (sig, htlc_sigs) = self.signer.sign_holder_commitment_and_htlcs(&self.holder_commitment, &self.secp_ctx).expect("signing holder commitment");
- self.holder_htlc_sigs = Some(Self::extract_holder_sigs(&self.holder_commitment, htlc_sigs));
+ let sig = self.signer.sign_holder_commitment(&self.holder_commitment, &self.secp_ctx).expect("signing holder commitment");
self.holder_commitment.add_holder_sig(funding_redeemscript, sig)
}
#[cfg(any(test, feature="unsafe_revoked_tx_signing"))]
pub(crate) fn get_fully_signed_copy_holder_tx(&mut self, funding_redeemscript: &Script) -> Transaction {
- let (sig, htlc_sigs) = self.signer.unsafe_sign_holder_commitment_and_htlcs(&self.holder_commitment, &self.secp_ctx).expect("sign holder commitment");
- self.holder_htlc_sigs = Some(Self::extract_holder_sigs(&self.holder_commitment, htlc_sigs));
+ let sig = self.signer.unsafe_sign_holder_commitment(&self.holder_commitment, &self.secp_ctx).expect("sign holder commitment");
self.holder_commitment.add_holder_sig(funding_redeemscript, sig)
}
pub(crate) fn get_fully_signed_htlc_tx(&mut self, outp: &::bitcoin::OutPoint, preimage: &Option<PaymentPreimage>) -> Option<Transaction> {
- let mut htlc_tx = None;
- let commitment_txid = self.holder_commitment.trust().txid();
- // Check if the HTLC spends from the current holder commitment
- if commitment_txid == outp.txid {
- self.sign_latest_holder_htlcs();
- if let &Some(ref htlc_sigs) = &self.holder_htlc_sigs {
- let &(ref htlc_idx, ref htlc_sig) = htlc_sigs[outp.vout as usize].as_ref().unwrap();
- let trusted_tx = self.holder_commitment.trust();
- let counterparty_htlc_sig = self.holder_commitment.counterparty_htlc_sigs[*htlc_idx];
- htlc_tx = Some(trusted_tx
- .get_signed_htlc_tx(&self.channel_transaction_parameters.as_holder_broadcastable(), *htlc_idx, &counterparty_htlc_sig, htlc_sig, preimage));
- }
- }
- // If the HTLC doesn't spend the current holder commitment, check if it spends the previous one
- if htlc_tx.is_none() && self.prev_holder_commitment.is_some() {
- let commitment_txid = self.prev_holder_commitment.as_ref().unwrap().trust().txid();
- if commitment_txid == outp.txid {
- self.sign_prev_holder_htlcs();
- if let &Some(ref htlc_sigs) = &self.prev_holder_htlc_sigs {
- let &(ref htlc_idx, ref htlc_sig) = htlc_sigs[outp.vout as usize].as_ref().unwrap();
- let holder_commitment = self.prev_holder_commitment.as_ref().unwrap();
- let trusted_tx = holder_commitment.trust();
- let counterparty_htlc_sig = holder_commitment.counterparty_htlc_sigs[*htlc_idx];
- htlc_tx = Some(trusted_tx
- .get_signed_htlc_tx(&self.channel_transaction_parameters.as_holder_broadcastable(), *htlc_idx, &counterparty_htlc_sig, htlc_sig, preimage));
- }
+ let get_signed_htlc_tx = |holder_commitment: &HolderCommitmentTransaction| {
+ let trusted_tx = holder_commitment.trust();
+ if trusted_tx.txid() != outp.txid {
+ return None;
}
- }
- htlc_tx
+ let (htlc_idx, htlc) = trusted_tx.htlcs().iter().enumerate()
+ .find(|(_, htlc)| htlc.transaction_output_index.unwrap() == outp.vout)
+ .unwrap();
+ let counterparty_htlc_sig = holder_commitment.counterparty_htlc_sigs[htlc_idx];
+ let mut htlc_tx = trusted_tx.build_unsigned_htlc_tx(
+ &self.channel_transaction_parameters.as_holder_broadcastable(), htlc_idx, preimage,
+ );
+
+ let htlc_descriptor = HTLCDescriptor {
+ channel_derivation_parameters: ChannelDerivationParameters {
+ value_satoshis: self.channel_value_satoshis,
+ keys_id: self.channel_keys_id,
+ transaction_parameters: self.channel_transaction_parameters.clone(),
+ },
+ commitment_txid: trusted_tx.txid(),
+ per_commitment_number: trusted_tx.commitment_number(),
+ per_commitment_point: trusted_tx.per_commitment_point(),
+ feerate_per_kw: trusted_tx.feerate_per_kw(),
+ htlc: htlc.clone(),
+ preimage: preimage.clone(),
+ counterparty_sig: counterparty_htlc_sig.clone(),
+ };
+ let htlc_sig = self.signer.sign_holder_htlc_transaction(&htlc_tx, 0, &htlc_descriptor, &self.secp_ctx).unwrap();
+ htlc_tx.input[0].witness = trusted_tx.build_htlc_input_witness(
+ htlc_idx, &counterparty_htlc_sig, &htlc_sig, preimage,
+ );
+ Some(htlc_tx)
+ };
+
+ // Check if the HTLC spends from the current holder commitment first, or the previous.
+ get_signed_htlc_tx(&self.holder_commitment)
+ .or_else(|| self.prev_holder_commitment.as_ref().and_then(|prev_holder_commitment| get_signed_htlc_tx(prev_holder_commitment)))
}
pub(crate) fn generate_external_htlc_claim(
pub(crate) fn channel_type_features(&self) -> &ChannelTypeFeatures {
&self.channel_transaction_parameters.channel_type_features
}
-
- #[cfg(any(test,feature = "unsafe_revoked_tx_signing"))]
- pub(crate) fn unsafe_get_fully_signed_htlc_tx(&mut self, outp: &::bitcoin::OutPoint, preimage: &Option<PaymentPreimage>) -> Option<Transaction> {
- let latest_had_sigs = self.holder_htlc_sigs.is_some();
- let prev_had_sigs = self.prev_holder_htlc_sigs.is_some();
- let ret = self.get_fully_signed_htlc_tx(outp, preimage);
- if !latest_had_sigs {
- self.holder_htlc_sigs = None;
- }
- if !prev_had_sigs {
- self.prev_holder_htlc_sigs = None;
- }
- ret
- }
}
use crate::ln::chan_utils::{TxCreationKeys, HTLCOutputInCommitment};
use crate::ln::chan_utils;
use crate::ln::msgs::DecodeError;
-use crate::chain::chaininterface::{FeeEstimator, ConfirmationTarget, MIN_RELAY_FEE_SAT_PER_1000_WEIGHT};
+use crate::chain::chaininterface::{FeeEstimator, ConfirmationTarget, MIN_RELAY_FEE_SAT_PER_1000_WEIGHT, compute_feerate_sat_per_1000_weight, FEERATE_FLOOR_SATS_PER_KW};
use crate::sign::WriteableEcdsaChannelSigner;
use crate::chain::onchaintx::{ExternalHTLCClaim, OnchainTxHandler};
use crate::util::logger::Logger;
}
/// Attempt to propose a bumping fee for a transaction from its spent output's values and predicted
-/// weight. We start with the highest priority feerate returned by the node's fee estimator then
-/// fall-back to lower priorities until we have enough value available to suck from.
+/// weight. We first try our [`OnChainSweep`] feerate, if it's not enough we try to sweep half of
+/// the input amounts.
///
/// If the proposed fee is less than the available spent output's values, we return the proposed
-/// fee and the corresponding updated feerate. If the proposed fee is equal or more than the
-/// available spent output's values, we return nothing
+/// fee and the corresponding updated feerate. If fee is under [`FEERATE_FLOOR_SATS_PER_KW`], we
+/// return nothing.
+///
+/// [`OnChainSweep`]: crate::chain::chaininterface::ConfirmationTarget::OnChainSweep
+/// [`FEERATE_FLOOR_SATS_PER_KW`]: crate::chain::chaininterface::MIN_RELAY_FEE_SAT_PER_1000_WEIGHT
fn compute_fee_from_spent_amounts<F: Deref, L: Deref>(input_amounts: u64, predicted_weight: usize, fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L) -> Option<(u64, u64)>
where F::Target: FeeEstimator,
L::Target: Logger,
{
- let mut updated_feerate = fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::HighPriority) as u64;
- let mut fee = updated_feerate * (predicted_weight as u64) / 1000;
- if input_amounts <= fee {
- updated_feerate = fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::Normal) as u64;
- fee = updated_feerate * (predicted_weight as u64) / 1000;
- if input_amounts <= fee {
- updated_feerate = fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::Background) as u64;
- fee = updated_feerate * (predicted_weight as u64) / 1000;
- if input_amounts <= fee {
- log_error!(logger, "Failed to generate an on-chain punishment tx as even low priority fee ({} sat) was more than the entire claim balance ({} sat)",
- fee, input_amounts);
- None
- } else {
- log_warn!(logger, "Used low priority fee for on-chain punishment tx as high priority fee was more than the entire claim balance ({} sat)",
- input_amounts);
- Some((fee, updated_feerate))
- }
- } else {
- log_warn!(logger, "Used medium priority fee for on-chain punishment tx as high priority fee was more than the entire claim balance ({} sat)",
- input_amounts);
- Some((fee, updated_feerate))
- }
+ let sweep_feerate = fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::OnChainSweep);
+ let fee_rate = cmp::min(sweep_feerate, compute_feerate_sat_per_1000_weight(input_amounts / 2, predicted_weight as u64)) as u64;
+ let fee = fee_rate * (predicted_weight as u64) / 1000;
+
+ // if the fee rate is below the floor, we don't sweep
+ if fee_rate < FEERATE_FLOOR_SATS_PER_KW as u64 {
+ log_error!(logger, "Failed to generate an on-chain tx with fee ({} sat/kw) was less than the floor ({} sat/kw)",
+ fee_rate, FEERATE_FLOOR_SATS_PER_KW);
+ None
} else {
- Some((fee, updated_feerate))
+ Some((fee, fee_rate))
}
}
use crate::ln::chan_utils;
use crate::ln::chan_utils::{
ANCHOR_INPUT_WITNESS_WEIGHT, HTLC_SUCCESS_INPUT_ANCHOR_WITNESS_WEIGHT,
- HTLC_TIMEOUT_INPUT_ANCHOR_WITNESS_WEIGHT, ChannelTransactionParameters, HTLCOutputInCommitment
+ HTLC_TIMEOUT_INPUT_ANCHOR_WITNESS_WEIGHT, HTLCOutputInCommitment
};
-use crate::ln::features::ChannelTypeFeatures;
-use crate::ln::PaymentPreimage;
use crate::prelude::*;
-use crate::sign::{EcdsaChannelSigner, SignerProvider, WriteableEcdsaChannelSigner, P2WPKH_WITNESS_WEIGHT};
+use crate::sign::{
+ ChannelDerivationParameters, HTLCDescriptor, EcdsaChannelSigner, SignerProvider,
+ WriteableEcdsaChannelSigner, P2WPKH_WITNESS_WEIGHT
+};
use crate::sync::Mutex;
use crate::util::logger::Logger;
-use bitcoin::{OutPoint, PackedLockTime, PubkeyHash, Sequence, Script, Transaction, Txid, TxIn, TxOut, Witness, WPubkeyHash};
+use bitcoin::{OutPoint, PackedLockTime, PubkeyHash, Sequence, Script, Transaction, TxIn, TxOut, Witness, WPubkeyHash};
use bitcoin::blockdata::constants::WITNESS_SCALE_FACTOR;
use bitcoin::consensus::Encodable;
use bitcoin::secp256k1;
-use bitcoin::secp256k1::{PublicKey, Secp256k1};
+use bitcoin::secp256k1::Secp256k1;
use bitcoin::secp256k1::ecdsa::Signature;
const EMPTY_SCRIPT_SIG_WEIGHT: u64 = 1 /* empty script_sig */ * WITNESS_SCALE_FACTOR as u64;
const BASE_INPUT_WEIGHT: u64 = BASE_INPUT_SIZE * WITNESS_SCALE_FACTOR as u64;
-/// The parameters required to derive a channel signer via [`SignerProvider`].
-#[derive(Clone, Debug, PartialEq, Eq)]
-pub struct ChannelDerivationParameters {
- /// The value in satoshis of the channel we're attempting to spend the anchor output of.
- pub value_satoshis: u64,
- /// The unique identifier to re-derive the signer for the associated channel.
- pub keys_id: [u8; 32],
- /// The necessary channel parameters that need to be provided to the re-derived signer through
- /// [`ChannelSigner::provide_channel_parameters`].
- ///
- /// [`ChannelSigner::provide_channel_parameters`]: crate::sign::ChannelSigner::provide_channel_parameters
- pub transaction_parameters: ChannelTransactionParameters,
-}
-
-impl_writeable_tlv_based!(ChannelDerivationParameters, {
- (0, value_satoshis, required),
- (2, keys_id, required),
- (4, transaction_parameters, required),
-});
-
/// A descriptor used to sign for a commitment transaction's anchor output.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AnchorDescriptor {
}
}
-/// A descriptor used to sign for a commitment transaction's HTLC output.
-#[derive(Clone, Debug, PartialEq, Eq)]
-pub struct HTLCDescriptor {
- /// The parameters required to derive the signer for the HTLC input.
- pub channel_derivation_parameters: ChannelDerivationParameters,
- /// The txid of the commitment transaction in which the HTLC output lives.
- pub commitment_txid: Txid,
- /// The number of the commitment transaction in which the HTLC output lives.
- pub per_commitment_number: u64,
- /// The key tweak corresponding to the number of the commitment transaction in which the HTLC
- /// output lives. This tweak is applied to all the basepoints for both parties in the channel to
- /// arrive at unique keys per commitment.
- ///
- /// See <https://github.com/lightning/bolts/blob/master/03-transactions.md#keys> for more info.
- pub per_commitment_point: PublicKey,
- /// The details of the HTLC as it appears in the commitment transaction.
- pub htlc: HTLCOutputInCommitment,
- /// The preimage, if `Some`, to claim the HTLC output with. If `None`, the timeout path must be
- /// taken.
- pub preimage: Option<PaymentPreimage>,
- /// The counterparty's signature required to spend the HTLC output.
- pub counterparty_sig: Signature
-}
-
-impl_writeable_tlv_based!(HTLCDescriptor, {
- (0, channel_derivation_parameters, required),
- (2, commitment_txid, required),
- (4, per_commitment_number, required),
- (6, per_commitment_point, required),
- (8, htlc, required),
- (10, preimage, option),
- (12, counterparty_sig, required),
-});
-
-impl HTLCDescriptor {
- /// Returns the outpoint of the HTLC output in the commitment transaction. This is the outpoint
- /// being spent by the HTLC input in the HTLC transaction.
- pub fn outpoint(&self) -> OutPoint {
- OutPoint {
- txid: self.commitment_txid,
- vout: self.htlc.transaction_output_index.unwrap(),
- }
- }
-
- /// Returns the UTXO to be spent by the HTLC input, which can be obtained via
- /// [`Self::unsigned_tx_input`].
- pub fn previous_utxo<C: secp256k1::Signing + secp256k1::Verification>(&self, secp: &Secp256k1<C>) -> TxOut {
- TxOut {
- script_pubkey: self.witness_script(secp).to_v0_p2wsh(),
- value: self.htlc.amount_msat / 1000,
- }
- }
-
- /// Returns the unsigned transaction input spending the HTLC output in the commitment
- /// transaction.
- pub fn unsigned_tx_input(&self) -> TxIn {
- chan_utils::build_htlc_input(&self.commitment_txid, &self.htlc, &ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies())
- }
-
- /// Returns the delayed output created as a result of spending the HTLC output in the commitment
- /// transaction.
- pub fn tx_output<C: secp256k1::Signing + secp256k1::Verification>(&self, secp: &Secp256k1<C>) -> TxOut {
- let channel_params = self.channel_derivation_parameters.transaction_parameters.as_holder_broadcastable();
- let broadcaster_keys = channel_params.broadcaster_pubkeys();
- let counterparty_keys = channel_params.countersignatory_pubkeys();
- let broadcaster_delayed_key = chan_utils::derive_public_key(
- secp, &self.per_commitment_point, &broadcaster_keys.delayed_payment_basepoint
- );
- let counterparty_revocation_key = chan_utils::derive_public_revocation_key(
- secp, &self.per_commitment_point, &counterparty_keys.revocation_basepoint
- );
- chan_utils::build_htlc_output(
- 0 /* feerate_per_kw */, channel_params.contest_delay(), &self.htlc,
- &ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies(), &broadcaster_delayed_key, &counterparty_revocation_key
- )
- }
-
- /// Returns the witness script of the HTLC output in the commitment transaction.
- pub fn witness_script<C: secp256k1::Signing + secp256k1::Verification>(&self, secp: &Secp256k1<C>) -> Script {
- let channel_params = self.channel_derivation_parameters.transaction_parameters.as_holder_broadcastable();
- let broadcaster_keys = channel_params.broadcaster_pubkeys();
- let counterparty_keys = channel_params.countersignatory_pubkeys();
- let broadcaster_htlc_key = chan_utils::derive_public_key(
- secp, &self.per_commitment_point, &broadcaster_keys.htlc_basepoint
- );
- let counterparty_htlc_key = chan_utils::derive_public_key(
- secp, &self.per_commitment_point, &counterparty_keys.htlc_basepoint
- );
- let counterparty_revocation_key = chan_utils::derive_public_revocation_key(
- secp, &self.per_commitment_point, &counterparty_keys.revocation_basepoint
- );
- chan_utils::get_htlc_redeemscript_with_explicit_keys(
- &self.htlc, &ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies(), &broadcaster_htlc_key, &counterparty_htlc_key,
- &counterparty_revocation_key,
- )
- }
-
- /// Returns the fully signed witness required to spend the HTLC output in the commitment
- /// transaction.
- pub fn tx_input_witness(&self, signature: &Signature, witness_script: &Script) -> Witness {
- chan_utils::build_htlc_input_witness(
- signature, &self.counterparty_sig, &self.preimage, witness_script, &ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies() /* opt_anchors */
- )
- }
-
- /// Derives the channel signer required to sign the HTLC input.
- pub fn derive_channel_signer<S: WriteableEcdsaChannelSigner, SP: Deref>(&self, signer_provider: &SP) -> S
- where
- SP::Target: SignerProvider<Signer = S>
- {
- let mut signer = signer_provider.derive_channel_signer(
- self.channel_derivation_parameters.value_satoshis,
- self.channel_derivation_parameters.keys_id,
- );
- signer.provide_channel_parameters(&self.channel_derivation_parameters.transaction_parameters);
- signer
- }
-}
-
/// Represents the different types of transactions, originating from LDK, to be bumped.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum BumpTransactionEvent {
///
/// [`EcdsaChannelSigner`]: crate::sign::EcdsaChannelSigner
/// [`EcdsaChannelSigner::sign_holder_htlc_transaction`]: crate::sign::EcdsaChannelSigner::sign_holder_htlc_transaction
- /// [`HTLCDescriptor::tx_input_witness`]: HTLCDescriptor::tx_input_witness
HTLCResolution {
/// The unique identifier for the claim of the HTLCs in the confirmed commitment
/// transaction.
sender_intended_total_msat: Option<u64>,
},
/// Indicates a request for an invoice failed to yield a response in a reasonable amount of time
- /// or was explicitly abandoned by [`ChannelManager::abandon_payment`].
+ /// or was explicitly abandoned by [`ChannelManager::abandon_payment`]. This may be for an
+ /// [`InvoiceRequest`] sent for an [`Offer`] or for a [`Refund`] that hasn't been redeemed.
///
/// [`ChannelManager::abandon_payment`]: crate::ln::channelmanager::ChannelManager::abandon_payment
- #[cfg(invreqfailed)]
+ /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
+ /// [`Offer`]: crate::offers::offer::Offer
+ /// [`Refund`]: crate::offers::refund::Refund
InvoiceRequestFailed {
/// The `payment_id` to have been associated with payment for the requested invoice.
payment_id: PaymentId,
(8, funding_txo, required),
});
},
- #[cfg(invreqfailed)]
&Event::InvoiceRequestFailed { ref payment_id } => {
33u8.write(writer)?;
write_tlv_fields!(writer, {
};
f()
},
- #[cfg(invreqfailed)]
33u8 => {
let f = || {
_init_and_read_len_prefixed_tlv_fields!(reader, {
fn get_and_clear_pending_msg_events(&self) -> Vec<MessageSendEvent>;
}
-/// A trait indicating an object may generate onion messages to send
-pub trait OnionMessageProvider {
- /// Gets the next pending onion message for the peer with the given node id.
- fn next_onion_message_for_peer(&self, peer_node_id: PublicKey) -> Option<msgs::OnionMessage>;
-}
-
/// A trait indicating an object may generate events.
///
/// Events are processed by passing an [`EventHandler`] to [`process_pending_events`].
use crate::events::MessageSendEventsProvider;
use crate::ln::channelmanager;
use crate::ln::channelmanager::{PaymentId, RecipientOnionFields};
-use crate::ln::features::Bolt12InvoiceFeatures;
use crate::ln::functional_test_utils::*;
use crate::ln::outbound_payment::Retry;
use crate::prelude::*;
nodes[3].node.get_our_node_id(), payee_tlvs, &chanmon_cfgs[3].keys_manager, &secp_ctx
).unwrap();
- let bolt12_features: Bolt12InvoiceFeatures =
- channelmanager::provided_invoice_features(&UserConfig::default()).to_context();
+ let bolt12_features =
+ channelmanager::provided_bolt12_invoice_features(&UserConfig::default());
let route_params = RouteParameters::from_payment_params_and_value(
- PaymentParameters::blinded(vec![blinded_path])
- .with_bolt12_features(bolt12_features).unwrap(),
+ PaymentParameters::blinded(vec![blinded_path]).with_bolt12_features(bolt12_features).unwrap(),
amt_msat,
);
nodes[0].node.send_payment(payment_hash, RecipientOnionFields::spontaneous_empty(), PaymentId(payment_hash.0), route_params, Retry::Attempts(0)).unwrap();
self.commitment_number
}
+ /// The per commitment point used by the broadcaster.
+ pub fn per_commitment_point(&self) -> PublicKey {
+ self.keys.per_commitment_point
+ }
+
/// The value to be sent to the broadcaster
pub fn to_broadcaster_value_sat(&self) -> u64 {
self.to_broadcaster_value_sat
Ok(ret)
}
- /// Gets a signed HTLC transaction given a preimage (for !htlc.offered) and the holder HTLC transaction signature.
- pub(crate) fn get_signed_htlc_tx(&self, channel_parameters: &DirectedChannelTransactionParameters, htlc_index: usize, counterparty_signature: &Signature, signature: &Signature, preimage: &Option<PaymentPreimage>) -> Transaction {
- let inner = self.inner;
- let keys = &inner.keys;
- let txid = inner.built.txid;
- let this_htlc = &inner.htlcs[htlc_index];
+ /// Builds the second-level holder HTLC transaction for the HTLC with index `htlc_index`.
+ pub(crate) fn build_unsigned_htlc_tx(
+ &self, channel_parameters: &DirectedChannelTransactionParameters, htlc_index: usize,
+ preimage: &Option<PaymentPreimage>,
+ ) -> Transaction {
+ let keys = &self.inner.keys;
+ let this_htlc = &self.inner.htlcs[htlc_index];
assert!(this_htlc.transaction_output_index.is_some());
// if we don't have preimage for an HTLC-Success, we can't generate an HTLC transaction.
if !this_htlc.offered && preimage.is_none() { unreachable!(); }
// Further, we should never be provided the preimage for an HTLC-Timeout transaction.
if this_htlc.offered && preimage.is_some() { unreachable!(); }
- let mut htlc_tx = build_htlc_transaction(&txid, inner.feerate_per_kw, channel_parameters.contest_delay(), &this_htlc, &self.channel_type_features, &keys.broadcaster_delayed_payment_key, &keys.revocation_key);
+ build_htlc_transaction(
+ &self.inner.built.txid, self.inner.feerate_per_kw, channel_parameters.contest_delay(), &this_htlc,
+ &self.channel_type_features, &keys.broadcaster_delayed_payment_key, &keys.revocation_key
+ )
+ }
- let htlc_redeemscript = get_htlc_redeemscript_with_explicit_keys(&this_htlc, &self.channel_type_features, &keys.broadcaster_htlc_key, &keys.countersignatory_htlc_key, &keys.revocation_key);
- htlc_tx.input[0].witness = chan_utils::build_htlc_input_witness(
- signature, counterparty_signature, preimage, &htlc_redeemscript, &self.channel_type_features,
+ /// Builds the witness required to spend the input for the HTLC with index `htlc_index` in a
+ /// second-level holder HTLC transaction.
+ pub(crate) fn build_htlc_input_witness(
+ &self, htlc_index: usize, counterparty_signature: &Signature, signature: &Signature,
+ preimage: &Option<PaymentPreimage>
+ ) -> Witness {
+ let keys = &self.inner.keys;
+ let htlc_redeemscript = get_htlc_redeemscript_with_explicit_keys(
+ &self.inner.htlcs[htlc_index], &self.channel_type_features, &keys.broadcaster_htlc_key,
+ &keys.countersignatory_htlc_key, &keys.revocation_key
);
- htlc_tx
+ chan_utils::build_htlc_input_witness(
+ signature, counterparty_signature, preimage, &htlc_redeemscript, &self.channel_type_features,
+ )
}
/// Returns the index of the revokeable output, i.e. the `to_local` output sending funds to
do_test_reload_mon_update_completion_actions(true);
do_test_reload_mon_update_completion_actions(false);
}
+
+fn do_test_glacial_peer_cant_hang(hold_chan_a: bool) {
+ // Test that if a peer manages to send an `update_fulfill_htlc` message without a
+ // `commitment_signed`, disconnects, then replays the `update_fulfill_htlc` message it doesn't
+ // result in a channel hang. This was previously broken as the `DuplicateClaim` case wasn't
+ // handled when claiming an HTLC and handling wasn't added when completion actions were added
+ // (which must always complete at some point).
+ 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);
+
+ create_announced_chan_between_nodes(&nodes, 0, 1);
+ create_announced_chan_between_nodes(&nodes, 1, 2);
+
+ // Route a payment from A, through B, to C, then claim it on C. Replay the
+ // `update_fulfill_htlc` twice on B to check that B doesn't hang.
+ let (payment_preimage, payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 1_000_000);
+
+ nodes[2].node.claim_funds(payment_preimage);
+ check_added_monitors(&nodes[2], 1);
+ expect_payment_claimed!(nodes[2], payment_hash, 1_000_000);
+
+ let cs_updates = get_htlc_update_msgs(&nodes[2], &nodes[1].node.get_our_node_id());
+ if hold_chan_a {
+ // The first update will be on the A <-> B channel, which we allow to complete.
+ chanmon_cfgs[1].persister.set_update_ret(ChannelMonitorUpdateStatus::InProgress);
+ }
+ nodes[1].node.handle_update_fulfill_htlc(&nodes[2].node.get_our_node_id(), &cs_updates.update_fulfill_htlcs[0]);
+ check_added_monitors(&nodes[1], 1);
+
+ if !hold_chan_a {
+ let bs_updates = get_htlc_update_msgs(&nodes[1], &nodes[0].node.get_our_node_id());
+ nodes[0].node.handle_update_fulfill_htlc(&nodes[1].node.get_our_node_id(), &bs_updates.update_fulfill_htlcs[0]);
+ commitment_signed_dance!(nodes[0], nodes[1], bs_updates.commitment_signed, false);
+ expect_payment_sent!(&nodes[0], payment_preimage);
+ }
+
+ nodes[1].node.peer_disconnected(&nodes[2].node.get_our_node_id());
+ nodes[2].node.peer_disconnected(&nodes[1].node.get_our_node_id());
+
+ let mut reconnect = ReconnectArgs::new(&nodes[1], &nodes[2]);
+ reconnect.pending_htlc_claims = (1, 0);
+ reconnect_nodes(reconnect);
+
+ if !hold_chan_a {
+ expect_payment_forwarded!(nodes[1], nodes[0], nodes[2], Some(1000), false, false);
+ send_payment(&nodes[0], &[&nodes[1], &nodes[2]], 100_000);
+ } else {
+ assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
+ assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
+
+ let (route, payment_hash_2, _, payment_secret_2) = get_route_and_payment_hash!(&nodes[1], nodes[2], 1_000_000);
+
+ nodes[1].node.send_payment_with_route(&route, payment_hash_2,
+ RecipientOnionFields::secret_only(payment_secret_2), PaymentId(payment_hash_2.0)).unwrap();
+ check_added_monitors(&nodes[1], 0);
+
+ assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
+ assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
+ }
+}
+
+#[test]
+fn test_glacial_peer_cant_hang() {
+ do_test_glacial_peer_cant_hang(false);
+ do_test_glacial_peer_cant_hang(true);
+}
// You may not use this file except in accordance with one or both of these
// licenses.
+use bitcoin::blockdata::constants::ChainHash;
use bitcoin::blockdata::script::{Script,Builder};
use bitcoin::blockdata::transaction::{Transaction, EcdsaSighashType};
use bitcoin::util::sighash;
pub shutdown_msg: Option<msgs::Shutdown>,
}
-/// The return type of `force_shutdown`
-///
-/// Contains a tuple with the following:
-/// - An optional (counterparty_node_id, funding_txo, [`ChannelMonitorUpdate`]) tuple
-/// - A list of HTLCs to fail back in the form of the (source, payment hash, and this channel's
-/// counterparty_node_id and channel_id).
-/// - An optional transaction id identifying a corresponding batch funding transaction.
-pub(crate) type ShutdownResult = (
- Option<(PublicKey, OutPoint, ChannelMonitorUpdate)>,
- Vec<(HTLCSource, PaymentHash, PublicKey, ChannelId)>,
- Option<Txid>
-);
+/// The result of a shutdown that should be handled.
+#[must_use]
+pub(crate) struct ShutdownResult {
+ /// A channel monitor update to apply.
+ pub(crate) monitor_update: Option<(PublicKey, OutPoint, ChannelMonitorUpdate)>,
+ /// A list of dropped outbound HTLCs that can safely be failed backwards immediately.
+ pub(crate) dropped_outbound_htlcs: Vec<(HTLCSource, PaymentHash, PublicKey, ChannelId)>,
+ /// An unbroadcasted batch funding transaction id. The closure of this channel should be
+ /// propagated to the remainder of the batch.
+ pub(crate) unbroadcasted_batch_funding_txid: Option<Txid>,
+}
/// If the majority of the channels funds are to the fundee and the initiator holds only just
/// enough funds to cover their reserve value, channels are at risk of getting "stuck". Because the
cur_holder_commitment_transaction_number: u64,
cur_counterparty_commitment_transaction_number: u64,
- value_to_self_msat: u64, // Excluding all pending_htlcs, excluding fees
+ value_to_self_msat: u64, // Excluding all pending_htlcs, fees, and anchor outputs
pending_inbound_htlcs: Vec<InboundHTLCOutput>,
pending_outbound_htlcs: Vec<OutboundHTLCOutput>,
holding_cell_htlc_updates: Vec<HTLCUpdateAwaitingACK>,
match self.config.options.max_dust_htlc_exposure {
MaxDustHTLCExposure::FeeRateMultiplier(multiplier) => {
let feerate_per_kw = fee_estimator.bounded_sat_per_1000_weight(
- ConfirmationTarget::HighPriority);
+ ConfirmationTarget::OnChainSweep);
feerate_per_kw as u64 * multiplier
},
MaxDustHTLCExposure::FixedLimitMsat(limit) => limit,
let mut available_capacity_msat = outbound_capacity_msat;
+ let anchor_outputs_value_msat = if context.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
+ ANCHOR_OUTPUT_VALUE_SATOSHI * 2 * 1000
+ } else {
+ 0
+ };
if context.is_outbound() {
// We should mind channel commit tx fee when computing how much of the available capacity
// can be used in the next htlc. Mirrors the logic in send_htlc.
}
let htlc_above_dust = HTLCCandidate::new(real_dust_limit_timeout_sat * 1000, HTLCInitiator::LocalOffered);
- let max_reserved_commit_tx_fee_msat = FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE * context.next_local_commit_tx_fee_msat(htlc_above_dust, Some(()));
+ let mut max_reserved_commit_tx_fee_msat = context.next_local_commit_tx_fee_msat(htlc_above_dust, Some(()));
let htlc_dust = HTLCCandidate::new(real_dust_limit_timeout_sat * 1000 - 1, HTLCInitiator::LocalOffered);
- let min_reserved_commit_tx_fee_msat = FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE * context.next_local_commit_tx_fee_msat(htlc_dust, Some(()));
+ let mut min_reserved_commit_tx_fee_msat = context.next_local_commit_tx_fee_msat(htlc_dust, Some(()));
+ if !context.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
+ max_reserved_commit_tx_fee_msat *= FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE;
+ min_reserved_commit_tx_fee_msat *= FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE;
+ }
// We will first subtract the fee as if we were above-dust. Then, if the resulting
// value ends up being below dust, we have this fee available again. In that case,
// match the value to right-below-dust.
- let mut capacity_minus_commitment_fee_msat: i64 = (available_capacity_msat as i64) - (max_reserved_commit_tx_fee_msat as i64);
+ let mut capacity_minus_commitment_fee_msat: i64 = available_capacity_msat as i64 -
+ max_reserved_commit_tx_fee_msat as i64 - anchor_outputs_value_msat as i64;
if capacity_minus_commitment_fee_msat < (real_dust_limit_timeout_sat as i64) * 1000 {
let one_htlc_difference_msat = max_reserved_commit_tx_fee_msat - min_reserved_commit_tx_fee_msat;
debug_assert!(one_htlc_difference_msat != 0);
let remote_balance_msat = (context.channel_value_satoshis * 1000 - context.value_to_self_msat)
.saturating_sub(inbound_stats.pending_htlcs_value_msat);
- if remote_balance_msat < max_reserved_commit_tx_fee_msat + holder_selected_chan_reserve_msat {
+ if remote_balance_msat < max_reserved_commit_tx_fee_msat + holder_selected_chan_reserve_msat + anchor_outputs_value_msat {
// If another HTLC's fee would reduce the remote's balance below the reserve limit
// we've selected for them, we can only send dust HTLCs.
available_capacity_msat = cmp::min(available_capacity_msat, real_dust_limit_success_sat * 1000 - 1);
self.channel_state = ChannelState::ShutdownComplete as u32;
self.update_time_counter += 1;
- (monitor_update, dropped_outbound_htlcs, unbroadcasted_batch_funding_txid)
+ ShutdownResult {
+ monitor_update,
+ dropped_outbound_htlcs,
+ unbroadcasted_batch_funding_txid,
+ }
}
}
// Get the fee cost in MSATS of a commitment tx with a given number of HTLC outputs.
// Note that num_htlcs should not include dust HTLCs.
-fn commit_tx_fee_msat(feerate_per_kw: u32, num_htlcs: usize, channel_type_features: &ChannelTypeFeatures) -> u64 {
+pub(crate) fn commit_tx_fee_msat(feerate_per_kw: u32, num_htlcs: usize, channel_type_features: &ChannelTypeFeatures) -> u64 {
// Note that we need to divide before multiplying to round properly,
// since the lowest denomination of bitcoin on-chain is the satoshi.
(commitment_tx_base_weight(channel_type_features) + num_htlcs as u64 * COMMITMENT_TX_WEIGHT_PER_HTLC) * feerate_per_kw as u64 / 1000 * 1000
// apply to channels supporting anchor outputs since HTLC transactions are pre-signed with a
// zero fee, so their fee is no longer considered to determine dust limits.
if !channel_type.supports_anchors_zero_fee_htlc_tx() {
- let upper_limit = cmp::max(250 * 25,
- fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::HighPriority) as u64 * 10);
+ let upper_limit =
+ fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::MaxAllowedNonAnchorChannelRemoteFee) as u64;
if feerate_per_kw as u64 > upper_limit {
return Err(ChannelError::Close(format!("Peer's feerate much too high. Actual: {}. Our expected upper limit: {}", feerate_per_kw, upper_limit)));
}
}
- // We can afford to use a lower bound with anchors than previously since we can now bump
- // fees when broadcasting our commitment. However, we must still make sure we meet the
- // minimum mempool feerate, until package relay is deployed, such that we can ensure the
- // commitment transaction propagates throughout node mempools on its own.
let lower_limit_conf_target = if channel_type.supports_anchors_zero_fee_htlc_tx() {
- ConfirmationTarget::MempoolMinimum
+ ConfirmationTarget::MinAllowedAnchorChannelRemoteFee
} else {
- ConfirmationTarget::Background
+ ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee
};
let lower_limit = fee_estimator.bounded_sat_per_1000_weight(lower_limit_conf_target);
- // Some fee estimators round up to the next full sat/vbyte (ie 250 sats per kw), causing
- // occasional issues with feerate disagreements between an initiator that wants a feerate
- // of 1.1 sat/vbyte and a receiver that wants 1.1 rounded up to 2. Thus, we always add 250
- // sat/kw before the comparison here.
- if feerate_per_kw + 250 < lower_limit {
+ if feerate_per_kw < lower_limit {
if let Some(cur_feerate) = cur_feerate_per_kw {
if feerate_per_kw > cur_feerate {
log_warn!(logger,
return Ok(());
}
}
- return Err(ChannelError::Close(format!("Peer's feerate much too low. Actual: {}. Our expected lower limit: {} (- 250)", feerate_per_kw, lower_limit)));
+ return Err(ChannelError::Close(format!("Peer's feerate much too low. Actual: {}. Our expected lower limit: {}", feerate_per_kw, lower_limit)));
}
Ok(())
}
/// and the channel is now usable (and public), this may generate an announcement_signatures to
/// reply with.
pub fn channel_ready<NS: Deref, L: Deref>(
- &mut self, msg: &msgs::ChannelReady, node_signer: &NS, genesis_block_hash: BlockHash,
+ &mut self, msg: &msgs::ChannelReady, node_signer: &NS, chain_hash: ChainHash,
user_config: &UserConfig, best_block: &BestBlock, logger: &L
) -> Result<Option<msgs::AnnouncementSignatures>, ChannelError>
where
log_info!(logger, "Received channel_ready from peer for channel {}", &self.context.channel_id());
- Ok(self.get_announcement_sigs(node_signer, genesis_block_hash, user_config, best_block.height(), logger))
+ Ok(self.get_announcement_sigs(node_signer, chain_hash, user_config, best_block.height(), logger))
}
pub fn update_add_htlc<F, FE: Deref, L: Deref>(
if inbound_stats.pending_htlcs_value_msat + msg.amount_msat > self.context.holder_max_htlc_value_in_flight_msat {
return Err(ChannelError::Close(format!("Remote HTLC add would put them over our max HTLC value ({})", self.context.holder_max_htlc_value_in_flight_msat)));
}
+
// 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).
// 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 self.context.is_outbound() { 0 } else {
- let htlc_candidate = HTLCCandidate::new(msg.amount_msat, HTLCInitiator::RemoteOffered);
- self.context.next_remote_commit_tx_fee_msat(htlc_candidate, None) // Don't include the extra fee spike buffer HTLC in calculations
- };
- if pending_remote_value_msat - msg.amount_msat < remote_commit_tx_fee_msat {
- return Err(ChannelError::Close("Remote HTLC add would not leave enough to pay for fees".to_owned()));
- };
-
- if pending_remote_value_msat - msg.amount_msat - remote_commit_tx_fee_msat < self.context.holder_selected_channel_reserve_satoshis * 1000 {
- return Err(ChannelError::Close("Remote HTLC add would put them under remote reserve value".to_owned()));
+ {
+ let remote_commit_tx_fee_msat = if self.context.is_outbound() { 0 } else {
+ let htlc_candidate = HTLCCandidate::new(msg.amount_msat, HTLCInitiator::RemoteOffered);
+ self.context.next_remote_commit_tx_fee_msat(htlc_candidate, None) // Don't include the extra fee spike buffer HTLC in calculations
+ };
+ let anchor_outputs_value_msat = if !self.context.is_outbound() && self.context.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
+ ANCHOR_OUTPUT_VALUE_SATOSHI * 2 * 1000
+ } else {
+ 0
+ };
+ if pending_remote_value_msat.saturating_sub(msg.amount_msat).saturating_sub(anchor_outputs_value_msat) < remote_commit_tx_fee_msat {
+ return Err(ChannelError::Close("Remote HTLC add would not leave enough to pay for fees".to_owned()));
+ };
+ if pending_remote_value_msat.saturating_sub(msg.amount_msat).saturating_sub(remote_commit_tx_fee_msat).saturating_sub(anchor_outputs_value_msat) < self.context.holder_selected_channel_reserve_satoshis * 1000 {
+ return Err(ChannelError::Close("Remote HTLC add would put them under remote reserve value".to_owned()));
+ }
}
+ let anchor_outputs_value_msat = if self.context.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
+ ANCHOR_OUTPUT_VALUE_SATOSHI * 2 * 1000
+ } else {
+ 0
+ };
if !self.context.is_outbound() {
- // `2 *` and `Some(())` is for the fee spike buffer we keep for the remote. This deviates from
- // the spec because in the spec, the fee spike buffer requirement doesn't exist on the
- // receiver's side, only on the sender's.
- // Note that when we eventually remove support for fee updates and switch to anchor output
- // fees, we will drop the `2 *`, since we no longer be as sensitive to fee spikes. But, keep
- // the extra htlc when calculating the next remote commitment transaction fee as we should
- // still be able to afford adding this HTLC plus one more future HTLC, regardless of being
- // sensitive to fee spikes.
+ // `Some(())` is for the fee spike buffer we keep for the remote. 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.
let htlc_candidate = HTLCCandidate::new(msg.amount_msat, HTLCInitiator::RemoteOffered);
- let remote_fee_cost_incl_stuck_buffer_msat = 2 * self.context.next_remote_commit_tx_fee_msat(htlc_candidate, Some(()));
- if pending_remote_value_msat - msg.amount_msat - self.context.holder_selected_channel_reserve_satoshis * 1000 < remote_fee_cost_incl_stuck_buffer_msat {
+ let mut remote_fee_cost_incl_stuck_buffer_msat = self.context.next_remote_commit_tx_fee_msat(htlc_candidate, Some(()));
+ if !self.context.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
+ remote_fee_cost_incl_stuck_buffer_msat *= FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE;
+ }
+ if pending_remote_value_msat.saturating_sub(msg.amount_msat).saturating_sub(self.context.holder_selected_channel_reserve_satoshis * 1000).saturating_sub(anchor_outputs_value_msat) < remote_fee_cost_incl_stuck_buffer_msat {
// Note that if the pending_forward_status is not updated here, then it's because we're already failing
// the HTLC, i.e. its status is already set to failing.
log_info!(logger, "Attempting to fail HTLC due to fee spike buffer violation in channel {}. Rebalancing is required.", &self.context.channel_id());
// 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.context.next_local_commit_tx_fee_msat(htlc_candidate, None);
- if self.context.value_to_self_msat < self.context.counterparty_selected_channel_reserve_satoshis.unwrap() * 1000 + local_commit_tx_fee_msat {
+ if self.context.value_to_self_msat < self.context.counterparty_selected_channel_reserve_satoshis.unwrap() * 1000 + local_commit_tx_fee_msat + anchor_outputs_value_msat {
return Err(ChannelError::Close("Cannot accept HTLC that would put our balance under counterparty-announced channel reserve value".to_owned()));
}
}
/// successfully and we should restore normal operation. Returns messages which should be sent
/// to the remote side.
pub fn monitor_updating_restored<L: Deref, NS: Deref>(
- &mut self, logger: &L, node_signer: &NS, genesis_block_hash: BlockHash,
+ &mut self, logger: &L, node_signer: &NS, chain_hash: ChainHash,
user_config: &UserConfig, best_block_height: u32
) -> MonitorRestoreUpdates
where
})
} else { None };
- let announcement_sigs = self.get_announcement_sigs(node_signer, genesis_block_hash, user_config, best_block_height, logger);
+ let announcement_sigs = self.get_announcement_sigs(node_signer, chain_hash, user_config, best_block_height, logger);
let mut accepted_htlcs = Vec::new();
mem::swap(&mut accepted_htlcs, &mut self.context.monitor_pending_forwards);
/// [`super::channelmanager::ChannelManager::force_close_all_channels_without_broadcasting_txn`].
pub fn channel_reestablish<L: Deref, NS: Deref>(
&mut self, msg: &msgs::ChannelReestablish, logger: &L, node_signer: &NS,
- genesis_block_hash: BlockHash, user_config: &UserConfig, best_block: &BestBlock
+ chain_hash: ChainHash, user_config: &UserConfig, best_block: &BestBlock
) -> Result<ReestablishResponses, ChannelError>
where
L::Target: Logger,
if msg.next_local_commitment_number >= INITIAL_COMMITMENT_NUMBER || msg.next_remote_commitment_number >= INITIAL_COMMITMENT_NUMBER ||
msg.next_local_commitment_number == 0 {
- return Err(ChannelError::Close("Peer sent a garbage channel_reestablish (usually an lnd node with lost state asking us to force-close for them)".to_owned()));
+ return Err(ChannelError::Close("Peer sent an invalid channel_reestablish to force close in a non-standard way".to_owned()));
}
if msg.next_remote_commitment_number > 0 {
let shutdown_msg = self.get_outbound_shutdown();
- let announcement_sigs = self.get_announcement_sigs(node_signer, genesis_block_hash, user_config, best_block.height(), logger);
+ let announcement_sigs = self.get_announcement_sigs(node_signer, chain_hash, user_config, best_block.height(), logger);
if self.context.channel_state & (ChannelState::FundingSent as u32) == ChannelState::FundingSent as u32 {
// If we're waiting on a monitor update, we shouldn't re-send any channel_ready's.
// Propose a range from our current Background feerate to our Normal feerate plus our
// force_close_avoidance_max_fee_satoshis.
// If we fail to come to consensus, we'll have to force-close.
- let mut proposed_feerate = fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::Background);
- let normal_feerate = fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::Normal);
+ let mut proposed_feerate = fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::ChannelCloseMinimum);
+ // Use NonAnchorChannelFee because this should be an estimate for a channel close
+ // that we don't expect to need fee bumping
+ let normal_feerate = fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::NonAnchorChannelFee);
let mut proposed_max_feerate = if self.context.is_outbound() { normal_feerate } else { u32::max_value() };
// The spec requires that (when the channel does not have anchors) we only send absolute
pub fn maybe_propose_closing_signed<F: Deref, L: Deref>(
&mut self, fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L)
- -> Result<(Option<msgs::ClosingSigned>, Option<Transaction>), ChannelError>
+ -> Result<(Option<msgs::ClosingSigned>, Option<Transaction>, Option<ShutdownResult>), ChannelError>
where F::Target: FeeEstimator, L::Target: Logger
{
if self.context.last_sent_closing_fee.is_some() || !self.closing_negotiation_ready() {
- return Ok((None, None));
+ return Ok((None, None, None));
}
if !self.context.is_outbound() {
if let Some(msg) = &self.context.pending_counterparty_closing_signed.take() {
return self.closing_signed(fee_estimator, &msg);
}
- return Ok((None, None));
+ return Ok((None, None, None));
}
let (our_min_fee, our_max_fee) = self.calculate_closing_fee_limits(fee_estimator);
min_fee_satoshis: our_min_fee,
max_fee_satoshis: our_max_fee,
}),
- }), None))
+ }), None, None))
}
}
}
pub fn closing_signed<F: Deref>(
&mut self, fee_estimator: &LowerBoundedFeeEstimator<F>, msg: &msgs::ClosingSigned)
- -> Result<(Option<msgs::ClosingSigned>, Option<Transaction>), ChannelError>
+ -> Result<(Option<msgs::ClosingSigned>, Option<Transaction>, Option<ShutdownResult>), ChannelError>
where F::Target: FeeEstimator
{
if self.context.channel_state & BOTH_SIDES_SHUTDOWN_MASK != BOTH_SIDES_SHUTDOWN_MASK {
if self.context.channel_state & ChannelState::MonitorUpdateInProgress as u32 != 0 {
self.context.pending_counterparty_closing_signed = Some(msg.clone());
- return Ok((None, None));
+ return Ok((None, None, None));
}
let funding_redeemscript = self.context.get_funding_redeemscript();
assert!(self.context.shutdown_scriptpubkey.is_some());
if let Some((last_fee, sig)) = self.context.last_sent_closing_fee {
if last_fee == msg.fee_satoshis {
+ let shutdown_result = ShutdownResult {
+ monitor_update: None,
+ dropped_outbound_htlcs: Vec::new(),
+ unbroadcasted_batch_funding_txid: self.context.unbroadcasted_batch_funding_txid(),
+ };
let tx = self.build_signed_closing_transaction(&mut closing_tx, &msg.signature, &sig);
self.context.channel_state = ChannelState::ShutdownComplete as u32;
self.context.update_time_counter += 1;
- return Ok((None, Some(tx)));
+ return Ok((None, Some(tx), Some(shutdown_result)));
}
}
let sig = ecdsa
.sign_closing_transaction(&closing_tx, &self.context.secp_ctx)
.map_err(|_| ChannelError::Close("External signer refused to sign closing transaction".to_owned()))?;
-
- let signed_tx = if $new_fee == msg.fee_satoshis {
+ let (signed_tx, shutdown_result) = if $new_fee == msg.fee_satoshis {
+ let shutdown_result = ShutdownResult {
+ monitor_update: None,
+ dropped_outbound_htlcs: Vec::new(),
+ unbroadcasted_batch_funding_txid: self.context.unbroadcasted_batch_funding_txid(),
+ };
self.context.channel_state = ChannelState::ShutdownComplete as u32;
self.context.update_time_counter += 1;
let tx = self.build_signed_closing_transaction(&closing_tx, &msg.signature, &sig);
- Some(tx)
- } else { None };
+ (Some(tx), Some(shutdown_result))
+ } else {
+ (None, None)
+ };
self.context.last_sent_closing_fee = Some((used_fee, sig.clone()));
Ok((Some(msgs::ClosingSigned {
min_fee_satoshis: our_min_fee,
max_fee_satoshis: our_max_fee,
}),
- }), signed_tx))
+ }), signed_tx, shutdown_result))
}
}
}
/// In the second, we simply return an Err indicating we need to be force-closed now.
pub fn transactions_confirmed<NS: Deref, L: Deref>(
&mut self, block_hash: &BlockHash, height: u32, txdata: &TransactionData,
- genesis_block_hash: BlockHash, node_signer: &NS, user_config: &UserConfig, logger: &L
+ chain_hash: ChainHash, node_signer: &NS, user_config: &UserConfig, logger: &L
) -> Result<(Option<msgs::ChannelReady>, Option<msgs::AnnouncementSignatures>), ClosureReason>
where
NS::Target: NodeSigner,
// may have already happened for this block).
if let Some(channel_ready) = self.check_get_channel_ready(height) {
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, genesis_block_hash, user_config, height, logger);
+ let announcement_sigs = self.get_announcement_sigs(node_signer, chain_hash, user_config, height, logger);
msgs = (Some(channel_ready), announcement_sigs);
}
}
/// May return some HTLCs (and their payment_hash) which have timed out and should be failed
/// back.
pub fn best_block_updated<NS: Deref, L: Deref>(
- &mut self, height: u32, highest_header_time: u32, genesis_block_hash: BlockHash,
+ &mut self, height: u32, highest_header_time: u32, chain_hash: ChainHash,
node_signer: &NS, user_config: &UserConfig, logger: &L
) -> Result<(Option<msgs::ChannelReady>, Vec<(HTLCSource, PaymentHash)>, Option<msgs::AnnouncementSignatures>), ClosureReason>
where
NS::Target: NodeSigner,
L::Target: Logger
{
- self.do_best_block_updated(height, highest_header_time, Some((genesis_block_hash, node_signer, user_config)), logger)
+ self.do_best_block_updated(height, highest_header_time, Some((chain_hash, node_signer, user_config)), logger)
}
fn do_best_block_updated<NS: Deref, L: Deref>(
&mut self, height: u32, highest_header_time: u32,
- genesis_node_signer: Option<(BlockHash, &NS, &UserConfig)>, logger: &L
+ chain_node_signer: Option<(ChainHash, &NS, &UserConfig)>, logger: &L
) -> Result<(Option<msgs::ChannelReady>, Vec<(HTLCSource, PaymentHash)>, Option<msgs::AnnouncementSignatures>), ClosureReason>
where
NS::Target: NodeSigner,
self.context.update_time_counter = cmp::max(self.context.update_time_counter, highest_header_time);
if let Some(channel_ready) = self.check_get_channel_ready(height) {
- let announcement_sigs = if let Some((genesis_block_hash, node_signer, user_config)) = genesis_node_signer {
- self.get_announcement_sigs(node_signer, genesis_block_hash, user_config, height, logger)
+ let announcement_sigs = if let Some((chain_hash, node_signer, user_config)) = chain_node_signer {
+ self.get_announcement_sigs(node_signer, chain_hash, user_config, height, logger)
} else { None };
log_info!(logger, "Sending a channel_ready to our peer for channel {}", &self.context.channel_id);
return Ok((Some(channel_ready), timed_out_htlcs, announcement_sigs));
return Err(ClosureReason::FundingTimedOut);
}
- let announcement_sigs = if let Some((genesis_block_hash, node_signer, user_config)) = genesis_node_signer {
- self.get_announcement_sigs(node_signer, genesis_block_hash, user_config, height, logger)
+ let announcement_sigs = if let Some((chain_hash, node_signer, user_config)) = chain_node_signer {
+ self.get_announcement_sigs(node_signer, chain_hash, user_config, height, logger)
} else { None };
Ok((None, timed_out_htlcs, announcement_sigs))
}
// larger. If we don't know that time has moved forward, we can just set it to the last
// time we saw and it will be ignored.
let best_time = self.context.update_time_counter;
- match self.do_best_block_updated(reorg_height, best_time, None::<(BlockHash, &&NodeSigner, &UserConfig)>, logger) {
+ match self.do_best_block_updated(reorg_height, best_time, None::<(ChainHash, &&NodeSigner, &UserConfig)>, logger) {
Ok((channel_ready, timed_out_htlcs, announcement_sigs)) => {
assert!(channel_ready.is_none(), "We can't generate a funding with 0 confirmations?");
assert!(timed_out_htlcs.is_empty(), "We can't have accepted HTLCs with a timeout before our funding confirmation?");
///
/// [`ChannelReady`]: crate::ln::msgs::ChannelReady
fn get_channel_announcement<NS: Deref>(
- &self, node_signer: &NS, chain_hash: BlockHash, user_config: &UserConfig,
+ &self, node_signer: &NS, chain_hash: ChainHash, user_config: &UserConfig,
) -> Result<msgs::UnsignedChannelAnnouncement, ChannelError> where NS::Target: NodeSigner {
if !self.context.config.announced_channel {
return Err(ChannelError::Ignore("Channel is not available for public announcements".to_owned()));
}
fn get_announcement_sigs<NS: Deref, L: Deref>(
- &mut self, node_signer: &NS, genesis_block_hash: BlockHash, user_config: &UserConfig,
+ &mut self, node_signer: &NS, chain_hash: ChainHash, user_config: &UserConfig,
best_block_height: u32, logger: &L
) -> Option<msgs::AnnouncementSignatures>
where
}
log_trace!(logger, "Creating an announcement_signatures message for channel {}", &self.context.channel_id());
- let announcement = match self.get_channel_announcement(node_signer, genesis_block_hash, user_config) {
+ let announcement = match self.get_channel_announcement(node_signer, chain_hash, user_config) {
Ok(a) => a,
Err(e) => {
log_trace!(logger, "{:?}", e);
/// channel_announcement message which we can broadcast and storing our counterparty's
/// signatures for later reconstruction/rebroadcast of the channel_announcement.
pub fn announcement_signatures<NS: Deref>(
- &mut self, node_signer: &NS, chain_hash: BlockHash, best_block_height: u32,
+ &mut self, node_signer: &NS, chain_hash: ChainHash, best_block_height: u32,
msg: &msgs::AnnouncementSignatures, user_config: &UserConfig
) -> Result<msgs::ChannelAnnouncement, ChannelError> where NS::Target: NodeSigner {
let announcement = self.get_channel_announcement(node_signer, chain_hash, user_config)?;
/// Gets a signed channel_announcement for this channel, if we previously received an
/// announcement_signatures from our counterparty.
pub fn get_signed_channel_announcement<NS: Deref>(
- &self, node_signer: &NS, chain_hash: BlockHash, best_block_height: u32, user_config: &UserConfig
+ &self, node_signer: &NS, chain_hash: ChainHash, best_block_height: u32, user_config: &UserConfig
) -> Option<msgs::ChannelAnnouncement> where NS::Target: NodeSigner {
if self.context.funding_tx_confirmation_height == 0 || self.context.funding_tx_confirmation_height + 5 > best_block_height {
return None;
/// [`ChannelMonitorUpdate`] will be returned).
pub fn get_shutdown(&mut self, signer_provider: &SP, their_features: &InitFeatures,
target_feerate_sats_per_kw: Option<u32>, override_shutdown_script: Option<ShutdownScript>)
- -> Result<(msgs::Shutdown, Option<ChannelMonitorUpdate>, Vec<(HTLCSource, PaymentHash)>), APIError>
+ -> Result<(msgs::Shutdown, Option<ChannelMonitorUpdate>, Vec<(HTLCSource, PaymentHash)>, Option<ShutdownResult>), APIError>
{
for htlc in self.context.pending_outbound_htlcs.iter() {
if let OutboundHTLCState::LocalAnnounced(_) = htlc.state {
// From here on out, we may not fail!
self.context.target_closing_feerate_sats_per_kw = target_feerate_sats_per_kw;
- if self.context.channel_state & !STATE_FLAGS < ChannelState::FundingSent as u32 {
+ let shutdown_result = if self.context.channel_state & !STATE_FLAGS < ChannelState::FundingSent as u32 {
+ let shutdown_result = ShutdownResult {
+ monitor_update: None,
+ dropped_outbound_htlcs: Vec::new(),
+ unbroadcasted_batch_funding_txid: self.context.unbroadcasted_batch_funding_txid(),
+ };
self.context.channel_state = ChannelState::ShutdownComplete as u32;
+ Some(shutdown_result)
} else {
self.context.channel_state |= ChannelState::LocalShutdownSent as u32;
- }
+ None
+ };
self.context.update_time_counter += 1;
let monitor_update = if update_shutdown_script {
debug_assert!(!self.is_shutdown() || monitor_update.is_none(),
"we can't both complete shutdown and return a monitor update");
- Ok((shutdown, monitor_update, dropped_outbound_htlcs))
+ Ok((shutdown, monitor_update, dropped_outbound_htlcs, shutdown_result))
}
pub fn inflight_htlc_sources(&self) -> impl Iterator<Item=(&HTLCSource, &PaymentHash)> {
let channel_type = Self::get_initial_channel_type(&config, their_features);
debug_assert!(channel_type.is_subset(&channelmanager::provided_channel_type_features(&config)));
- let commitment_conf_target = if channel_type.supports_anchors_zero_fee_htlc_tx() {
- ConfirmationTarget::MempoolMinimum
+ let (commitment_conf_target, anchor_outputs_value_msat) = if channel_type.supports_anchors_zero_fee_htlc_tx() {
+ (ConfirmationTarget::AnchorChannelFee, ANCHOR_OUTPUT_VALUE_SATOSHI * 2 * 1000)
} else {
- ConfirmationTarget::Normal
+ (ConfirmationTarget::NonAnchorChannelFee, 0)
};
let commitment_feerate = fee_estimator.bounded_sat_per_1000_weight(commitment_conf_target);
let value_to_self_msat = channel_value_satoshis * 1000 - push_msat;
let commitment_tx_fee = commit_tx_fee_msat(commitment_feerate, MIN_AFFORDABLE_HTLC_COUNT, &channel_type);
- if value_to_self_msat < commitment_tx_fee {
+ if value_to_self_msat.saturating_sub(anchor_outputs_value_msat) < commitment_tx_fee {
return Err(APIError::APIMisuseError{ err: format!("Funding amount ({}) can't even pay fee for initial commitment transaction fee of {}.", value_to_self_msat / 1000, commitment_tx_fee / 1000) });
}
/// not of our ability to open any channel at all. Thus, on error, we should first call this
/// and see if we get a new `OpenChannel` message, otherwise the channel is failed.
pub(crate) fn maybe_handle_error_without_close<F: Deref>(
- &mut self, chain_hash: BlockHash, fee_estimator: &LowerBoundedFeeEstimator<F>
+ &mut self, chain_hash: ChainHash, fee_estimator: &LowerBoundedFeeEstimator<F>
) -> Result<msgs::OpenChannel, ()>
where
F::Target: FeeEstimator
// whatever reason.
if self.context.channel_type.supports_anchors_zero_fee_htlc_tx() {
self.context.channel_type.clear_anchors_zero_fee_htlc_tx();
- self.context.feerate_per_kw = fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::Normal);
+ self.context.feerate_per_kw = fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::NonAnchorChannelFee);
assert!(!self.context.channel_transaction_parameters.channel_type_features.supports_anchors_nonzero_fee_htlc_tx());
} else if self.context.channel_type.supports_scid_privacy() {
self.context.channel_type.clear_scid_privacy();
Ok(self.get_open_channel(chain_hash))
}
- pub fn get_open_channel(&self, chain_hash: BlockHash) -> msgs::OpenChannel {
+ pub fn get_open_channel(&self, chain_hash: ChainHash) -> msgs::OpenChannel {
if !self.context.is_outbound() {
panic!("Tried to open a channel for an inbound channel?");
}
// check if the funder's amount for the initial commitment tx is sufficient
// for full fee payment plus a few HTLCs to ensure the channel will be useful.
+ let anchor_outputs_value = if channel_type.supports_anchors_zero_fee_htlc_tx() {
+ ANCHOR_OUTPUT_VALUE_SATOSHI * 2
+ } else {
+ 0
+ };
let funders_amount_msat = msg.funding_satoshis * 1000 - msg.push_msat;
let commitment_tx_fee = commit_tx_fee_msat(msg.feerate_per_kw, MIN_AFFORDABLE_HTLC_COUNT, &channel_type) / 1000;
- if funders_amount_msat / 1000 < commitment_tx_fee {
- return Err(ChannelError::Close(format!("Funding amount ({} sats) can't even pay fee for initial commitment transaction fee of {} sats.", funders_amount_msat / 1000, commitment_tx_fee)));
+ if (funders_amount_msat / 1000).saturating_sub(anchor_outputs_value) < commitment_tx_fee {
+ return Err(ChannelError::Close(format!("Funding amount ({} sats) can't even pay fee for initial commitment transaction fee of {} sats.", (funders_amount_msat / 1000).saturating_sub(anchor_outputs_value), commitment_tx_fee)));
}
- let to_remote_satoshis = funders_amount_msat / 1000 - commitment_tx_fee;
+ let to_remote_satoshis = funders_amount_msat / 1000 - commitment_tx_fee - anchor_outputs_value;
// While it's reasonable for us to not meet the channel reserve initially (if they don't
// want to push much to us), our counterparty should always have more than our reserve.
if to_remote_satoshis < holder_selected_channel_reserve_satoshis {
#[cfg(test)]
mod tests {
use std::cmp;
+ use bitcoin::blockdata::constants::ChainHash;
use bitcoin::blockdata::script::{Script, Builder};
use bitcoin::blockdata::transaction::{Transaction, TxOut};
- use bitcoin::blockdata::constants::genesis_block;
use bitcoin::blockdata::opcodes;
use bitcoin::network::constants::Network;
use hex;
// Now change the fee so we can check that the fee in the open_channel message is the
// same as the old fee.
fee_est.fee_est = 500;
- let open_channel_msg = node_a_chan.get_open_channel(genesis_block(network).header.block_hash());
+ let open_channel_msg = node_a_chan.get_open_channel(ChainHash::using_genesis_block(network));
assert_eq!(open_channel_msg.feerate_per_kw, original_fee);
}
// Create Node B's channel by receiving Node A's open_channel message
// Make sure A's dust limit is as we expect.
- let open_channel_msg = node_a_chan.get_open_channel(genesis_block(network).header.block_hash());
+ let open_channel_msg = node_a_chan.get_open_channel(ChainHash::using_genesis_block(network));
let node_b_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[7; 32]).unwrap());
let mut node_b_chan = InboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider, node_b_node_id, &channelmanager::provided_channel_type_features(&config), &channelmanager::provided_init_features(&config), &open_channel_msg, 7, &config, 0, &&logger, /*is_0conf=*/false).unwrap();
let seed = [42; 32];
let network = Network::Testnet;
let best_block = BestBlock::from_network(network);
- let chain_hash = best_block.block_hash();
+ let chain_hash = ChainHash::using_genesis_block(network);
let keys_provider = test_utils::TestKeysInterface::new(&seed, network);
// Go through the flow of opening a channel between two nodes.
let chan_2_value_msat = chan_2.context.channel_value_satoshis * 1000;
assert_eq!(chan_2.context.holder_max_htlc_value_in_flight_msat, (chan_2_value_msat as f64 * 0.99) as u64);
- let chan_1_open_channel_msg = chan_1.get_open_channel(genesis_block(network).header.block_hash());
+ let chan_1_open_channel_msg = chan_1.get_open_channel(ChainHash::using_genesis_block(network));
// Test that `InboundV1Channel::new` creates a channel with the correct value for
// `holder_max_htlc_value_in_flight_msat`, when configured with a valid percentage value,
let expected_outbound_selected_chan_reserve = cmp::max(MIN_THEIR_CHAN_RESERVE_SATOSHIS, (chan.context.channel_value_satoshis as f64 * outbound_selected_channel_reserve_perc) as u64);
assert_eq!(chan.context.holder_selected_channel_reserve_satoshis, expected_outbound_selected_chan_reserve);
- let chan_open_channel_msg = chan.get_open_channel(genesis_block(network).header.block_hash());
+ let chan_open_channel_msg = chan.get_open_channel(ChainHash::using_genesis_block(network));
let mut inbound_node_config = UserConfig::default();
inbound_node_config.channel_handshake_config.their_channel_reserve_proportional_millionths = (inbound_selected_channel_reserve_perc * 1_000_000.0) as u32;
let seed = [42; 32];
let network = Network::Testnet;
let best_block = BestBlock::from_network(network);
- let chain_hash = genesis_block(network).header.block_hash();
+ let chain_hash = ChainHash::using_genesis_block(network);
let keys_provider = test_utils::TestKeysInterface::new(&seed, network);
// Create Node A's channel pointing to Node B's pubkey
// Create Node B's channel by receiving Node A's open_channel message
// Make sure A's dust limit is as we expect.
- let open_channel_msg = node_a_chan.get_open_channel(genesis_block(network).header.block_hash());
+ let open_channel_msg = node_a_chan.get_open_channel(ChainHash::using_genesis_block(network));
let node_b_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[7; 32]).unwrap());
let mut node_b_chan = InboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider, node_b_node_id, &channelmanager::provided_channel_type_features(&config), &channelmanager::provided_init_features(&config), &open_channel_msg, 7, &config, 0, &&logger, /*is_0conf=*/false).unwrap();
use bitcoin::hashes::hex::FromHex;
use bitcoin::hash_types::Txid;
use bitcoin::secp256k1::Message;
- use crate::sign::EcdsaChannelSigner;
+ use crate::sign::{ChannelDerivationParameters, HTLCDescriptor, EcdsaChannelSigner};
use crate::ln::PaymentPreimage;
use crate::ln::channel::{HTLCOutputInCommitment ,TxCreationKeys};
use crate::ln::chan_utils::{ChannelPublicKeys, HolderCommitmentTransaction, CounterpartyChannelTransactionParameters};
&chan.context.holder_signer.as_ref().pubkeys().funding_pubkey,
chan.context.counterparty_funding_pubkey()
);
- let (holder_sig, htlc_sigs) = signer.sign_holder_commitment_and_htlcs(&holder_commitment_tx, &secp_ctx).unwrap();
+ let holder_sig = signer.sign_holder_commitment(&holder_commitment_tx, &secp_ctx).unwrap();
assert_eq!(Signature::from_der(&hex::decode($sig_hex).unwrap()[..]).unwrap(), holder_sig, "holder_sig");
let funding_redeemscript = chan.context.get_funding_redeemscript();
assert_eq!(serialize(&tx)[..], hex::decode($tx_hex).unwrap()[..], "tx");
// ((htlc, counterparty_sig), (index, holder_sig))
- let mut htlc_sig_iter = holder_commitment_tx.htlcs().iter().zip(&holder_commitment_tx.counterparty_htlc_sigs).zip(htlc_sigs.iter().enumerate());
+ let mut htlc_counterparty_sig_iter = holder_commitment_tx.counterparty_htlc_sigs.iter();
$({
log_trace!(logger, "verifying htlc {}", $htlc_idx);
let remote_signature = Signature::from_der(&hex::decode($counterparty_htlc_sig_hex).unwrap()[..]).unwrap();
let ref htlc = htlcs[$htlc_idx];
- let htlc_tx = chan_utils::build_htlc_transaction(&unsigned_tx.txid, chan.context.feerate_per_kw,
+ let mut htlc_tx = chan_utils::build_htlc_transaction(&unsigned_tx.txid, chan.context.feerate_per_kw,
chan.context.get_counterparty_selected_contest_delay().unwrap(),
&htlc, $opt_anchors, &keys.broadcaster_delayed_payment_key, &keys.revocation_key);
let htlc_redeemscript = chan_utils::get_htlc_redeemscript(&htlc, $opt_anchors, &keys);
assert!(preimage.is_some());
}
- let htlc_sig = htlc_sig_iter.next().unwrap();
+ let htlc_counterparty_sig = htlc_counterparty_sig_iter.next().unwrap();
+ let htlc_holder_sig = signer.sign_holder_htlc_transaction(&htlc_tx, 0, &HTLCDescriptor {
+ channel_derivation_parameters: ChannelDerivationParameters {
+ value_satoshis: chan.context.channel_value_satoshis,
+ keys_id: chan.context.channel_keys_id,
+ transaction_parameters: chan.context.channel_transaction_parameters.clone(),
+ },
+ commitment_txid: trusted_tx.txid(),
+ per_commitment_number: trusted_tx.commitment_number(),
+ per_commitment_point: trusted_tx.per_commitment_point(),
+ feerate_per_kw: trusted_tx.feerate_per_kw(),
+ htlc: htlc.clone(),
+ preimage: preimage.clone(),
+ counterparty_sig: *htlc_counterparty_sig,
+ }, &secp_ctx).unwrap();
let num_anchors = if $opt_anchors.supports_anchors_zero_fee_htlc_tx() { 2 } else { 0 };
- assert_eq!((htlc_sig.0).0.transaction_output_index, Some($htlc_idx + num_anchors), "output index");
+ assert_eq!(htlc.transaction_output_index, Some($htlc_idx + num_anchors), "output index");
let signature = Signature::from_der(&hex::decode($htlc_sig_hex).unwrap()[..]).unwrap();
- assert_eq!(signature, *(htlc_sig.1).1, "htlc sig");
- let index = (htlc_sig.1).0;
- let channel_parameters = chan.context.channel_transaction_parameters.as_holder_broadcastable();
+ assert_eq!(signature, htlc_holder_sig, "htlc sig");
let trusted_tx = holder_commitment_tx.trust();
- log_trace!(logger, "htlc_tx = {}", hex::encode(serialize(&trusted_tx.get_signed_htlc_tx(&channel_parameters, index, &(htlc_sig.0).1, (htlc_sig.1).1, &preimage))));
- assert_eq!(serialize(&trusted_tx.get_signed_htlc_tx(&channel_parameters, index, &(htlc_sig.0).1, (htlc_sig.1).1, &preimage))[..],
- hex::decode($htlc_tx_hex).unwrap()[..], "htlc tx");
+ htlc_tx.input[0].witness = trusted_tx.build_htlc_input_witness($htlc_idx, htlc_counterparty_sig, &htlc_holder_sig, &preimage);
+ log_trace!(logger, "htlc_tx = {}", hex::encode(serialize(&htlc_tx)));
+ assert_eq!(serialize(&htlc_tx)[..], hex::decode($htlc_tx_hex).unwrap()[..], "htlc tx");
})*
- assert!(htlc_sig_iter.next().is_none());
+ assert!(htlc_counterparty_sig_iter.next().is_none());
} }
}
let mut channel_type_features = ChannelTypeFeatures::only_static_remote_key();
channel_type_features.set_zero_conf_required();
- let mut open_channel_msg = node_a_chan.get_open_channel(genesis_block(network).header.block_hash());
+ let mut open_channel_msg = node_a_chan.get_open_channel(ChainHash::using_genesis_block(network));
open_channel_msg.channel_type = Some(channel_type_features);
let node_b_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[7; 32]).unwrap());
let res = InboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider,
&channelmanager::provided_init_features(&config), 10000000, 100000, 42, &config, 0, 42
).unwrap();
- let open_channel_msg = channel_a.get_open_channel(genesis_block(network).header.block_hash());
+ let open_channel_msg = channel_a.get_open_channel(ChainHash::using_genesis_block(network));
let channel_b = InboundV1Channel::<&TestKeysInterface>::new(
&fee_estimator, &&keys_provider, &&keys_provider, node_id_a,
&channelmanager::provided_channel_type_features(&config), &channelmanager::provided_init_features(&config),
).unwrap();
// Set `channel_type` to `None` to force the implicit feature negotiation.
- let mut open_channel_msg = channel_a.get_open_channel(genesis_block(network).header.block_hash());
+ let mut open_channel_msg = channel_a.get_open_channel(ChainHash::using_genesis_block(network));
open_channel_msg.channel_type = None;
// Since A supports both `static_remote_key` and `option_anchors`, but B only accepts
&channelmanager::provided_init_features(&config), 10000000, 100000, 42, &config, 0, 42
).unwrap();
- let mut open_channel_msg = channel_a.get_open_channel(genesis_block(network).header.block_hash());
+ let mut open_channel_msg = channel_a.get_open_channel(ChainHash::using_genesis_block(network));
open_channel_msg.channel_type = Some(simple_anchors_channel_type.clone());
let res = InboundV1Channel::<&TestKeysInterface>::new(
10000000, 100000, 42, &config, 0, 42
).unwrap();
- let open_channel_msg = channel_a.get_open_channel(genesis_block(network).header.block_hash());
+ let open_channel_msg = channel_a.get_open_channel(ChainHash::using_genesis_block(network));
let channel_b = InboundV1Channel::<&TestKeysInterface>::new(
&fee_estimator, &&keys_provider, &&keys_provider, node_id_a,
let seed = [42; 32];
let network = Network::Testnet;
let best_block = BestBlock::from_network(network);
- let chain_hash = genesis_block(network).header.block_hash();
+ let chain_hash = ChainHash::using_genesis_block(network);
let keys_provider = test_utils::TestKeysInterface::new(&seed, network);
let mut config = UserConfig::default();
42,
).unwrap();
- let open_channel_msg = node_a_chan.get_open_channel(genesis_block(network).header.block_hash());
+ let open_channel_msg = node_a_chan.get_open_channel(ChainHash::using_genesis_block(network));
let node_b_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[7; 32]).unwrap());
let mut node_b_chan = InboundV1Channel::<&TestKeysInterface>::new(
&feeest,
use bitcoin::blockdata::block::BlockHeader;
use bitcoin::blockdata::transaction::Transaction;
-use bitcoin::blockdata::constants::{genesis_block, ChainHash};
+use bitcoin::blockdata::constants::ChainHash;
use bitcoin::network::constants::Network;
use bitcoin::hashes::Hash;
use bitcoin::secp256k1::Secp256k1;
use bitcoin::{LockTime, secp256k1, Sequence};
+use crate::blinded_path::BlindedPath;
+use crate::blinded_path::payment::{PaymentConstraints, ReceiveTlvs};
use crate::chain;
use crate::chain::{Confirm, ChannelMonitorUpdateStatus, Watch, BestBlock};
use crate::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator, LowerBoundedFeeEstimator};
// construct one themselves.
use crate::ln::{inbound_payment, ChannelId, PaymentHash, PaymentPreimage, PaymentSecret};
use crate::ln::channel::{Channel, ChannelPhase, ChannelContext, ChannelError, ChannelUpdateStatus, ShutdownResult, UnfundedChannelContext, UpdateFulfillCommitFetch, OutboundV1Channel, InboundV1Channel};
-use crate::ln::features::{ChannelFeatures, ChannelTypeFeatures, InitFeatures, NodeFeatures};
+use crate::ln::features::{Bolt12InvoiceFeatures, ChannelFeatures, ChannelTypeFeatures, InitFeatures, NodeFeatures};
#[cfg(any(feature = "_test_utils", test))]
use crate::ln::features::Bolt11InvoiceFeatures;
use crate::routing::gossip::NetworkGraph;
use crate::ln::msgs::{ChannelMessageHandler, DecodeError, LightningError};
#[cfg(test)]
use crate::ln::outbound_payment;
-use crate::ln::outbound_payment::{OutboundPayments, PaymentAttempts, PendingOutboundPayment, SendAlongPathArgs};
+use crate::ln::outbound_payment::{Bolt12PaymentError, OutboundPayments, PaymentAttempts, PendingOutboundPayment, SendAlongPathArgs, StaleExpiration};
use crate::ln::wire::Encode;
+use crate::offers::invoice::{BlindedPayInfo, Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, DerivedSigningPubkey, InvoiceBuilder};
+use crate::offers::invoice_error::InvoiceError;
+use crate::offers::merkle::SignError;
+use crate::offers::offer::{DerivedMetadata, Offer, OfferBuilder};
+use crate::offers::parse::Bolt12SemanticError;
+use crate::offers::refund::{Refund, RefundBuilder};
+use crate::onion_message::{Destination, OffersMessage, OffersMessageHandler, PendingOnionMessage, new_pending_onion_message};
use crate::sign::{EntropySource, KeysManager, NodeSigner, Recipient, SignerProvider, WriteableEcdsaChannelSigner};
use crate::util::config::{UserConfig, ChannelConfig, ChannelConfigUpdate};
use crate::util::wakers::{Future, Notifier};
}
#[inline]
fn from_finish_shutdown(err: String, channel_id: ChannelId, user_channel_id: u128, shutdown_res: ShutdownResult, channel_update: Option<msgs::ChannelUpdate>, channel_capacity: u64) -> Self {
+ let err_msg = msgs::ErrorMessage { channel_id, data: err.clone() };
+ let action = if shutdown_res.monitor_update.is_some() {
+ // We have a closing `ChannelMonitorUpdate`, which means the channel was funded and we
+ // should disconnect our peer such that we force them to broadcast their latest
+ // commitment upon reconnecting.
+ msgs::ErrorAction::DisconnectPeer { msg: Some(err_msg) }
+ } else {
+ msgs::ErrorAction::SendErrorMessage { msg: err_msg }
+ };
Self {
- err: LightningError {
- err: err.clone(),
- action: msgs::ErrorAction::SendErrorMessage {
- msg: msgs::ErrorMessage {
- channel_id,
- data: err
- },
- },
- },
+ err: LightningError { err, action },
chan_id: Some((channel_id, user_channel_id)),
shutdown_finish: Some((shutdown_res, channel_update)),
channel_capacity: Some(channel_capacity)
/// usually because we're running pre-full-init. They are handled immediately once we detect we are
/// running normally, and specifically must be processed before any other non-background
/// [`ChannelMonitorUpdate`]s are applied.
+#[derive(Debug)]
enum BackgroundEvent {
/// Handle a ChannelMonitorUpdate which closes the channel or for an already-closed channel.
/// This is only separated from [`Self::MonitorUpdateRegeneratedOnStartup`] as the
event: events::Event,
downstream_counterparty_and_funding_outpoint: Option<(PublicKey, OutPoint, RAAMonitorUpdateBlockingAction)>,
},
+ /// Indicates we should immediately resume the operation of another channel, unless there is
+ /// some other reason why the channel is blocked. In practice this simply means immediately
+ /// removing the [`RAAMonitorUpdateBlockingAction`] provided from the blocking set.
+ ///
+ /// This is usually generated when we've forwarded an HTLC and want to block the outbound edge
+ /// from completing a monitor update which removes the payment preimage until the inbound edge
+ /// completes a monitor update containing the payment preimage. However, we use this variant
+ /// instead of [`Self::EmitEventAndFreeOtherChannel`] when we discover that the claim was in
+ /// fact duplicative and we simply want to resume the outbound edge channel immediately.
+ ///
+ /// This variant should thus never be written to disk, as it is processed inline rather than
+ /// stored for later processing.
+ FreeOtherChannelImmediately {
+ downstream_counterparty_node_id: PublicKey,
+ downstream_funding_outpoint: OutPoint,
+ blocking_action: RAAMonitorUpdateBlockingAction,
+ },
}
impl_writeable_tlv_based_enum_upgradable!(MonitorUpdateCompletionAction,
(0, PaymentClaimed) => { (0, payment_hash, required) },
+ // Note that FreeOtherChannelImmediately should never be written - we were supposed to free
+ // *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, required),
+ },
(2, EmitEventAndFreeOtherChannel) => {
(0, event, upgradable_required),
// LDK prior to 0.0.116 did not have this field as the monitor update application order was
/// or, respectively, [`Router`] for its router, but this type alias chooses the concrete types
/// of [`KeysManager`] and [`DefaultRouter`].
///
-/// This is not exported to bindings users as Arcs don't make sense in bindings
+/// This is not exported to bindings users as type aliases aren't supported in most languages.
+#[cfg(not(c_bindings))]
pub type SimpleArcChannelManager<M, T, F, L> = ChannelManager<
Arc<M>,
Arc<T>,
/// or, respectively, [`Router`] for its router, but this type alias chooses the concrete types
/// of [`KeysManager`] and [`DefaultRouter`].
///
-/// This is not exported to bindings users as Arcs don't make sense in bindings
+/// This is not exported to bindings users as type aliases aren't supported in most languages.
+#[cfg(not(c_bindings))]
pub type SimpleRefChannelManager<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, M, T, F, L> =
ChannelManager<
&'a M,
//
// Lock order tree:
//
+// `pending_offers_messages`
+//
// `total_consistency_lock`
// |
// |__`forward_htlcs`
// | |__`pending_intercepted_htlcs`
// |
// |__`per_peer_state`
-// | |
-// | |__`pending_inbound_payments`
-// | |
-// | |__`claimable_payments`
-// | |
-// | |__`pending_outbound_payments` // This field's struct contains a map of pending outbounds
-// | |
-// | |__`peer_state`
-// | |
-// | |__`id_to_peer`
-// | |
-// | |__`short_to_chan_info`
-// | |
-// | |__`outbound_scid_aliases`
-// | |
-// | |__`best_block`
-// | |
-// | |__`pending_events`
-// | |
-// | |__`pending_background_events`
+// |
+// |__`pending_inbound_payments`
+// |
+// |__`claimable_payments`
+// |
+// |__`pending_outbound_payments` // This field's struct contains a map of pending outbounds
+// |
+// |__`peer_state`
+// |
+// |__`id_to_peer`
+// |
+// |__`short_to_chan_info`
+// |
+// |__`outbound_scid_aliases`
+// |
+// |__`best_block`
+// |
+// |__`pending_events`
+// |
+// |__`pending_background_events`
//
pub struct ChannelManager<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref>
where
L::Target: Logger,
{
default_configuration: UserConfig,
- genesis_hash: BlockHash,
+ chain_hash: ChainHash,
fee_estimator: LowerBoundedFeeEstimator<F>,
chain_monitor: M,
tx_broadcaster: T,
event_persist_notifier: Notifier,
needs_persist_flag: AtomicBool,
+ pending_offers_messages: Mutex<Vec<PendingOnionMessage<OffersMessage>>>,
+
entropy_source: ES,
node_signer: NS,
signer_provider: SP,
macro_rules! handle_monitor_update_completion {
($self: ident, $peer_state_lock: expr, $peer_state: expr, $per_peer_state_lock: expr, $chan: expr) => { {
let mut updates = $chan.monitor_updating_restored(&$self.logger,
- &$self.node_signer, $self.genesis_hash, &$self.default_configuration,
+ &$self.node_signer, $self.chain_hash, &$self.default_configuration,
$self.best_block.read().unwrap().height());
let counterparty_node_id = $chan.context.get_counterparty_node_id();
let channel_update = if updates.channel_ready.is_some() && $chan.context.is_usable() {
let expanded_inbound_key = inbound_payment::ExpandedKey::new(&inbound_pmt_key_material);
ChannelManager {
default_configuration: config.clone(),
- genesis_hash: genesis_block(params.network).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(params.network),
fee_estimator: LowerBoundedFeeEstimator::new(fee_est),
chain_monitor,
tx_broadcaster,
needs_persist_flag: AtomicBool::new(false),
funding_batch_states: Mutex::new(BTreeMap::new()),
+ pending_offers_messages: Mutex::new(Vec::new()),
+
entropy_source,
node_signer,
signer_provider,
if cfg!(fuzzing) { // fuzzing chacha20 doesn't use the key at all so we always get the same alias
outbound_scid_alias += 1;
} else {
- outbound_scid_alias = fake_scid::Namespace::OutboundAlias.get_fake_scid(height, &self.genesis_hash, &self.fake_scid_rand_bytes, &self.entropy_source);
+ outbound_scid_alias = fake_scid::Namespace::OutboundAlias.get_fake_scid(height, &self.chain_hash, &self.fake_scid_rand_bytes, &self.entropy_source);
}
if outbound_scid_alias != 0 && self.outbound_scid_aliases.lock().unwrap().insert(outbound_scid_alias) {
break;
},
}
};
- let res = channel.get_open_channel(self.genesis_hash.clone());
+ let res = channel.get_open_channel(self.chain_hash);
let temporary_channel_id = channel.context.channel_id();
match peer_state.channel_by_id.entry(temporary_channel_id) {
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
let mut failed_htlcs: Vec<(HTLCSource, PaymentHash)>;
- let mut shutdown_result = None;
+ let shutdown_result;
loop {
let per_peer_state = self.per_peer_state.read().unwrap();
if let ChannelPhase::Funded(chan) = chan_phase_entry.get_mut() {
let funding_txo_opt = chan.context.get_funding_txo();
let their_features = &peer_state.latest_features;
- let unbroadcasted_batch_funding_txid = chan.context.unbroadcasted_batch_funding_txid();
- let (shutdown_msg, mut monitor_update_opt, htlcs) =
+ let (shutdown_msg, mut monitor_update_opt, htlcs, local_shutdown_result) =
chan.get_shutdown(&self.signer_provider, their_features, target_feerate_sats_per_1000_weight, override_shutdown_script)?;
failed_htlcs = htlcs;
+ shutdown_result = local_shutdown_result;
+ debug_assert_eq!(shutdown_result.is_some(), chan.is_shutdown());
// We can send the `shutdown` message before updating the `ChannelMonitor`
// here as we don't need the monitor update to complete until we send a
});
}
self.issue_channel_close_events(&chan.context, ClosureReason::HolderForceClosed);
- shutdown_result = Some((None, Vec::new(), unbroadcasted_batch_funding_txid));
}
}
break;
/// will be accepted on the given channel, and after additional timeout/the closing of all
/// pending HTLCs, the channel will be closed on chain.
///
- /// * If we are the channel initiator, we will pay between our [`Background`] and
- /// [`ChannelConfig::force_close_avoidance_max_fee_satoshis`] plus our [`Normal`] fee
- /// estimate.
+ /// * If we are the channel initiator, we will pay between our [`ChannelCloseMinimum`] and
+ /// [`ChannelConfig::force_close_avoidance_max_fee_satoshis`] plus our [`NonAnchorChannelFee`]
+ /// fee estimate.
/// * If our counterparty is the channel initiator, we will require a channel closing
- /// transaction feerate of at least our [`Background`] feerate or the feerate which
+ /// transaction feerate of at least our [`ChannelCloseMinimum`] feerate or the feerate which
/// would appear on a force-closure transaction, whichever is lower. We will allow our
/// counterparty to pay as much fee as they'd like, however.
///
/// channel.
///
/// [`ChannelConfig::force_close_avoidance_max_fee_satoshis`]: crate::util::config::ChannelConfig::force_close_avoidance_max_fee_satoshis
- /// [`Background`]: crate::chain::chaininterface::ConfirmationTarget::Background
- /// [`Normal`]: crate::chain::chaininterface::ConfirmationTarget::Normal
+ /// [`ChannelCloseMinimum`]: crate::chain::chaininterface::ConfirmationTarget::ChannelCloseMinimum
+ /// [`NonAnchorChannelFee`]: crate::chain::chaininterface::ConfirmationTarget::NonAnchorChannelFee
/// [`SendShutdown`]: crate::events::MessageSendEvent::SendShutdown
pub fn close_channel(&self, channel_id: &ChannelId, counterparty_node_id: &PublicKey) -> Result<(), APIError> {
self.close_channel_internal(channel_id, counterparty_node_id, None, None)
/// the channel being closed or not:
/// * If we are the channel initiator, we will pay at least this feerate on the closing
/// transaction. The upper-bound is set by
- /// [`ChannelConfig::force_close_avoidance_max_fee_satoshis`] plus our [`Normal`] fee
- /// estimate (or `target_feerate_sat_per_1000_weight`, if it is greater).
+ /// [`ChannelConfig::force_close_avoidance_max_fee_satoshis`] plus our [`NonAnchorChannelFee`]
+ /// fee estimate (or `target_feerate_sat_per_1000_weight`, if it is greater).
/// * If our counterparty is the channel initiator, we will refuse to accept a channel closure
/// transaction feerate below `target_feerate_sat_per_1000_weight` (or the feerate which
/// will appear on a force-closure transaction, whichever is lower).
/// channel.
///
/// [`ChannelConfig::force_close_avoidance_max_fee_satoshis`]: crate::util::config::ChannelConfig::force_close_avoidance_max_fee_satoshis
- /// [`Background`]: crate::chain::chaininterface::ConfirmationTarget::Background
- /// [`Normal`]: crate::chain::chaininterface::ConfirmationTarget::Normal
+ /// [`NonAnchorChannelFee`]: crate::chain::chaininterface::ConfirmationTarget::NonAnchorChannelFee
/// [`SendShutdown`]: crate::events::MessageSendEvent::SendShutdown
pub fn close_channel_with_feerate_and_script(&self, channel_id: &ChannelId, counterparty_node_id: &PublicKey, target_feerate_sats_per_1000_weight: Option<u32>, shutdown_script: Option<ShutdownScript>) -> Result<(), APIError> {
self.close_channel_internal(channel_id, counterparty_node_id, target_feerate_sats_per_1000_weight, shutdown_script)
}
- fn finish_close_channel(&self, shutdown_res: ShutdownResult) {
+ fn finish_close_channel(&self, mut shutdown_res: ShutdownResult) {
debug_assert_ne!(self.per_peer_state.held_by_thread(), LockHeldState::HeldByThread);
#[cfg(debug_assertions)]
for (_, peer) in self.per_peer_state.read().unwrap().iter() {
debug_assert_ne!(peer.held_by_thread(), LockHeldState::HeldByThread);
}
- let (monitor_update_option, mut failed_htlcs, unbroadcasted_batch_funding_txid) = shutdown_res;
- log_debug!(self.logger, "Finishing force-closure of channel with {} HTLCs to fail", failed_htlcs.len());
- for htlc_source in failed_htlcs.drain(..) {
+ log_debug!(self.logger, "Finishing closure of channel with {} HTLCs to fail", shutdown_res.dropped_outbound_htlcs.len());
+ for htlc_source in shutdown_res.dropped_outbound_htlcs.drain(..) {
let (source, payment_hash, counterparty_node_id, channel_id) = htlc_source;
let reason = HTLCFailReason::from_failure_code(0x4000 | 8);
let receiver = HTLCDestination::NextHopChannel { node_id: Some(counterparty_node_id), channel_id };
self.fail_htlc_backwards_internal(&source, &payment_hash, &reason, receiver);
}
- if let Some((_, funding_txo, monitor_update)) = monitor_update_option {
+ if let Some((_, funding_txo, monitor_update)) = shutdown_res.monitor_update {
// There isn't anything we can do if we get an update failure - we're already
// force-closing. The monitor update on the required in-memory copy should broadcast
// the latest local state, which is the best we can do anyway. Thus, it is safe to
let _ = self.chain_monitor.update_channel(funding_txo, &monitor_update);
}
let mut shutdown_results = Vec::new();
- if let Some(txid) = unbroadcasted_batch_funding_txid {
+ if let Some(txid) = shutdown_res.unbroadcasted_batch_funding_txid {
let mut funding_batch_states = self.funding_batch_states.lock().unwrap();
let affected_channels = funding_batch_states.remove(&txid).into_iter().flatten();
let per_peer_state = self.per_peer_state.read().unwrap();
peer_state.pending_msg_events.push(
events::MessageSendEvent::HandleError {
node_id: counterparty_node_id,
- action: msgs::ErrorAction::SendErrorMessage {
- msg: msgs::ErrorMessage { channel_id: *channel_id, data: "Channel force-closed".to_owned() }
+ action: msgs::ErrorAction::DisconnectPeer {
+ msg: Some(msgs::ErrorMessage { channel_id: *channel_id, data: "Channel force-closed".to_owned() })
},
}
);
// Note that this is likely a timing oracle for detecting whether an scid is a
// phantom or an intercept.
if (self.default_configuration.accept_intercept_htlcs &&
- fake_scid::is_valid_intercept(&self.fake_scid_rand_bytes, outgoing_scid, &self.genesis_hash)) ||
- fake_scid::is_valid_phantom(&self.fake_scid_rand_bytes, outgoing_scid, &self.genesis_hash)
+ fake_scid::is_valid_intercept(&self.fake_scid_rand_bytes, outgoing_scid, &self.chain_hash)) ||
+ fake_scid::is_valid_phantom(&self.fake_scid_rand_bytes, outgoing_scid, &self.chain_hash)
{
None
} else {
};
let unsigned = msgs::UnsignedChannelUpdate {
- chain_hash: self.genesis_hash,
+ chain_hash: self.chain_hash,
short_channel_id,
timestamp: chan.context.get_update_time_counter(),
flags: (!were_node_one) as u8 | ((!enabled as u8) << 1),
self.pending_outbound_payments.test_set_payment_metadata(payment_id, new_payment_metadata);
}
+ pub(super) fn send_payment_for_bolt12_invoice(&self, invoice: &Bolt12Invoice, payment_id: PaymentId) -> Result<(), Bolt12PaymentError> {
+ let best_block_height = self.best_block.read().unwrap().height();
+ let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
+ self.pending_outbound_payments
+ .send_payment_for_bolt12_invoice(
+ invoice, payment_id, &self.router, self.list_usable_channels(),
+ || self.compute_inflight_htlcs(), &self.entropy_source, &self.node_signer,
+ best_block_height, &self.logger, &self.pending_events,
+ |args| self.send_payment_along_path(args)
+ )
+ }
/// Signals that no further attempts for the given payment should occur. Useful if you have a
/// pending outbound payment with retries remaining, but wish to stop retrying the payment before
/// wait until you receive either a [`Event::PaymentFailed`] or [`Event::PaymentSent`] event to
/// determine the ultimate status of a payment.
///
+ /// # Requested Invoices
+ ///
+ /// In the case of paying a [`Bolt12Invoice`] via [`ChannelManager::pay_for_offer`], abandoning
+ /// the payment prior to receiving the invoice will result in an [`Event::InvoiceRequestFailed`]
+ /// and prevent any attempts at paying it once received. The other events may only be generated
+ /// once the invoice has been received.
+ ///
/// # Restart Behavior
///
/// If an [`Event::PaymentFailed`] is generated and we restart without first persisting the
- /// [`ChannelManager`], another [`Event::PaymentFailed`] may be generated.
+ /// [`ChannelManager`], another [`Event::PaymentFailed`] may be generated; likewise for
+ /// [`Event::InvoiceRequestFailed`].
+ ///
+ /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
pub fn abandon_payment(&self, payment_id: PaymentId) {
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
self.pending_outbound_payments.abandon_payment(payment_id, PaymentFailureReason::UserAbandoned, &self.pending_events);
/// Return values are identical to [`Self::funding_transaction_generated`], respective to
/// each individual channel and transaction output.
///
- /// Do NOT broadcast the funding transaction yourself. This batch funding transcaction
+ /// Do NOT broadcast the funding transaction yourself. This batch funding transaction
/// will only be broadcast when we have safely received and persisted the counterparty's
/// signature for each channel.
///
btree_map::Entry::Vacant(vacant) => Some(vacant.insert(Vec::new())),
}
});
- for &(temporary_channel_id, counterparty_node_id) in temporary_channels.iter() {
+ for &(temporary_channel_id, counterparty_node_id) in temporary_channels {
result = result.and_then(|_| self.funding_transaction_generated_intern(
temporary_channel_id,
counterparty_node_id,
}
if let PendingHTLCRouting::Forward { onion_packet, .. } = routing {
let phantom_pubkey_res = self.node_signer.get_node_id(Recipient::PhantomNode);
- if phantom_pubkey_res.is_ok() && fake_scid::is_valid_phantom(&self.fake_scid_rand_bytes, short_chan_id, &self.genesis_hash) {
+ if phantom_pubkey_res.is_ok() && fake_scid::is_valid_phantom(&self.fake_scid_rand_bytes, short_chan_id, &self.chain_hash) {
let phantom_shared_secret = self.node_signer.ecdh(Recipient::PhantomNode, &onion_packet.public_key.unwrap(), None).unwrap().secret_bytes();
let next_hop = match onion_utils::decode_next_payment_hop(
phantom_shared_secret, &onion_packet.hop_data, onion_packet.hmac,
}
}
}
- let (counterparty_node_id, forward_chan_id) = match self.short_to_chan_info.read().unwrap().get(&short_chan_id) {
- Some((cp_id, chan_id)) => (cp_id.clone(), chan_id.clone()),
+ let chan_info_opt = self.short_to_chan_info.read().unwrap().get(&short_chan_id).cloned();
+ let (counterparty_node_id, forward_chan_id) = match chan_info_opt {
+ Some((cp_id, chan_id)) => (cp_id, chan_id),
None => {
forwarding_channel_not_found!();
continue;
PersistenceNotifierGuard::optionally_notify(self, || {
let mut should_persist = NotifyOption::SkipPersistNoEvents;
- let normal_feerate = self.fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::Normal);
- let min_mempool_feerate = self.fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::MempoolMinimum);
+ let non_anchor_feerate = self.fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::NonAnchorChannelFee);
+ let anchor_feerate = self.fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::AnchorChannelFee);
let per_peer_state = self.per_peer_state.read().unwrap();
for (_cp_id, peer_state_mutex) in per_peer_state.iter() {
|(chan_id, phase)| if let ChannelPhase::Funded(chan) = phase { Some((chan_id, chan)) } else { None }
) {
let new_feerate = if chan.context.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
- min_mempool_feerate
+ anchor_feerate
} else {
- normal_feerate
+ non_anchor_feerate
};
let chan_needs_persist = self.update_channel_fee(chan_id, chan, new_feerate);
if chan_needs_persist == NotifyOption::DoPersist { should_persist = NotifyOption::DoPersist; }
/// with the current [`ChannelConfig`].
/// * Removing peers which have disconnected but and no longer have any channels.
/// * Force-closing and removing channels which have not completed establishment in a timely manner.
+ /// * Forgetting about stale outbound payments, either those that have already been fulfilled
+ /// or those awaiting an invoice that hasn't been delivered in the necessary amount of time.
+ /// The latter is determined using the system clock in `std` and the highest seen block time
+ /// minus two hours in `no-std`.
///
/// Note that this may cause reentrancy through [`chain::Watch::update_channel`] calls or feerate
/// estimate fetches.
PersistenceNotifierGuard::optionally_notify(self, || {
let mut should_persist = NotifyOption::SkipPersistNoEvents;
- let normal_feerate = self.fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::Normal);
- let min_mempool_feerate = self.fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::MempoolMinimum);
+ let non_anchor_feerate = self.fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::NonAnchorChannelFee);
+ let anchor_feerate = self.fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::AnchorChannelFee);
let mut handle_errors: Vec<(Result<(), _>, _)> = Vec::new();
let mut timed_out_mpp_htlcs = Vec::new();
match phase {
ChannelPhase::Funded(chan) => {
let new_feerate = if chan.context.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
- min_mempool_feerate
+ anchor_feerate
} else {
- normal_feerate
+ non_anchor_feerate
};
let chan_needs_persist = self.update_channel_fee(chan_id, chan, new_feerate);
if chan_needs_persist == NotifyOption::DoPersist { should_persist = NotifyOption::DoPersist; }
self.finish_close_channel(shutdown_res);
}
- self.pending_outbound_payments.remove_stale_payments(&self.pending_events);
+ #[cfg(feature = "std")]
+ let duration_since_epoch = std::time::SystemTime::now()
+ .duration_since(std::time::SystemTime::UNIX_EPOCH)
+ .expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH");
+ #[cfg(not(feature = "std"))]
+ let duration_since_epoch = Duration::from_secs(
+ self.highest_seen_timestamp.load(Ordering::Acquire).saturating_sub(7200) as u64
+ );
+
+ self.pending_outbound_payments.remove_stale_payments(
+ duration_since_epoch, &self.pending_events
+ );
// Technically we don't need to do this here, but if we have holding cell entries in a
// channel that need freeing, it's better to do that here and block a background task
for htlc in sources.drain(..) {
if let Err((pk, err)) = self.claim_funds_from_hop(
htlc.prev_hop, payment_preimage,
- |_| Some(MonitorUpdateCompletionAction::PaymentClaimed { payment_hash }))
- {
+ |_, definitely_duplicate| {
+ debug_assert!(!definitely_duplicate, "We shouldn't claim duplicatively from a payment");
+ Some(MonitorUpdateCompletionAction::PaymentClaimed { payment_hash })
+ }
+ ) {
if let msgs::ErrorAction::IgnoreError = err.err.action {
// We got a temporary failure updating monitor, but will claim the
// HTLC when the monitor updating is restored (or on chain).
}
}
- fn claim_funds_from_hop<ComplFunc: FnOnce(Option<u64>) -> Option<MonitorUpdateCompletionAction>>(&self,
+ fn claim_funds_from_hop<ComplFunc: FnOnce(Option<u64>, bool) -> Option<MonitorUpdateCompletionAction>>(&self,
prev_hop: HTLCPreviousHopData, payment_preimage: PaymentPreimage, completion_action: ComplFunc)
-> Result<(), (PublicKey, MsgHandleErrInternal)> {
//TODO: Delay the claimed_funds relaying just like we do outbound relay!
// `BackgroundEvent`s.
let during_init = !self.background_events_processed_since_startup.load(Ordering::Acquire);
+ // As we may call handle_monitor_update_completion_actions in rather rare cases, check that
+ // the required mutexes are not held before we start.
+ debug_assert_ne!(self.pending_events.held_by_thread(), LockHeldState::HeldByThread);
+ debug_assert_ne!(self.claimable_payments.held_by_thread(), LockHeldState::HeldByThread);
+
{
let per_peer_state = self.per_peer_state.read().unwrap();
let chan_id = prev_hop.outpoint.to_channel_id();
let counterparty_node_id = chan.context.get_counterparty_node_id();
let fulfill_res = chan.get_update_fulfill_htlc_and_commit(prev_hop.htlc_id, payment_preimage, &self.logger);
- if let UpdateFulfillCommitFetch::NewClaim { htlc_value_msat, monitor_update } = fulfill_res {
- if let Some(action) = completion_action(Some(htlc_value_msat)) {
- log_trace!(self.logger, "Tracking monitor update completion action for channel {}: {:?}",
- chan_id, action);
- peer_state.monitor_update_blocked_actions.entry(chan_id).or_insert(Vec::new()).push(action);
+ match fulfill_res {
+ UpdateFulfillCommitFetch::NewClaim { htlc_value_msat, monitor_update } => {
+ if let Some(action) = completion_action(Some(htlc_value_msat), false) {
+ log_trace!(self.logger, "Tracking monitor update completion action for channel {}: {:?}",
+ chan_id, action);
+ peer_state.monitor_update_blocked_actions.entry(chan_id).or_insert(Vec::new()).push(action);
+ }
+ if !during_init {
+ handle_new_monitor_update!(self, prev_hop.outpoint, monitor_update, peer_state_lock,
+ peer_state, per_peer_state, chan);
+ } else {
+ // If we're running during init we cannot update a monitor directly -
+ // they probably haven't actually been loaded yet. Instead, push the
+ // monitor update as a background event.
+ self.pending_background_events.lock().unwrap().push(
+ BackgroundEvent::MonitorUpdateRegeneratedOnStartup {
+ counterparty_node_id,
+ funding_txo: prev_hop.outpoint,
+ update: monitor_update.clone(),
+ });
+ }
}
- if !during_init {
- handle_new_monitor_update!(self, prev_hop.outpoint, monitor_update, peer_state_lock,
- peer_state, per_peer_state, chan);
- } else {
- // If we're running during init we cannot update a monitor directly -
- // they probably haven't actually been loaded yet. Instead, push the
- // monitor update as a background event.
- self.pending_background_events.lock().unwrap().push(
- BackgroundEvent::MonitorUpdateRegeneratedOnStartup {
- counterparty_node_id,
- funding_txo: prev_hop.outpoint,
- update: monitor_update.clone(),
- });
+ UpdateFulfillCommitFetch::DuplicateClaim {} => {
+ let action = if let Some(action) = completion_action(None, true) {
+ action
+ } else {
+ return Ok(());
+ };
+ mem::drop(peer_state_lock);
+
+ log_trace!(self.logger, "Completing monitor update completion action for channel {} as claim was redundant: {:?}",
+ chan_id, action);
+ let (node_id, funding_outpoint, blocker) =
+ if let MonitorUpdateCompletionAction::FreeOtherChannelImmediately {
+ downstream_counterparty_node_id: node_id,
+ downstream_funding_outpoint: funding_outpoint,
+ blocking_action: blocker,
+ } = action {
+ (node_id, funding_outpoint, blocker)
+ } else {
+ debug_assert!(false,
+ "Duplicate claims should always free another channel immediately");
+ return Ok(());
+ };
+ if let Some(peer_state_mtx) = per_peer_state.get(&node_id) {
+ let mut peer_state = peer_state_mtx.lock().unwrap();
+ if let Some(blockers) = peer_state
+ .actions_blocking_raa_monitor_updates
+ .get_mut(&funding_outpoint.to_channel_id())
+ {
+ let mut found_blocker = false;
+ blockers.retain(|iter| {
+ // Note that we could actually be blocked, in
+ // which case we need to only remove the one
+ // blocker which was added duplicatively.
+ let first_blocker = !found_blocker;
+ if *iter == blocker { found_blocker = true; }
+ *iter != blocker || !first_blocker
+ });
+ debug_assert!(found_blocker);
+ }
+ } else {
+ debug_assert!(false);
+ }
}
}
}
// `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`).
- self.handle_monitor_update_completion_actions(completion_action(None));
+ self.handle_monitor_update_completion_actions(completion_action(None, false));
Ok(())
}
}
fn claim_funds_internal(&self, source: HTLCSource, payment_preimage: PaymentPreimage,
- forwarded_htlc_value_msat: Option<u64>, from_onchain: bool,
+ forwarded_htlc_value_msat: Option<u64>, from_onchain: bool, startup_replay: bool,
next_channel_counterparty_node_id: Option<PublicKey>, next_channel_outpoint: OutPoint
) {
match source {
HTLCSource::PreviousHopData(hop_data) => {
let prev_outpoint = hop_data.outpoint;
let completed_blocker = RAAMonitorUpdateBlockingAction::from_prev_hop_data(&hop_data);
+ #[cfg(debug_assertions)]
+ let claiming_chan_funding_outpoint = hop_data.outpoint;
let res = self.claim_funds_from_hop(hop_data, payment_preimage,
- |htlc_claim_value_msat| {
- if let Some(forwarded_htlc_value) = forwarded_htlc_value_msat {
- let fee_earned_msat = if let Some(claimed_htlc_value) = htlc_claim_value_msat {
- Some(claimed_htlc_value - forwarded_htlc_value)
- } else { None };
+ |htlc_claim_value_msat, definitely_duplicate| {
+ let chan_to_release =
+ if let Some(node_id) = next_channel_counterparty_node_id {
+ Some((node_id, next_channel_outpoint, completed_blocker))
+ } else {
+ // We can only get `None` here if we are processing a
+ // `ChannelMonitor`-originated event, in which case we
+ // don't care about ensuring we wake the downstream
+ // channel's monitor updating - the channel is already
+ // closed.
+ None
+ };
+ if definitely_duplicate && startup_replay {
+ // On startup we may get redundant claims which are related to
+ // monitor updates still in flight. In that case, we shouldn't
+ // immediately free, but instead let that monitor update complete
+ // in the background.
+ #[cfg(debug_assertions)] {
+ let background_events = self.pending_background_events.lock().unwrap();
+ // There should be a `BackgroundEvent` pending...
+ assert!(background_events.iter().any(|ev| {
+ match ev {
+ // to apply a monitor update that blocked the claiming channel,
+ BackgroundEvent::MonitorUpdateRegeneratedOnStartup {
+ funding_txo, update, ..
+ } => {
+ if *funding_txo == claiming_chan_funding_outpoint {
+ assert!(update.updates.iter().any(|upd|
+ if let ChannelMonitorUpdateStep::PaymentPreimage {
+ payment_preimage: update_preimage
+ } = upd {
+ payment_preimage == *update_preimage
+ } else { false }
+ ), "{:?}", update);
+ true
+ } else { false }
+ },
+ // or the channel we'd unblock is already closed,
+ BackgroundEvent::ClosedMonitorUpdateRegeneratedOnStartup(
+ (funding_txo, monitor_update)
+ ) => {
+ if *funding_txo == next_channel_outpoint {
+ assert_eq!(monitor_update.updates.len(), 1);
+ assert!(matches!(
+ monitor_update.updates[0],
+ ChannelMonitorUpdateStep::ChannelForceClosed { .. }
+ ));
+ true
+ } else { false }
+ },
+ // or the monitor update has completed and will unblock
+ // immediately once we get going.
+ BackgroundEvent::MonitorUpdatesComplete {
+ channel_id, ..
+ } =>
+ *channel_id == claiming_chan_funding_outpoint.to_channel_id(),
+ }
+ }), "{:?}", *background_events);
+ }
+ None
+ } else if definitely_duplicate {
+ if let Some(other_chan) = chan_to_release {
+ Some(MonitorUpdateCompletionAction::FreeOtherChannelImmediately {
+ downstream_counterparty_node_id: other_chan.0,
+ downstream_funding_outpoint: other_chan.1,
+ blocking_action: other_chan.2,
+ })
+ } else { None }
+ } else {
+ let fee_earned_msat = if let Some(forwarded_htlc_value) = forwarded_htlc_value_msat {
+ if let Some(claimed_htlc_value) = htlc_claim_value_msat {
+ Some(claimed_htlc_value - forwarded_htlc_value)
+ } else { None }
+ } else { None };
Some(MonitorUpdateCompletionAction::EmitEventAndFreeOtherChannel {
event: events::Event::PaymentForwarded {
fee_earned_msat,
next_channel_id: Some(next_channel_outpoint.to_channel_id()),
outbound_amount_forwarded_msat: forwarded_htlc_value_msat,
},
- downstream_counterparty_and_funding_outpoint:
- if let Some(node_id) = next_channel_counterparty_node_id {
- Some((node_id, next_channel_outpoint, completed_blocker))
- } else {
- // We can only get `None` here if we are processing a
- // `ChannelMonitor`-originated event, in which case we
- // don't care about ensuring we wake the downstream
- // channel's monitor updating - the channel is already
- // closed.
- None
- },
+ downstream_counterparty_and_funding_outpoint: chan_to_release,
})
- } else { None }
+ }
});
if let Err((pk, err)) = res {
let result: Result<(), _> = Err(err);
}
fn handle_monitor_update_completion_actions<I: IntoIterator<Item=MonitorUpdateCompletionAction>>(&self, actions: I) {
+ debug_assert_ne!(self.pending_events.held_by_thread(), LockHeldState::HeldByThread);
+ debug_assert_ne!(self.claimable_payments.held_by_thread(), LockHeldState::HeldByThread);
+ debug_assert_ne!(self.per_peer_state.held_by_thread(), LockHeldState::HeldByThread);
+
for action in actions.into_iter() {
match action {
MonitorUpdateCompletionAction::PaymentClaimed { payment_hash } => {
self.handle_monitor_update_release(node_id, funding_outpoint, Some(blocker));
}
},
+ MonitorUpdateCompletionAction::FreeOtherChannelImmediately {
+ downstream_counterparty_node_id, downstream_funding_outpoint, blocking_action,
+ } => {
+ self.handle_monitor_update_release(
+ downstream_counterparty_node_id,
+ downstream_funding_outpoint,
+ Some(blocking_action),
+ );
+ },
}
}
}
fn internal_open_channel(&self, counterparty_node_id: &PublicKey, msg: &msgs::OpenChannel) -> Result<(), MsgHandleErrInternal> {
// Note that the ChannelManager is NOT re-persisted on disk after this, so any changes are
// likely to be lost on restart!
- if msg.chain_hash != self.genesis_hash {
+ if msg.chain_hash != self.chain_hash {
return Err(MsgHandleErrInternal::send_err_msg_no_close("Unknown genesis block hash".to_owned(), msg.temporary_channel_id.clone()));
}
hash_map::Entry::Occupied(mut chan_phase_entry) => {
if let ChannelPhase::Funded(chan) = chan_phase_entry.get_mut() {
let announcement_sigs_opt = try_chan_phase_entry!(self, chan.channel_ready(&msg, &self.node_signer,
- self.genesis_hash.clone(), &self.default_configuration, &self.best_block.read().unwrap(), &self.logger), chan_phase_entry);
+ self.chain_hash, &self.default_configuration, &self.best_block.read().unwrap(), &self.logger), chan_phase_entry);
if let Some(announcement_sigs) = announcement_sigs_opt {
log_trace!(self.logger, "Sending announcement_signatures for channel {}", chan.context.channel_id());
peer_state.pending_msg_events.push(events::MessageSendEvent::SendAnnouncementSignatures {
}
fn internal_closing_signed(&self, counterparty_node_id: &PublicKey, msg: &msgs::ClosingSigned) -> Result<(), MsgHandleErrInternal> {
- let mut shutdown_result = None;
- let unbroadcasted_batch_funding_txid;
let per_peer_state = self.per_peer_state.read().unwrap();
let peer_state_mutex = per_peer_state.get(counterparty_node_id)
.ok_or_else(|| {
debug_assert!(false);
MsgHandleErrInternal::send_err_msg_no_close(format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id), msg.channel_id)
})?;
- let (tx, chan_option) = {
+ let (tx, chan_option, shutdown_result) = {
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
let peer_state = &mut *peer_state_lock;
match peer_state.channel_by_id.entry(msg.channel_id.clone()) {
hash_map::Entry::Occupied(mut chan_phase_entry) => {
if let ChannelPhase::Funded(chan) = chan_phase_entry.get_mut() {
- unbroadcasted_batch_funding_txid = chan.context.unbroadcasted_batch_funding_txid();
- let (closing_signed, tx) = try_chan_phase_entry!(self, chan.closing_signed(&self.fee_estimator, &msg), chan_phase_entry);
+ let (closing_signed, tx, shutdown_result) = try_chan_phase_entry!(self, chan.closing_signed(&self.fee_estimator, &msg), chan_phase_entry);
+ debug_assert_eq!(shutdown_result.is_some(), chan.is_shutdown());
if let Some(msg) = closing_signed {
peer_state.pending_msg_events.push(events::MessageSendEvent::SendClosingSigned {
node_id: counterparty_node_id.clone(),
// also implies there are no pending HTLCs left on the channel, so we can
// fully delete it from tracking (the channel monitor is still around to
// watch for old state broadcasts)!
- (tx, Some(remove_channel_phase!(self, chan_phase_entry)))
- } else { (tx, None) }
+ (tx, Some(remove_channel_phase!(self, chan_phase_entry)), shutdown_result)
+ } else { (tx, None, shutdown_result) }
} else {
return try_chan_phase_entry!(self, Err(ChannelError::Close(
"Got a closing_signed message for an unfunded channel!".into())), chan_phase_entry);
});
}
self.issue_channel_close_events(&chan.context, ClosureReason::CooperativeClosure);
- shutdown_result = Some((None, Vec::new(), unbroadcasted_batch_funding_txid));
}
mem::drop(per_peer_state);
if let Some(shutdown_result) = shutdown_result {
if let ChannelPhase::Funded(chan) = chan_phase_entry.get_mut() {
let res = try_chan_phase_entry!(self, chan.update_fulfill_htlc(&msg), chan_phase_entry);
if let HTLCSource::PreviousHopData(prev_hop) = &res.0 {
+ log_trace!(self.logger,
+ "Holding the next revoke_and_ack from {} until the preimage is durably persisted in the inbound edge's ChannelMonitor",
+ msg.channel_id);
peer_state.actions_blocking_raa_monitor_updates.entry(msg.channel_id)
.or_insert_with(Vec::new)
.push(RAAMonitorUpdateBlockingAction::from_prev_hop_data(&prev_hop));
hash_map::Entry::Vacant(_) => return Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}", counterparty_node_id), msg.channel_id))
}
};
- self.claim_funds_internal(htlc_source, msg.payment_preimage.clone(), Some(forwarded_htlc_value), false, Some(*counterparty_node_id), funding_txo);
+ self.claim_funds_internal(htlc_source, msg.payment_preimage.clone(), Some(forwarded_htlc_value), false, false, Some(*counterparty_node_id), funding_txo);
Ok(())
}
},
hash_map::Entry::Vacant(entry) => {
if !is_our_scid && forward_info.incoming_amt_msat.is_some() &&
- fake_scid::is_valid_intercept(&self.fake_scid_rand_bytes, scid, &self.genesis_hash)
+ fake_scid::is_valid_intercept(&self.fake_scid_rand_bytes, scid, &self.chain_hash)
{
let intercept_id = InterceptId(Sha256::hash(&forward_info.incoming_shared_secret).into_inner());
let mut pending_intercepts = self.pending_intercepted_htlcs.lock().unwrap();
peer_state.pending_msg_events.push(events::MessageSendEvent::BroadcastChannelAnnouncement {
msg: try_chan_phase_entry!(self, chan.announcement_signatures(
- &self.node_signer, self.genesis_hash.clone(), self.best_block.read().unwrap().height(),
+ &self.node_signer, self.chain_hash, self.best_block.read().unwrap().height(),
msg, &self.default_configuration
), chan_phase_entry),
// Note that announcement_signatures fails if the channel cannot be announced,
let peer_state_mutex = per_peer_state.get(counterparty_node_id)
.ok_or_else(|| {
debug_assert!(false);
- MsgHandleErrInternal::send_err_msg_no_close(format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id), msg.channel_id)
+ MsgHandleErrInternal::send_err_msg_no_close(
+ format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id),
+ msg.channel_id
+ )
})?;
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
let peer_state = &mut *peer_state_lock;
// freed HTLCs to fail backwards. If in the future we no longer drop pending
// add-HTLCs on disconnect, we may be handed HTLCs to fail backwards here.
let responses = try_chan_phase_entry!(self, chan.channel_reestablish(
- msg, &self.logger, &self.node_signer, self.genesis_hash,
+ msg, &self.logger, &self.node_signer, self.chain_hash,
&self.default_configuration, &*self.best_block.read().unwrap()), chan_phase_entry);
let mut channel_update = None;
if let Some(msg) = responses.shutdown_msg {
"Got a channel_reestablish message for an unfunded channel!".into())), chan_phase_entry);
}
},
- hash_map::Entry::Vacant(_) => return Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}", counterparty_node_id), msg.channel_id))
+ hash_map::Entry::Vacant(_) => {
+ log_debug!(self.logger, "Sending bogus ChannelReestablish for unknown channel {} to force channel closure",
+ log_bytes!(msg.channel_id.0));
+ // Unfortunately, lnd doesn't force close on errors
+ // (https://github.com/lightningnetwork/lnd/blob/abb1e3463f3a83bbb843d5c399869dbe930ad94f/htlcswitch/link.go#L2119).
+ // One of the few ways to get an lnd counterparty to force close is by
+ // replicating what they do when restoring static channel backups (SCBs). They
+ // send an invalid `ChannelReestablish` with `0` commitment numbers and an
+ // invalid `your_last_per_commitment_secret`.
+ //
+ // Since we received a `ChannelReestablish` for a channel that doesn't exist, we
+ // can assume it's likely the channel closed from our point of view, but it
+ // remains open on the counterparty's side. By sending this bogus
+ // `ChannelReestablish` message now as a response to theirs, we trigger them to
+ // force close broadcasting their latest state. If the closing transaction from
+ // our point of view remains unconfirmed, it'll enter a race with the
+ // counterparty's to-be-broadcast latest commitment transaction.
+ peer_state.pending_msg_events.push(MessageSendEvent::SendChannelReestablish {
+ node_id: *counterparty_node_id,
+ msg: msgs::ChannelReestablish {
+ channel_id: msg.channel_id,
+ next_local_commitment_number: 0,
+ next_remote_commitment_number: 0,
+ your_last_per_commitment_secret: [1u8; 32],
+ my_current_per_commitment_point: PublicKey::from_slice(&[2u8; 33]).unwrap(),
+ next_funding_txid: None,
+ },
+ });
+ return Err(MsgHandleErrInternal::send_err_msg_no_close(
+ format!("Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}",
+ counterparty_node_id), msg.channel_id)
+ )
+ }
}
};
MonitorEvent::HTLCEvent(htlc_update) => {
if let Some(preimage) = htlc_update.payment_preimage {
log_trace!(self.logger, "Claiming HTLC with preimage {} from our monitor", preimage);
- self.claim_funds_internal(htlc_update.source, preimage, htlc_update.htlc_value_satoshis.map(|v| v * 1000), true, counterparty_node_id, funding_outpoint);
+ self.claim_funds_internal(htlc_update.source, preimage, htlc_update.htlc_value_satoshis.map(|v| v * 1000), true, false, counterparty_node_id, funding_outpoint);
} else {
log_trace!(self.logger, "Failing HTLC with hash {} from our monitor", &htlc_update.payment_hash);
let receiver = HTLCDestination::NextHopChannel { node_id: counterparty_node_id, channel_id: funding_outpoint.to_channel_id() };
self.issue_channel_close_events(&chan.context, ClosureReason::HolderForceClosed);
pending_msg_events.push(events::MessageSendEvent::HandleError {
node_id: chan.context.get_counterparty_node_id(),
- action: msgs::ErrorAction::SendErrorMessage {
- msg: msgs::ErrorMessage { channel_id: chan.context.channel_id(), data: "Channel force-closed".to_owned() }
+ action: msgs::ErrorAction::DisconnectPeer {
+ msg: Some(msgs::ErrorMessage { channel_id: chan.context.channel_id(), data: "Channel force-closed".to_owned() })
},
});
}
peer_state.channel_by_id.retain(|channel_id, phase| {
match phase {
ChannelPhase::Funded(chan) => {
- let unbroadcasted_batch_funding_txid = chan.context.unbroadcasted_batch_funding_txid();
match chan.maybe_propose_closing_signed(&self.fee_estimator, &self.logger) {
- Ok((msg_opt, tx_opt)) => {
+ Ok((msg_opt, tx_opt, shutdown_result_opt)) => {
if let Some(msg) = msg_opt {
has_update = true;
pending_msg_events.push(events::MessageSendEvent::SendClosingSigned {
node_id: chan.context.get_counterparty_node_id(), msg,
});
}
+ debug_assert_eq!(shutdown_result_opt.is_some(), chan.is_shutdown());
+ if let Some(shutdown_result) = shutdown_result_opt {
+ shutdown_results.push(shutdown_result);
+ }
if let Some(tx) = tx_opt {
// We're done with this channel. We got a closing_signed and sent back
// a closing_signed with a closing transaction to broadcast.
log_info!(self.logger, "Broadcasting {}", log_tx!(tx));
self.tx_broadcaster.broadcast_transactions(&[&tx]);
update_maps_on_chan_removal!(self, &chan.context);
- shutdown_results.push((None, Vec::new(), unbroadcasted_batch_funding_txid));
false
} else { true }
},
// Channel::force_shutdown tries to make us do) as we may still be in initialization,
// so we track the update internally and handle it when the user next calls
// timer_tick_occurred, guaranteeing we're running normally.
- if let Some((counterparty_node_id, funding_txo, update)) = failure.0.take() {
+ if let Some((counterparty_node_id, funding_txo, update)) = failure.monitor_update.take() {
assert_eq!(update.updates.len(), 1);
if let ChannelMonitorUpdateStep::ChannelForceClosed { should_broadcast } = update.updates[0] {
assert!(should_broadcast);
}
}
+ /// Creates an [`OfferBuilder`] such that the [`Offer`] it builds is recognized by the
+ /// [`ChannelManager`] when handling [`InvoiceRequest`] messages for the offer. The offer will
+ /// not have an expiration unless otherwise set on the builder.
+ ///
+ /// # Privacy
+ ///
+ /// Uses a one-hop [`BlindedPath`] for the offer with [`ChannelManager::get_our_node_id`] as the
+ /// introduction node and a derived signing pubkey for recipient privacy. As such, currently,
+ /// the node must be announced. Otherwise, there is no way to find a path to the introduction
+ /// node in order to send the [`InvoiceRequest`].
+ ///
+ /// # Limitations
+ ///
+ /// Requires a direct connection to the introduction node in the responding [`InvoiceRequest`]'s
+ /// reply path.
+ ///
+ /// This is not exported to bindings users as builder patterns don't map outside of move semantics.
+ ///
+ /// [`Offer`]: crate::offers::offer::Offer
+ /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
+ pub fn create_offer_builder(
+ &self, description: String
+ ) -> OfferBuilder<DerivedMetadata, secp256k1::All> {
+ let node_id = self.get_our_node_id();
+ let expanded_key = &self.inbound_payment_key;
+ let entropy = &*self.entropy_source;
+ let secp_ctx = &self.secp_ctx;
+ let path = self.create_one_hop_blinded_path();
+
+ OfferBuilder::deriving_signing_pubkey(description, node_id, expanded_key, entropy, secp_ctx)
+ .chain_hash(self.chain_hash)
+ .path(path)
+ }
+
+ /// Creates a [`RefundBuilder`] such that the [`Refund`] it builds is recognized by the
+ /// [`ChannelManager`] when handling [`Bolt12Invoice`] messages for the refund.
+ ///
+ /// # Payment
+ ///
+ /// The provided `payment_id` is used to ensure that only one invoice is paid for the refund.
+ /// See [Avoiding Duplicate Payments] for other requirements once the payment has been sent.
+ ///
+ /// The builder will have the provided expiration set. Any changes to the expiration on the
+ /// returned builder will not be honored by [`ChannelManager`]. For `no-std`, the highest seen
+ /// block time minus two hours is used for the current time when determining if the refund has
+ /// expired.
+ ///
+ /// To revoke the refund, use [`ChannelManager::abandon_payment`] prior to receiving the
+ /// invoice. If abandoned, or an invoice isn't received before expiration, the payment will fail
+ /// with an [`Event::InvoiceRequestFailed`].
+ ///
+ /// If `max_total_routing_fee_msat` is not specified, The default from
+ /// [`RouteParameters::from_payment_params_and_value`] is applied.
+ ///
+ /// # Privacy
+ ///
+ /// Uses a one-hop [`BlindedPath`] for the refund with [`ChannelManager::get_our_node_id`] as
+ /// the introduction node and a derived payer id for payer privacy. As such, currently, the
+ /// node must be announced. Otherwise, there is no way to find a path to the introduction node
+ /// in order to send the [`Bolt12Invoice`].
+ ///
+ /// # Limitations
+ ///
+ /// Requires a direct connection to an introduction node in the responding
+ /// [`Bolt12Invoice::payment_paths`].
+ ///
+ /// # Errors
+ ///
+ /// Errors if a duplicate `payment_id` is provided given the caveats in the aforementioned link
+ /// or if `amount_msats` is invalid.
+ ///
+ /// This is not exported to bindings users as builder patterns don't map outside of move semantics.
+ ///
+ /// [`Refund`]: crate::offers::refund::Refund
+ /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
+ /// [`Bolt12Invoice::payment_paths`]: crate::offers::invoice::Bolt12Invoice::payment_paths
+ pub fn create_refund_builder(
+ &self, description: String, amount_msats: u64, absolute_expiry: Duration,
+ payment_id: PaymentId, retry_strategy: Retry, max_total_routing_fee_msat: Option<u64>
+ ) -> Result<RefundBuilder<secp256k1::All>, Bolt12SemanticError> {
+ let node_id = self.get_our_node_id();
+ let expanded_key = &self.inbound_payment_key;
+ let entropy = &*self.entropy_source;
+ let secp_ctx = &self.secp_ctx;
+ let path = self.create_one_hop_blinded_path();
+
+ let builder = RefundBuilder::deriving_payer_id(
+ description, node_id, expanded_key, entropy, secp_ctx, amount_msats, payment_id
+ )?
+ .chain_hash(self.chain_hash)
+ .absolute_expiry(absolute_expiry)
+ .path(path);
+
+ let expiration = StaleExpiration::AbsoluteTimeout(absolute_expiry);
+ self.pending_outbound_payments
+ .add_new_awaiting_invoice(
+ payment_id, expiration, retry_strategy, max_total_routing_fee_msat,
+ )
+ .map_err(|_| Bolt12SemanticError::DuplicatePaymentId)?;
+
+ Ok(builder)
+ }
+
+ /// Pays for an [`Offer`] using the given parameters by creating an [`InvoiceRequest`] and
+ /// enqueuing it to be sent via an onion message. [`ChannelManager`] will pay the actual
+ /// [`Bolt12Invoice`] once it is received.
+ ///
+ /// Uses [`InvoiceRequestBuilder`] such that the [`InvoiceRequest`] it builds is recognized by
+ /// the [`ChannelManager`] when handling a [`Bolt12Invoice`] message in response to the request.
+ /// The optional parameters are used in the builder, if `Some`:
+ /// - `quantity` for [`InvoiceRequest::quantity`] which must be set if
+ /// [`Offer::expects_quantity`] is `true`.
+ /// - `amount_msats` if overpaying what is required for the given `quantity` is desired, and
+ /// - `payer_note` for [`InvoiceRequest::payer_note`].
+ ///
+ /// If `max_total_routing_fee_msat` is not specified, The default from
+ /// [`RouteParameters::from_payment_params_and_value`] is applied.
+ ///
+ /// # Payment
+ ///
+ /// The provided `payment_id` is used to ensure that only one invoice is paid for the request
+ /// when received. See [Avoiding Duplicate Payments] for other requirements once the payment has
+ /// been sent.
+ ///
+ /// To revoke the request, use [`ChannelManager::abandon_payment`] prior to receiving the
+ /// invoice. If abandoned, or an invoice isn't received in a reasonable amount of time, the
+ /// payment will fail with an [`Event::InvoiceRequestFailed`].
+ ///
+ /// # Privacy
+ ///
+ /// Uses a one-hop [`BlindedPath`] for the reply path with [`ChannelManager::get_our_node_id`]
+ /// as the introduction node and a derived payer id for payer privacy. As such, currently, the
+ /// node must be announced. Otherwise, there is no way to find a path to the introduction node
+ /// in order to send the [`Bolt12Invoice`].
+ ///
+ /// # Limitations
+ ///
+ /// Requires a direct connection to an introduction node in [`Offer::paths`] or to
+ /// [`Offer::signing_pubkey`], if empty. A similar restriction applies to the responding
+ /// [`Bolt12Invoice::payment_paths`].
+ ///
+ /// # Errors
+ ///
+ /// Errors if a duplicate `payment_id` is provided given the caveats in the aforementioned link
+ /// or if the provided parameters are invalid for the offer.
+ ///
+ /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
+ /// [`InvoiceRequest::quantity`]: crate::offers::invoice_request::InvoiceRequest::quantity
+ /// [`InvoiceRequest::payer_note`]: crate::offers::invoice_request::InvoiceRequest::payer_note
+ /// [`InvoiceRequestBuilder`]: crate::offers::invoice_request::InvoiceRequestBuilder
+ /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
+ /// [`Bolt12Invoice::payment_paths`]: crate::offers::invoice::Bolt12Invoice::payment_paths
+ /// [Avoiding Duplicate Payments]: #avoiding-duplicate-payments
+ pub fn pay_for_offer(
+ &self, offer: &Offer, quantity: Option<u64>, amount_msats: Option<u64>,
+ payer_note: Option<String>, payment_id: PaymentId, retry_strategy: Retry,
+ max_total_routing_fee_msat: Option<u64>
+ ) -> Result<(), Bolt12SemanticError> {
+ let expanded_key = &self.inbound_payment_key;
+ let entropy = &*self.entropy_source;
+ let secp_ctx = &self.secp_ctx;
+
+ let builder = offer
+ .request_invoice_deriving_payer_id(expanded_key, entropy, secp_ctx, payment_id)?
+ .chain_hash(self.chain_hash)?;
+ let builder = match quantity {
+ None => builder,
+ Some(quantity) => builder.quantity(quantity)?,
+ };
+ let builder = match amount_msats {
+ None => builder,
+ Some(amount_msats) => builder.amount_msats(amount_msats)?,
+ };
+ let builder = match payer_note {
+ None => builder,
+ Some(payer_note) => builder.payer_note(payer_note),
+ };
+
+ let invoice_request = builder.build_and_sign()?;
+ let reply_path = self.create_one_hop_blinded_path();
+
+ let expiration = StaleExpiration::TimerTicks(1);
+ self.pending_outbound_payments
+ .add_new_awaiting_invoice(
+ payment_id, expiration, retry_strategy, max_total_routing_fee_msat
+ )
+ .map_err(|_| Bolt12SemanticError::DuplicatePaymentId)?;
+
+ let mut pending_offers_messages = self.pending_offers_messages.lock().unwrap();
+ if offer.paths().is_empty() {
+ let message = new_pending_onion_message(
+ OffersMessage::InvoiceRequest(invoice_request),
+ Destination::Node(offer.signing_pubkey()),
+ Some(reply_path),
+ );
+ pending_offers_messages.push(message);
+ } else {
+ // Send as many invoice requests as there are paths in the offer (with an upper bound).
+ // Using only one path could result in a failure if the path no longer exists. But only
+ // one invoice for a given payment id will be paid, even if more than one is received.
+ const REQUEST_LIMIT: usize = 10;
+ for path in offer.paths().into_iter().take(REQUEST_LIMIT) {
+ let message = new_pending_onion_message(
+ OffersMessage::InvoiceRequest(invoice_request.clone()),
+ Destination::BlindedPath(path.clone()),
+ Some(reply_path.clone()),
+ );
+ pending_offers_messages.push(message);
+ }
+ }
+
+ Ok(())
+ }
+
+ /// Creates a [`Bolt12Invoice`] for a [`Refund`] and enqueues it to be sent via an onion
+ /// message.
+ ///
+ /// The resulting invoice uses a [`PaymentHash`] recognized by the [`ChannelManager`] and a
+ /// [`BlindedPath`] containing the [`PaymentSecret`] needed to reconstruct the corresponding
+ /// [`PaymentPreimage`].
+ ///
+ /// # Limitations
+ ///
+ /// Requires a direct connection to an introduction node in [`Refund::paths`] or to
+ /// [`Refund::payer_id`], if empty. This request is best effort; an invoice will be sent to each
+ /// node meeting the aforementioned criteria, but there's no guarantee that they will be
+ /// received and no retries will be made.
+ ///
+ /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
+ pub fn request_refund_payment(&self, refund: &Refund) -> Result<(), Bolt12SemanticError> {
+ let expanded_key = &self.inbound_payment_key;
+ let entropy = &*self.entropy_source;
+ let secp_ctx = &self.secp_ctx;
+
+ let amount_msats = refund.amount_msats();
+ let relative_expiry = DEFAULT_RELATIVE_EXPIRY.as_secs() as u32;
+
+ match self.create_inbound_payment(Some(amount_msats), relative_expiry, None) {
+ Ok((payment_hash, payment_secret)) => {
+ let payment_paths = vec![
+ self.create_one_hop_blinded_payment_path(payment_secret),
+ ];
+ #[cfg(not(feature = "no-std"))]
+ let builder = refund.respond_using_derived_keys(
+ payment_paths, payment_hash, expanded_key, entropy
+ )?;
+ #[cfg(feature = "no-std")]
+ let created_at = Duration::from_secs(
+ self.highest_seen_timestamp.load(Ordering::Acquire) as u64
+ );
+ #[cfg(feature = "no-std")]
+ let builder = refund.respond_using_derived_keys_no_std(
+ payment_paths, payment_hash, created_at, expanded_key, entropy
+ )?;
+ let invoice = builder.allow_mpp().build_and_sign(secp_ctx)?;
+ let reply_path = self.create_one_hop_blinded_path();
+
+ let mut pending_offers_messages = self.pending_offers_messages.lock().unwrap();
+ if refund.paths().is_empty() {
+ let message = new_pending_onion_message(
+ OffersMessage::Invoice(invoice),
+ Destination::Node(refund.payer_id()),
+ Some(reply_path),
+ );
+ pending_offers_messages.push(message);
+ } else {
+ for path in refund.paths() {
+ let message = new_pending_onion_message(
+ OffersMessage::Invoice(invoice.clone()),
+ Destination::BlindedPath(path.clone()),
+ Some(reply_path.clone()),
+ );
+ pending_offers_messages.push(message);
+ }
+ }
+
+ Ok(())
+ },
+ Err(()) => Err(Bolt12SemanticError::InvalidAmount),
+ }
+ }
+
/// Gets a payment secret and payment hash for use in an invoice given to a third party wishing
/// to pay us.
///
inbound_payment::get_payment_preimage(payment_hash, payment_secret, &self.inbound_payment_key)
}
+ /// Creates a one-hop blinded path with [`ChannelManager::get_our_node_id`] as the introduction
+ /// node.
+ fn create_one_hop_blinded_path(&self) -> BlindedPath {
+ let entropy_source = self.entropy_source.deref();
+ let secp_ctx = &self.secp_ctx;
+ BlindedPath::one_hop_for_message(self.get_our_node_id(), entropy_source, secp_ctx).unwrap()
+ }
+
+ /// Creates a one-hop blinded path with [`ChannelManager::get_our_node_id`] as the introduction
+ /// node.
+ fn create_one_hop_blinded_payment_path(
+ &self, payment_secret: PaymentSecret
+ ) -> (BlindedPayInfo, BlindedPath) {
+ let entropy_source = self.entropy_source.deref();
+ let secp_ctx = &self.secp_ctx;
+
+ let payee_node_id = self.get_our_node_id();
+ let max_cltv_expiry = self.best_block.read().unwrap().height() + LATENCY_GRACE_PERIOD_BLOCKS;
+ let payee_tlvs = ReceiveTlvs {
+ payment_secret,
+ payment_constraints: PaymentConstraints {
+ max_cltv_expiry,
+ htlc_minimum_msat: 1,
+ },
+ };
+ // TODO: Err for overflow?
+ BlindedPath::one_hop_for_payment(
+ payee_node_id, payee_tlvs, entropy_source, secp_ctx
+ ).unwrap()
+ }
+
/// Gets a fake short channel id for use in receiving [phantom node payments]. These fake scids
/// are used when constructing the phantom invoice's route hints.
///
let best_block_height = self.best_block.read().unwrap().height();
let short_to_chan_info = self.short_to_chan_info.read().unwrap();
loop {
- let scid_candidate = fake_scid::Namespace::Phantom.get_fake_scid(best_block_height, &self.genesis_hash, &self.fake_scid_rand_bytes, &self.entropy_source);
+ let scid_candidate = fake_scid::Namespace::Phantom.get_fake_scid(best_block_height, &self.chain_hash, &self.fake_scid_rand_bytes, &self.entropy_source);
// Ensure the generated scid doesn't conflict with a real channel.
match short_to_chan_info.get(&scid_candidate) {
Some(_) => continue,
let best_block_height = self.best_block.read().unwrap().height();
let short_to_chan_info = self.short_to_chan_info.read().unwrap();
loop {
- let scid_candidate = fake_scid::Namespace::Intercept.get_fake_scid(best_block_height, &self.genesis_hash, &self.fake_scid_rand_bytes, &self.entropy_source);
+ let scid_candidate = fake_scid::Namespace::Intercept.get_fake_scid(best_block_height, &self.chain_hash, &self.fake_scid_rand_bytes, &self.entropy_source);
// Ensure the generated scid doesn't conflict with a real channel.
if short_to_chan_info.contains_key(&scid_candidate) { continue }
return scid_candidate
*best_block = BestBlock::new(header.prev_blockhash, new_height)
}
- self.do_chain_event(Some(new_height), |channel| channel.best_block_updated(new_height, header.time, self.genesis_hash.clone(), &self.node_signer, &self.default_configuration, &self.logger));
+ self.do_chain_event(Some(new_height), |channel| channel.best_block_updated(new_height, header.time, self.chain_hash, &self.node_signer, &self.default_configuration, &self.logger));
}
}
let _persistence_guard =
PersistenceNotifierGuard::optionally_notify_skipping_background_events(
self, || -> NotifyOption { NotifyOption::DoPersist });
- self.do_chain_event(Some(height), |channel| channel.transactions_confirmed(&block_hash, height, txdata, self.genesis_hash.clone(), &self.node_signer, &self.default_configuration, &self.logger)
+ self.do_chain_event(Some(height), |channel| channel.transactions_confirmed(&block_hash, height, txdata, self.chain_hash, &self.node_signer, &self.default_configuration, &self.logger)
.map(|(a, b)| (a, Vec::new(), b)));
let last_best_block_height = self.best_block.read().unwrap().height();
if height < last_best_block_height {
let timestamp = self.highest_seen_timestamp.load(Ordering::Acquire);
- self.do_chain_event(Some(last_best_block_height), |channel| channel.best_block_updated(last_best_block_height, timestamp as u32, self.genesis_hash.clone(), &self.node_signer, &self.default_configuration, &self.logger));
+ self.do_chain_event(Some(last_best_block_height), |channel| channel.best_block_updated(last_best_block_height, timestamp as u32, self.chain_hash, &self.node_signer, &self.default_configuration, &self.logger));
}
}
self, || -> NotifyOption { NotifyOption::DoPersist });
*self.best_block.write().unwrap() = BestBlock::new(block_hash, height);
- self.do_chain_event(Some(height), |channel| channel.best_block_updated(height, header.time, self.genesis_hash.clone(), &self.node_signer, &self.default_configuration, &self.logger));
+ self.do_chain_event(Some(height), |channel| channel.best_block_updated(height, header.time, self.chain_hash, &self.node_signer, &self.default_configuration, &self.logger));
macro_rules! max_time {
($timestamp: expr) => {
msg: announcement_sigs,
});
if let Some(height) = height_opt {
- if let Some(announcement) = channel.get_signed_channel_announcement(&self.node_signer, self.genesis_hash, height, &self.default_configuration) {
+ if let Some(announcement) = channel.get_signed_channel_announcement(&self.node_signer, self.chain_hash, height, &self.default_configuration) {
pending_msg_events.push(events::MessageSendEvent::BroadcastChannelAnnouncement {
msg: announcement,
// Note that announcement_signatures fails if the channel cannot be announced,
self.issue_channel_close_events(&channel.context, reason);
pending_msg_events.push(events::MessageSendEvent::HandleError {
node_id: channel.context.get_counterparty_node_id(),
- action: msgs::ErrorAction::SendErrorMessage { msg: msgs::ErrorMessage {
- channel_id: channel.context.channel_id(),
- data: reason_message,
- } },
+ action: msgs::ErrorAction::DisconnectPeer {
+ msg: Some(msgs::ErrorMessage {
+ channel_id: channel.context.channel_id(),
+ data: reason_message,
+ })
+ },
});
return false;
}
self.best_block.read().unwrap().clone()
}
- /// Fetches the set of [`NodeFeatures`] flags which are provided by or required by
+ /// Fetches the set of [`NodeFeatures`] flags that are provided by or required by
/// [`ChannelManager`].
pub fn node_features(&self) -> NodeFeatures {
provided_node_features(&self.default_configuration)
}
- /// Fetches the set of [`Bolt11InvoiceFeatures`] flags which are provided by or required by
+ /// Fetches the set of [`Bolt11InvoiceFeatures`] flags that are provided by or required by
/// [`ChannelManager`].
///
/// Note that the invoice feature flags can vary depending on if the invoice is a "phantom invoice"
/// or not. Thus, this method is not public.
#[cfg(any(feature = "_test_utils", test))]
- pub fn invoice_features(&self) -> Bolt11InvoiceFeatures {
- provided_invoice_features(&self.default_configuration)
+ pub fn bolt11_invoice_features(&self) -> Bolt11InvoiceFeatures {
+ provided_bolt11_invoice_features(&self.default_configuration)
}
- /// Fetches the set of [`ChannelFeatures`] flags which are provided by or required by
+ /// Fetches the set of [`Bolt12InvoiceFeatures`] flags that are provided by or required by
+ /// [`ChannelManager`].
+ fn bolt12_invoice_features(&self) -> Bolt12InvoiceFeatures {
+ provided_bolt12_invoice_features(&self.default_configuration)
+ }
+
+ /// Fetches the set of [`ChannelFeatures`] flags that are provided by or required by
/// [`ChannelManager`].
pub fn channel_features(&self) -> ChannelFeatures {
provided_channel_features(&self.default_configuration)
}
- /// Fetches the set of [`ChannelTypeFeatures`] flags which are provided by or required by
+ /// Fetches the set of [`ChannelTypeFeatures`] flags that are provided by or required by
/// [`ChannelManager`].
pub fn channel_type_features(&self) -> ChannelTypeFeatures {
provided_channel_type_features(&self.default_configuration)
}
- /// Fetches the set of [`InitFeatures`] flags which are provided by or required by
+ /// Fetches the set of [`InitFeatures`] flags that are provided by or required by
/// [`ChannelManager`].
pub fn init_features(&self) -> InitFeatures {
provided_init_features(&self.default_configuration)
let mut peer_state_lock = peer_state_mutex_opt.unwrap().lock().unwrap();
let peer_state = &mut *peer_state_lock;
if let Some(ChannelPhase::UnfundedOutboundV1(chan)) = peer_state.channel_by_id.get_mut(&msg.channel_id) {
- if let Ok(msg) = chan.maybe_handle_error_without_close(self.genesis_hash, &self.fee_estimator) {
+ if let Ok(msg) = chan.maybe_handle_error_without_close(self.chain_hash, &self.fee_estimator) {
peer_state.pending_msg_events.push(events::MessageSendEvent::SendOpenChannel {
node_id: *counterparty_node_id,
msg,
provided_init_features(&self.default_configuration)
}
- fn get_genesis_hashes(&self) -> Option<Vec<ChainHash>> {
- Some(vec![ChainHash::from(&self.genesis_hash[..])])
+ fn get_chain_hashes(&self) -> Option<Vec<ChainHash>> {
+ Some(vec![self.chain_hash])
}
fn handle_tx_add_input(&self, counterparty_node_id: &PublicKey, msg: &msgs::TxAddInput) {
}
}
-/// Fetches the set of [`NodeFeatures`] flags which are provided by or required by
+impl<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref>
+OffersMessageHandler for ChannelManager<M, T, ES, NS, SP, F, R, L>
+where
+ M::Target: chain::Watch<<SP::Target as SignerProvider>::Signer>,
+ T::Target: BroadcasterInterface,
+ ES::Target: EntropySource,
+ NS::Target: NodeSigner,
+ SP::Target: SignerProvider,
+ F::Target: FeeEstimator,
+ R::Target: Router,
+ L::Target: Logger,
+{
+ fn handle_message(&self, message: OffersMessage) -> Option<OffersMessage> {
+ let secp_ctx = &self.secp_ctx;
+ let expanded_key = &self.inbound_payment_key;
+
+ match message {
+ OffersMessage::InvoiceRequest(invoice_request) => {
+ let amount_msats = match InvoiceBuilder::<DerivedSigningPubkey>::amount_msats(
+ &invoice_request
+ ) {
+ Ok(amount_msats) => Some(amount_msats),
+ Err(error) => return Some(OffersMessage::InvoiceError(error.into())),
+ };
+ let invoice_request = match invoice_request.verify(expanded_key, secp_ctx) {
+ Ok(invoice_request) => invoice_request,
+ Err(()) => {
+ let error = Bolt12SemanticError::InvalidMetadata;
+ return Some(OffersMessage::InvoiceError(error.into()));
+ },
+ };
+ let relative_expiry = DEFAULT_RELATIVE_EXPIRY.as_secs() as u32;
+
+ match self.create_inbound_payment(amount_msats, relative_expiry, None) {
+ Ok((payment_hash, payment_secret)) if invoice_request.keys.is_some() => {
+ let payment_paths = vec![
+ self.create_one_hop_blinded_payment_path(payment_secret),
+ ];
+ #[cfg(not(feature = "no-std"))]
+ let builder = invoice_request.respond_using_derived_keys(
+ payment_paths, payment_hash
+ );
+ #[cfg(feature = "no-std")]
+ let created_at = Duration::from_secs(
+ self.highest_seen_timestamp.load(Ordering::Acquire) as u64
+ );
+ #[cfg(feature = "no-std")]
+ let builder = invoice_request.respond_using_derived_keys_no_std(
+ payment_paths, payment_hash, created_at
+ );
+ match builder.and_then(|b| b.allow_mpp().build_and_sign(secp_ctx)) {
+ Ok(invoice) => Some(OffersMessage::Invoice(invoice)),
+ Err(error) => Some(OffersMessage::InvoiceError(error.into())),
+ }
+ },
+ Ok((payment_hash, payment_secret)) => {
+ let payment_paths = vec![
+ self.create_one_hop_blinded_payment_path(payment_secret),
+ ];
+ #[cfg(not(feature = "no-std"))]
+ let builder = invoice_request.respond_with(payment_paths, payment_hash);
+ #[cfg(feature = "no-std")]
+ let created_at = Duration::from_secs(
+ self.highest_seen_timestamp.load(Ordering::Acquire) as u64
+ );
+ #[cfg(feature = "no-std")]
+ let builder = invoice_request.respond_with_no_std(
+ payment_paths, payment_hash, created_at
+ );
+ let response = builder.and_then(|builder| builder.allow_mpp().build())
+ .map_err(|e| OffersMessage::InvoiceError(e.into()))
+ .and_then(|invoice|
+ match invoice.sign(|invoice| self.node_signer.sign_bolt12_invoice(invoice)) {
+ Ok(invoice) => Ok(OffersMessage::Invoice(invoice)),
+ Err(SignError::Signing(())) => Err(OffersMessage::InvoiceError(
+ InvoiceError::from_string("Failed signing invoice".to_string())
+ )),
+ Err(SignError::Verification(_)) => Err(OffersMessage::InvoiceError(
+ InvoiceError::from_string("Failed invoice signature verification".to_string())
+ )),
+ });
+ match response {
+ Ok(invoice) => Some(invoice),
+ Err(error) => Some(error),
+ }
+ },
+ Err(()) => {
+ Some(OffersMessage::InvoiceError(Bolt12SemanticError::InvalidAmount.into()))
+ },
+ }
+ },
+ OffersMessage::Invoice(invoice) => {
+ match invoice.verify(expanded_key, secp_ctx) {
+ Err(()) => {
+ Some(OffersMessage::InvoiceError(InvoiceError::from_string("Unrecognized invoice".to_owned())))
+ },
+ Ok(_) if invoice.invoice_features().requires_unknown_bits_from(&self.bolt12_invoice_features()) => {
+ Some(OffersMessage::InvoiceError(Bolt12SemanticError::UnknownRequiredFeatures.into()))
+ },
+ Ok(payment_id) => {
+ if let Err(e) = self.send_payment_for_bolt12_invoice(&invoice, payment_id) {
+ log_trace!(self.logger, "Failed paying invoice: {:?}", e);
+ Some(OffersMessage::InvoiceError(InvoiceError::from_string(format!("{:?}", e))))
+ } else {
+ None
+ }
+ },
+ }
+ },
+ OffersMessage::InvoiceError(invoice_error) => {
+ log_trace!(self.logger, "Received invoice_error: {}", invoice_error);
+ None
+ },
+ }
+ }
+
+ fn release_pending_messages(&self) -> Vec<PendingOnionMessage<OffersMessage>> {
+ core::mem::take(&mut self.pending_offers_messages.lock().unwrap())
+ }
+}
+
+/// Fetches the set of [`NodeFeatures`] flags that are provided by or required by
/// [`ChannelManager`].
pub(crate) fn provided_node_features(config: &UserConfig) -> NodeFeatures {
let mut node_features = provided_init_features(config).to_context();
node_features
}
-/// Fetches the set of [`Bolt11InvoiceFeatures`] flags which are provided by or required by
+/// Fetches the set of [`Bolt11InvoiceFeatures`] flags that are provided by or required by
/// [`ChannelManager`].
///
/// Note that the invoice feature flags can vary depending on if the invoice is a "phantom invoice"
/// or not. Thus, this method is not public.
#[cfg(any(feature = "_test_utils", test))]
-pub(crate) fn provided_invoice_features(config: &UserConfig) -> Bolt11InvoiceFeatures {
+pub(crate) fn provided_bolt11_invoice_features(config: &UserConfig) -> Bolt11InvoiceFeatures {
+ provided_init_features(config).to_context()
+}
+
+/// Fetches the set of [`Bolt12InvoiceFeatures`] flags that are provided by or required by
+/// [`ChannelManager`].
+pub(crate) fn provided_bolt12_invoice_features(config: &UserConfig) -> Bolt12InvoiceFeatures {
provided_init_features(config).to_context()
}
-/// Fetches the set of [`ChannelFeatures`] flags which are provided by or required by
+/// Fetches the set of [`ChannelFeatures`] flags that are provided by or required by
/// [`ChannelManager`].
pub(crate) fn provided_channel_features(config: &UserConfig) -> ChannelFeatures {
provided_init_features(config).to_context()
}
-/// Fetches the set of [`ChannelTypeFeatures`] flags which are provided by or required by
+/// Fetches the set of [`ChannelTypeFeatures`] flags that are provided by or required by
/// [`ChannelManager`].
pub(crate) fn provided_channel_type_features(config: &UserConfig) -> ChannelTypeFeatures {
ChannelTypeFeatures::from_init(&provided_init_features(config))
}
-/// Fetches the set of [`InitFeatures`] flags which are provided by or required by
+/// Fetches the set of [`InitFeatures`] flags that are provided by or required by
/// [`ChannelManager`].
pub fn provided_init_features(config: &UserConfig) -> InitFeatures {
// Note that if new features are added here which other peers may (eventually) require, we
write_ver_prefix!(writer, SERIALIZATION_VERSION, MIN_SERIALIZATION_VERSION);
- self.genesis_hash.write(writer)?;
+ self.chain_hash.write(writer)?;
{
let best_block = self.best_block.read().unwrap();
best_block.height().write(writer)?;
fn read<Reader: io::Read>(reader: &mut Reader, mut args: ChannelManagerReadArgs<'a, M, T, ES, NS, SP, F, R, L>) -> Result<Self, DecodeError> {
let _ver = read_ver_prefix!(reader, SERIALIZATION_VERSION);
- let genesis_hash: BlockHash = Readable::read(reader)?;
+ let chain_hash: ChainHash = Readable::read(reader)?;
let best_block_height: u32 = Readable::read(reader)?;
let best_block_hash: BlockHash = Readable::read(reader)?;
log_error!(args.logger, " The ChannelMonitor for channel {} is at counterparty commitment transaction number {} but the ChannelManager is at counterparty commitment transaction number {}.",
&channel.context.channel_id(), monitor.get_cur_counterparty_commitment_number(), channel.get_cur_counterparty_commitment_transaction_number());
}
- let (monitor_update, mut new_failed_htlcs, batch_funding_txid) = channel.context.force_shutdown(true);
- if batch_funding_txid.is_some() {
+ let mut shutdown_result = channel.context.force_shutdown(true);
+ if shutdown_result.unbroadcasted_batch_funding_txid.is_some() {
return Err(DecodeError::InvalidValue);
}
- if let Some((counterparty_node_id, funding_txo, update)) = monitor_update {
+ if let Some((counterparty_node_id, funding_txo, update)) = shutdown_result.monitor_update {
close_background_events.push(BackgroundEvent::MonitorUpdateRegeneratedOnStartup {
counterparty_node_id, funding_txo, update
});
}
- failed_htlcs.append(&mut new_failed_htlcs);
+ failed_htlcs.append(&mut shutdown_result.dropped_outbound_htlcs);
channel_closures.push_back((events::Event::ChannelClosed {
channel_id: channel.context.channel_id(),
user_channel_id: channel.context.get_user_id(),
let mut outbound_scid_alias;
loop {
outbound_scid_alias = fake_scid::Namespace::OutboundAlias
- .get_fake_scid(best_block_height, &genesis_hash, fake_scid_rand_bytes.as_ref().unwrap(), &args.entropy_source);
+ .get_fake_scid(best_block_height, &chain_hash, fake_scid_rand_bytes.as_ref().unwrap(), &args.entropy_source);
if outbound_scid_aliases.insert(outbound_scid_alias) { break; }
}
chan.context.set_outbound_scid_alias(outbound_scid_alias);
Some((blocked_node_id, blocked_channel_outpoint, blocking_action)), ..
} = action {
if let Some(blocked_peer_state) = per_peer_state.get(&blocked_node_id) {
+ log_trace!(args.logger,
+ "Holding the next revoke_and_ack from {} until the preimage is durably persisted in the inbound edge's ChannelMonitor",
+ blocked_channel_outpoint.to_channel_id());
blocked_peer_state.lock().unwrap().actions_blocking_raa_monitor_updates
.entry(blocked_channel_outpoint.to_channel_id())
.or_insert_with(Vec::new).push(blocking_action.clone());
// anymore.
}
}
+ if let MonitorUpdateCompletionAction::FreeOtherChannelImmediately { .. } = action {
+ debug_assert!(false, "Non-event-generating channel freeing should not appear in our queue");
+ }
}
}
peer_state.lock().unwrap().monitor_update_blocked_actions = monitor_update_blocked_actions;
}
let channel_manager = ChannelManager {
- genesis_hash,
+ chain_hash,
fee_estimator: bounded_fee_estimator,
chain_monitor: args.chain_monitor,
tx_broadcaster: args.tx_broadcaster,
funding_batch_states: Mutex::new(BTreeMap::new()),
+ pending_offers_messages: Mutex::new(Vec::new()),
+
entropy_source: args.entropy_source,
node_signer: args.node_signer,
signer_provider: args.signer_provider,
// don't remember in the `ChannelMonitor` where we got a preimage from, but if the
// channel is closed we just assume that it probably came from an on-chain claim.
channel_manager.claim_funds_internal(source, preimage, Some(downstream_value),
- downstream_closed, downstream_node_id, downstream_funding);
+ downstream_closed, true, downstream_node_id, downstream_funding);
}
//TODO: Broadcast channel update for closed channels, but only after we've made a
let payment_preimage = PaymentPreimage([42; 32]);
assert_eq!(format!("{}", &payment_preimage), "2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a");
}
+
+ #[test]
+ fn test_trigger_lnd_force_close() {
+ let chanmon_cfg = create_chanmon_cfgs(2);
+ let node_cfg = create_node_cfgs(2, &chanmon_cfg);
+ let user_config = test_default_channel_config();
+ let node_chanmgr = create_node_chanmgrs(2, &node_cfg, &[Some(user_config), Some(user_config)]);
+ let nodes = create_network(2, &node_cfg, &node_chanmgr);
+
+ // Open a channel, immediately disconnect each other, and broadcast Alice's latest state.
+ let (_, _, chan_id, funding_tx) = create_announced_chan_between_nodes(&nodes, 0, 1);
+ 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.force_close_broadcasting_latest_txn(&chan_id, &nodes[1].node.get_our_node_id()).unwrap();
+ check_closed_broadcast(&nodes[0], 1, true);
+ check_added_monitors(&nodes[0], 1);
+ check_closed_event!(nodes[0], 1, ClosureReason::HolderForceClosed, [nodes[1].node.get_our_node_id()], 100000);
+ {
+ let txn = nodes[0].tx_broadcaster.txn_broadcast();
+ assert_eq!(txn.len(), 1);
+ check_spends!(txn[0], funding_tx);
+ }
+
+ // Since they're disconnected, Bob won't receive Alice's `Error` message. Reconnect them
+ // such that Bob sends a `ChannelReestablish` to Alice since the channel is still open from
+ // their side.
+ 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();
+ assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
+ let channel_reestablish = get_event_msg!(
+ nodes[1], MessageSendEvent::SendChannelReestablish, nodes[0].node.get_our_node_id()
+ );
+ nodes[0].node.handle_channel_reestablish(&nodes[1].node.get_our_node_id(), &channel_reestablish);
+
+ // Alice should respond with an error since the channel isn't known, but a bogus
+ // `ChannelReestablish` should be sent first, such that we actually trigger Bob to force
+ // close even if it was an lnd node.
+ let msg_events = nodes[0].node.get_and_clear_pending_msg_events();
+ assert_eq!(msg_events.len(), 2);
+ if let MessageSendEvent::SendChannelReestablish { node_id, msg } = &msg_events[0] {
+ assert_eq!(*node_id, nodes[1].node.get_our_node_id());
+ assert_eq!(msg.next_local_commitment_number, 0);
+ assert_eq!(msg.next_remote_commitment_number, 0);
+ nodes[1].node.handle_channel_reestablish(&nodes[0].node.get_our_node_id(), &msg);
+ } else { panic!() };
+ check_closed_broadcast(&nodes[1], 1, true);
+ check_added_monitors(&nodes[1], 1);
+ let expected_close_reason = ClosureReason::ProcessingError {
+ err: "Peer sent an invalid channel_reestablish to force close in a non-standard way".to_string()
+ };
+ check_closed_event!(nodes[1], 1, expected_close_reason, [nodes[0].node.get_our_node_id()], 100000);
+ {
+ let txn = nodes[1].tx_broadcaster.txn_broadcast();
+ assert_eq!(txn.len(), 1);
+ check_spends!(txn[0], funding_tx);
+ }
+ }
}
#[cfg(ldk_bench)]
macro_rules! send_payment {
($node_a: expr, $node_b: expr) => {
let payment_params = PaymentParameters::from_node_id($node_b.get_our_node_id(), TEST_FINAL_CLTV)
- .with_bolt11_features($node_b.invoice_features()).unwrap();
+ .with_bolt11_features($node_b.bolt11_invoice_features()).unwrap();
let mut payment_preimage = PaymentPreimage([0; 32]);
payment_preimage.0[0..8].copy_from_slice(&payment_count.to_le_bytes());
payment_count += 1;
assert_eq!(node_id, recipient);
(*msg).clone()
},
+ MessageSendEvent::HandleError {
+ action: msgs::ErrorAction::DisconnectPeer { ref msg }, ref node_id
+ } => {
+ assert_eq!(node_id, recipient);
+ msg.as_ref().unwrap().clone()
+ },
_ => panic!("Unexpected event"),
}
}
assert_eq!(msg.contents.flags & 2, 2);
None
},
- MessageSendEvent::HandleError { action: msgs::ErrorAction::SendErrorMessage { ref msg }, node_id: _ } => {
+ MessageSendEvent::HandleError { action: msgs::ErrorAction::SendErrorMessage { msg }, node_id: _ } => {
assert!(with_error_msg);
// TODO: Check node_id
- Some(msg.clone())
+ Some(msg)
+ },
+ MessageSendEvent::HandleError { action: msgs::ErrorAction::DisconnectPeer { msg }, node_id: _ } => {
+ assert!(with_error_msg);
+ // TODO: Check node_id
+ Some(msg.unwrap())
},
_ => panic!("Unexpected event"),
}
}
}
+#[derive(Default)]
+pub struct ExpectedCloseEvent {
+ pub channel_capacity_sats: Option<u64>,
+ pub channel_id: Option<ChannelId>,
+ pub counterparty_node_id: Option<PublicKey>,
+ pub discard_funding: bool,
+ pub reason: Option<ClosureReason>,
+}
+
+/// Check that multiple channel closing events have been issued.
+pub fn check_closed_events(node: &Node, expected_close_events: &[ExpectedCloseEvent]) {
+ let closed_events_count = expected_close_events.len();
+ let discard_events_count = expected_close_events.iter().filter(|e| e.discard_funding).count();
+ let events = node.node.get_and_clear_pending_events();
+ assert_eq!(events.len(), closed_events_count + discard_events_count, "{:?}", events);
+ for expected_event in expected_close_events {
+ assert!(events.iter().any(|e| matches!(
+ e,
+ Event::ChannelClosed {
+ channel_id,
+ reason,
+ counterparty_node_id,
+ channel_capacity_sats,
+ ..
+ } if (
+ expected_event.channel_id.map(|expected| *channel_id == expected).unwrap_or(true) &&
+ expected_event.reason.as_ref().map(|expected| reason == expected).unwrap_or(true) &&
+ expected_event.counterparty_node_id.map(|expected| *counterparty_node_id == Some(expected)).unwrap_or(true) &&
+ expected_event.channel_capacity_sats.map(|expected| *channel_capacity_sats == Some(expected)).unwrap_or(true)
+ )
+ )));
+ }
+ assert_eq!(events.iter().filter(|e| matches!(
+ e,
+ Event::DiscardFunding { .. },
+ )).count(), discard_events_count);
+}
+
/// Check that a channel's closing channel events has been issued
pub fn check_closed_event(node: &Node, events_count: usize, expected_reason: ClosureReason, is_check_discard_funding: bool,
expected_counterparty_node_ids: &[PublicKey], expected_channel_capacity: u64) {
- let events = node.node.get_and_clear_pending_events();
- assert_eq!(events.len(), events_count, "{:?}", events);
- let mut issues_discard_funding = false;
- for event in events {
- match event {
- Event::ChannelClosed { ref reason, counterparty_node_id,
- channel_capacity_sats, .. } => {
- assert_eq!(*reason, expected_reason);
- assert!(expected_counterparty_node_ids.iter().any(|id| id == &counterparty_node_id.unwrap()));
- assert_eq!(channel_capacity_sats.unwrap(), expected_channel_capacity);
- },
- Event::DiscardFunding { .. } => {
- issues_discard_funding = true;
- }
- _ => panic!("Unexpected event"),
- }
- }
- assert_eq!(is_check_discard_funding, issues_discard_funding);
+ let expected_events_count = if is_check_discard_funding {
+ 2 * expected_counterparty_node_ids.len()
+ } else {
+ expected_counterparty_node_ids.len()
+ };
+ assert_eq!(events_count, expected_events_count);
+ let expected_close_events = expected_counterparty_node_ids.iter().map(|node_id| ExpectedCloseEvent {
+ channel_capacity_sats: Some(expected_channel_capacity),
+ channel_id: None,
+ counterparty_node_id: Some(*node_id),
+ discard_funding: is_check_discard_funding,
+ reason: Some(expected_reason.clone()),
+ }).collect::<Vec<_>>();
+ check_closed_events(node, expected_close_events.as_slice());
}
/// Check that a channel's closing channel events has been issued
macro_rules! get_route_and_payment_hash {
($send_node: expr, $recv_node: expr, $recv_value: expr) => {{
let payment_params = $crate::routing::router::PaymentParameters::from_node_id($recv_node.node.get_our_node_id(), TEST_FINAL_CLTV)
- .with_bolt11_features($recv_node.node.invoice_features()).unwrap();
+ .with_bolt11_features($recv_node.node.bolt11_invoice_features()).unwrap();
$crate::get_route_and_payment_hash!($send_node, $recv_node, payment_params, $recv_value)
}};
($send_node: expr, $recv_node: expr, $payment_params: expr, $recv_value: expr) => {{
pub fn route_payment<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_route: &[&Node<'a, 'b, 'c>], recv_value: u64) -> (PaymentPreimage, PaymentHash, PaymentSecret, PaymentId) {
let payment_params = PaymentParameters::from_node_id(expected_route.last().unwrap().node.get_our_node_id(), TEST_FINAL_CLTV)
- .with_bolt11_features(expected_route.last().unwrap().node.invoice_features()).unwrap();
+ .with_bolt11_features(expected_route.last().unwrap().node.bolt11_invoice_features()).unwrap();
let route_params = RouteParameters::from_payment_params_and_value(payment_params, recv_value);
let route = get_route(origin_node, &route_params).unwrap();
assert_eq!(route.paths.len(), 1);
pub fn route_over_limit<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_route: &[&Node<'a, 'b, 'c>], recv_value: u64) {
let payment_params = PaymentParameters::from_node_id(expected_route.last().unwrap().node.get_our_node_id(), TEST_FINAL_CLTV)
- .with_bolt11_features(expected_route.last().unwrap().node.invoice_features()).unwrap();
+ .with_bolt11_features(expected_route.last().unwrap().node.bolt11_invoice_features()).unwrap();
let route_params = RouteParameters::from_payment_params_and_value(payment_params, recv_value);
let network_graph = origin_node.network_graph.read_only();
let scorer = test_utils::TestScorer::new();
nodes[b].node.handle_error(&nodes[a].node.get_our_node_id(), msg);
}
},
+ MessageSendEvent::HandleError { node_id, action: msgs::ErrorAction::DisconnectPeer { ref msg } } => {
+ assert_eq!(node_id, nodes[b].node.get_our_node_id());
+ assert_eq!(msg.as_ref().unwrap().data, expected_error);
+ if needs_err_handle {
+ nodes[b].node.handle_error(&nodes[a].node.get_our_node_id(), msg.as_ref().unwrap());
+ }
+ },
_ => panic!("Unexpected event"),
}
assert_eq!(node_id, nodes[a].node.get_our_node_id());
assert_eq!(msg.data, expected_error);
},
+ MessageSendEvent::HandleError { node_id, action: msgs::ErrorAction::DisconnectPeer { ref msg } } => {
+ assert_eq!(node_id, nodes[a].node.get_our_node_id());
+ assert_eq!(msg.as_ref().unwrap().data, expected_error);
+ },
_ => panic!("Unexpected event"),
}
}
use bitcoin::hash_types::BlockHash;
use bitcoin::blockdata::script::{Builder, Script};
use bitcoin::blockdata::opcodes;
-use bitcoin::blockdata::constants::genesis_block;
+use bitcoin::blockdata::constants::ChainHash;
use bitcoin::network::constants::Network;
use bitcoin::{PackedLockTime, Sequence, Transaction, TxIn, TxOut, Witness};
use bitcoin::OutPoint as BitcoinOutPoint;
for e in events {
match e {
MessageSendEvent::BroadcastChannelUpdate { .. } => {},
- MessageSendEvent::HandleError { node_id, action: msgs::ErrorAction::SendErrorMessage { ref msg } } => {
+ MessageSendEvent::HandleError { node_id, action: msgs::ErrorAction::DisconnectPeer { ref msg } } => {
assert_eq!(node_id, nodes[1].node.get_our_node_id());
- assert_eq!(msg.data, "Channel closed because commitment or closing transaction was confirmed on chain.");
+ assert_eq!(msg.as_ref().unwrap().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());
// attempt to send amt_msat > their_max_htlc_value_in_flight_msat
{
let payment_params = PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), TEST_FINAL_CLTV)
- .with_bolt11_features(nodes[2].node.invoice_features()).unwrap().with_max_channel_saturation_power_of_half(0);
+ .with_bolt11_features(nodes[2].node.bolt11_invoice_features()).unwrap().with_max_channel_saturation_power_of_half(0);
let (mut route, our_payment_hash, _, our_payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[2], payment_params, recv_value_0);
route.paths[0].hops.last_mut().unwrap().fee_msat += 1;
assert!(route.paths[0].hops.iter().rev().skip(1).all(|h| h.fee_msat == feemsat));
}
let payment_params = PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), TEST_FINAL_CLTV)
- .with_bolt11_features(nodes[2].node.invoice_features()).unwrap().with_max_channel_saturation_power_of_half(0);
+ .with_bolt11_features(nodes[2].node.bolt11_invoice_features()).unwrap().with_max_channel_saturation_power_of_half(0);
let route = get_route!(nodes[0], payment_params, recv_value_0).unwrap();
let (payment_preimage, ..) = send_along_route(&nodes[0], route, &[&nodes[1], &nodes[2]], recv_value_0);
claim_payment(&nodes[0], &[&nodes[1], &nodes[2]], payment_preimage);
_ => panic!("Unexpected event"),
};
match events[1] {
- MessageSendEvent::HandleError { action: ErrorAction::SendErrorMessage { .. }, node_id } => {
+ MessageSendEvent::HandleError { action: ErrorAction::DisconnectPeer { .. }, node_id } => {
assert_eq!(node_id, nodes[4].node.get_our_node_id());
},
_ => panic!("Unexpected event"),
_ => panic!("Unexpected event"),
};
match events[1] {
- MessageSendEvent::HandleError { action: ErrorAction::SendErrorMessage { .. }, node_id } => {
+ MessageSendEvent::HandleError { action: ErrorAction::DisconnectPeer { .. }, node_id } => {
assert_eq!(node_id, nodes[3].node.get_our_node_id());
},
_ => panic!("Unexpected event"),
let nodes_0_event = remove_first_msg_event_to_node(&nodes[0].node.get_our_node_id(), &mut events);
match nodes_2_event {
- MessageSendEvent::HandleError { action: ErrorAction::SendErrorMessage { .. }, node_id: _ } => {},
+ MessageSendEvent::HandleError { action: ErrorAction::DisconnectPeer { .. }, node_id: _ } => {},
_ => panic!("Unexpected event"),
}
let nodes_2_event = remove_first_msg_event_to_node(&nodes[2].node.get_our_node_id(), &mut events);
match nodes_2_event {
- MessageSendEvent::HandleError { action: ErrorAction::SendErrorMessage { msg: msgs::ErrorMessage { channel_id, ref data } }, node_id: _ } => {
+ MessageSendEvent::HandleError { action: ErrorAction::DisconnectPeer { msg: Some(msgs::ErrorMessage { channel_id, ref data }) }, node_id: _ } => {
assert_eq!(channel_id, chan_2.2);
assert_eq!(data.as_str(), "Channel closed because commitment or closing transaction was confirmed on chain.");
},
let nodes_0_event = remove_first_msg_event_to_node(&nodes[0].node.get_our_node_id(), &mut msg_events);
match nodes_2_event {
- MessageSendEvent::HandleError { action: ErrorAction::SendErrorMessage { .. }, node_id: _ } => {},
+ MessageSendEvent::HandleError { action: ErrorAction::DisconnectPeer { .. }, node_id: _ } => {},
_ => panic!("Unexpected event"),
}
// script push size limit so that the below script length checks match
// ACCEPTED_HTLC_SCRIPT_WEIGHT.
let payment_params = PaymentParameters::from_node_id(nodes[3].node.get_our_node_id(), TEST_FINAL_CLTV - 40)
- .with_bolt11_features(nodes[3].node.invoice_features()).unwrap();
+ .with_bolt11_features(nodes[3].node.bolt11_invoice_features()).unwrap();
let (route, _, _, _) = get_route_and_payment_hash!(nodes[0], nodes[3], payment_params, 800_000);
send_along_route_with_secret(&nodes[0], route, &[&[&nodes[1], &nodes[2], &nodes[3]]], 800_000, duplicate_payment_hash, payment_secret);
assert!(node0_to_1_send_open_channel.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=genesis_block(Network::Testnet).header.block_hash();
- assert_eq!(node0_to_1_send_open_channel.chain_hash,chain_hash);
+ let chain_hash = ChainHash::using_genesis_block(Network::Testnet);
+ assert_eq!(node0_to_1_send_open_channel.chain_hash, chain_hash);
// BOLT #2 spec: Sending node must set funding_pubkey, revocation_basepoint, htlc_basepoint, payment_basepoint, and delayed_payment_basepoint to valid DER-encoded, compressed, secp256k1 pubkeys.
assert!(PublicKey::from_slice(&node0_to_1_send_open_channel.funding_pubkey.serialize()).is_ok());
let _chan = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1000000, 0);
let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), 0)
- .with_bolt11_features(nodes[1].node.invoice_features()).unwrap();
+ .with_bolt11_features(nodes[1].node.bolt11_invoice_features()).unwrap();
let (mut route, our_payment_hash, _, our_payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[1], payment_params, 100000000);
route.paths[0].hops.last_mut().unwrap().cltv_expiry_delta = 500000001;
unwrap_send_err!(nodes[0].node.send_payment_with_route(&route, our_payment_hash,
let scorer = test_utils::TestScorer::new();
let random_seed_bytes = chanmon_cfgs[1].keys_manager.get_secure_random_bytes();
- let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(),
- TEST_FINAL_CLTV).with_bolt11_features(nodes[1].node.invoice_features()).unwrap();
+ let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), TEST_FINAL_CLTV)
+ .with_bolt11_features(nodes[1].node.bolt11_invoice_features()).unwrap();
let route_params = RouteParameters::from_payment_params_and_value(payment_params, 10_000);
let route = get_route(&nodes[0].node.get_our_node_id(), &route_params, &nodes[0].network_graph.read_only(),
None, nodes[0].logger, &scorer, &Default::default(), &random_seed_bytes).unwrap();
let payment_preimage = route_payment(&nodes[0], &vec!(&nodes[1])[..], 3000000).0;
let payment_params = PaymentParameters::from_node_id(nodes[0].node.get_our_node_id(), 30)
- .with_bolt11_features(nodes[0].node.invoice_features()).unwrap();
+ .with_bolt11_features(nodes[0].node.bolt11_invoice_features()).unwrap();
let (route,_, _, _) = get_route_and_payment_hash!(nodes[1], nodes[0], payment_params, 3000000);
send_along_route(&nodes[1], route, &vec!(&nodes[0])[..], 3000000);
let chan = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1000000, 59000000);
// Lock HTLC in both directions (using a slightly lower CLTV delay to provide timely RBF bumps)
- let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), 50).with_bolt11_features(nodes[1].node.invoice_features()).unwrap();
+ let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), 50).with_bolt11_features(nodes[1].node.bolt11_invoice_features()).unwrap();
let scorer = test_utils::TestScorer::new();
let random_seed_bytes = chanmon_cfgs[1].keys_manager.get_secure_random_bytes();
let route_params = RouteParameters::from_payment_params_and_value(payment_params, 3_000_000);
let route = get_route(&nodes[0].node.get_our_node_id(), &route_params, &nodes[0].network_graph.read_only(), None,
nodes[0].logger, &scorer, &Default::default(), &random_seed_bytes).unwrap();
let payment_preimage = send_along_route(&nodes[0], route, &[&nodes[1]], 3_000_000).0;
- let payment_params = PaymentParameters::from_node_id(nodes[0].node.get_our_node_id(), 50).with_bolt11_features(nodes[0].node.invoice_features()).unwrap();
+ let payment_params = PaymentParameters::from_node_id(nodes[0].node.get_our_node_id(), 50)
+ .with_bolt11_features(nodes[0].node.bolt11_invoice_features()).unwrap();
let route_params = RouteParameters::from_payment_params_and_value(payment_params, 3_000_000);
let route = get_route(&nodes[1].node.get_our_node_id(), &route_params, &nodes[1].network_graph.read_only(), None,
nodes[0].logger, &scorer, &Default::default(), &random_seed_bytes).unwrap();
let close_ev = nodes[1].node.get_and_clear_pending_msg_events();
assert_eq!(close_ev.len(), 1);
match close_ev[0] {
- MessageSendEvent::HandleError { action: ErrorAction::SendErrorMessage { ref msg }, ref node_id } => {
+ MessageSendEvent::HandleError { action: ErrorAction::DisconnectPeer { ref msg }, ref node_id } => {
assert_eq!(*node_id, nodes[0].node.get_our_node_id());
- assert_eq!(msg.data, "Channel closed because funding transaction failed to confirm within 2016 blocks");
+ assert_eq!(msg.as_ref().unwrap().data, "Channel closed because funding transaction failed to confirm within 2016 blocks");
},
_ => panic!("Unexpected event"),
}
nodes[0].node.handle_error(&nodes[1].node.get_our_node_id(), &msgs::ErrorMessage { channel_id, data: "Hi".to_owned() });
assert!(nodes[0].chain_monitor.added_monitors.lock().unwrap().is_empty());
check_closed_event!(nodes[0], 2, ClosureReason::CounterpartyForceClosed { peer_msg: UntrustedString("Hi".to_string()) }, true,
- [nodes[1].node.get_our_node_id(); 2], 100000);
+ [nodes[1].node.get_our_node_id()], 100000);
}
#[test]
assert_eq!(events_2.len(), 1);
if let MessageSendEvent::HandleError { node_id, action } = &events_2[0] {
assert_eq!(*node_id, nodes[0].node.get_our_node_id());
- if let msgs::ErrorAction::SendErrorMessage { msg } = action {
- assert_eq!(msg.data, "Channel closed because of an exception: ".to_owned() + expected_err);
+ if let msgs::ErrorAction::DisconnectPeer { msg } = action {
+ assert_eq!(msg.as_ref().unwrap().data, "Channel closed because of an exception: ".to_owned() + expected_err);
} else { panic!(); }
} else { panic!(); }
assert_eq!(nodes[1].node.list_channels().len(), 0);
let _chan = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100000, 10001);
let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), TEST_FINAL_CLTV)
- .with_bolt11_features(nodes[1].node.invoice_features()).unwrap();
+ .with_bolt11_features(nodes[1].node.bolt11_invoice_features()).unwrap();
let route = get_route!(nodes[0], payment_params, 10_000).unwrap();
let (our_payment_preimage, our_payment_hash, our_payment_secret) = get_payment_preimage_hash!(&nodes[1]);
let chan_2_3 =create_announced_chan_between_nodes_with_value(&nodes, 2, 3, 100_000, 0);
let payment_params = PaymentParameters::from_node_id(nodes[3].node.get_our_node_id(), TEST_FINAL_CLTV)
- .with_bolt11_features(nodes[3].node.invoice_features()).unwrap();
+ .with_bolt11_features(nodes[3].node.bolt11_invoice_features()).unwrap();
let mut route = get_route!(nodes[0], payment_params, 15_000_000).unwrap();
assert_eq!(route.paths.len(), 2);
route.paths.sort_by(|path_a, _| {
MessageSendEvent::UpdateHTLCs { updates: msgs::CommitmentUpdate { ref update_fee, .. }, .. } => {
nodes[1].node.handle_update_fee(&nodes[0].node.get_our_node_id(), update_fee.as_ref().unwrap());
check_closed_event!(nodes[1], 1, ClosureReason::ProcessingError {
- err: "Peer's feerate much too low. Actual: 1000. Our expected lower limit: 5000 (- 250)".to_owned() },
+ err: "Peer's feerate much too low. Actual: 1000. Our expected lower limit: 5000".to_owned() },
[nodes[0].node.get_our_node_id()], 100000);
check_closed_broadcast!(nodes[1], true);
check_added_monitors!(nodes[1], 1);
// The channels in the batch will close immediately.
let channel_id_1 = OutPoint { txid: tx.txid(), index: 0 }.to_channel_id();
let channel_id_2 = OutPoint { txid: tx.txid(), index: 1 }.to_channel_id();
- let events = nodes[0].node.get_and_clear_pending_events();
- assert_eq!(events.len(), 4);
- assert!(events.iter().any(|e| matches!(
- e,
- Event::ChannelClosed {
- channel_id,
- ..
- } if channel_id == &channel_id_1
- )));
- assert!(events.iter().any(|e| matches!(
- e,
- Event::ChannelClosed {
- channel_id,
- ..
- } if channel_id == &channel_id_2
- )));
- assert_eq!(events.iter().filter(|e| matches!(
- e,
- Event::DiscardFunding { .. },
- )).count(), 2);
+ check_closed_events(&nodes[0], &[
+ ExpectedCloseEvent {
+ channel_id: Some(channel_id_1),
+ discard_funding: true,
+ ..Default::default()
+ },
+ ExpectedCloseEvent {
+ channel_id: Some(channel_id_2),
+ discard_funding: true,
+ ..Default::default()
+ },
+ ]);
// The monitor should become closed.
check_added_monitors(&nodes[0], 1);
}
// All channels in the batch should close immediately.
- let events = nodes[0].node.get_and_clear_pending_events();
- assert_eq!(events.len(), 4);
- assert!(events.iter().any(|e| matches!(
- e,
- Event::ChannelClosed {
- channel_id,
- ..
- } if channel_id == &channel_id_1
- )));
- assert!(events.iter().any(|e| matches!(
- e,
- Event::ChannelClosed {
- channel_id,
- ..
- } if channel_id == &channel_id_2
- )));
- assert_eq!(events.iter().filter(|e| matches!(
- e,
- Event::DiscardFunding { .. },
- )).count(), 2);
+ check_closed_events(&nodes[0], &[
+ ExpectedCloseEvent {
+ channel_id: Some(channel_id_1),
+ discard_funding: true,
+ ..Default::default()
+ },
+ ExpectedCloseEvent {
+ channel_id: Some(channel_id_2),
+ discard_funding: true,
+ ..Default::default()
+ },
+ ]);
// Ensure the channels don't exist anymore.
assert!(nodes[0].node.list_channels().is_empty());
let mut msg_events = closing_node.node.get_and_clear_pending_msg_events();
assert_eq!(msg_events.len(), 1);
match msg_events.pop().unwrap() {
- MessageSendEvent::HandleError { action: msgs::ErrorAction::SendErrorMessage { .. }, .. } => {},
+ MessageSendEvent::HandleError { action: msgs::ErrorAction::DisconnectPeer { .. }, .. } => {},
_ => panic!("Unexpected event"),
}
check_added_monitors(closing_node, 1);
// Create some initial channels
let (_, _, chan_id, funding_tx) =
- create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 11_000_000);
+ create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 12_000_000);
let funding_outpoint = OutPoint { txid: funding_tx.txid(), index: 0 };
assert_eq!(funding_outpoint.to_channel_id(), chan_id);
assert_eq!(revoked_local_txn[0].input.len(), 1);
assert_eq!(revoked_local_txn[0].input[0].previous_output.txid, funding_tx.txid());
if anchors {
- assert_eq!(revoked_local_txn[0].output[4].value, 10000); // to_self output
+ assert_eq!(revoked_local_txn[0].output[4].value, 11000); // to_self output
} else {
- assert_eq!(revoked_local_txn[0].output[2].value, 10000); // to_self output
+ assert_eq!(revoked_local_txn[0].output[2].value, 11000); // to_self output
}
// The to-be-revoked commitment tx should have two HTLCs, an output for each side, and an
let anchor_outputs_value = if anchors { channel::ANCHOR_OUTPUT_VALUE_SATOSHI * 2 } else { 0 };
let as_balances = sorted_vec(vec![Balance::ClaimableAwaitingConfirmations {
// to_remote output in B's revoked commitment
- amount_satoshis: 1_000_000 - 11_000 - 3_000 - commitment_tx_fee - anchor_outputs_value,
+ amount_satoshis: 1_000_000 - 12_000 - 3_000 - commitment_tx_fee - anchor_outputs_value,
confirmation_height: to_remote_conf_height,
}, Balance::CounterpartyRevokedOutputClaimable {
// to_self output in B's revoked commitment
- amount_satoshis: 10_000,
+ amount_satoshis: 11_000,
}, Balance::CounterpartyRevokedOutputClaimable { // HTLC 1
amount_satoshis: 3_000,
}, Balance::CounterpartyRevokedOutputClaimable { // HTLC 2
mine_transaction(&nodes[0], &as_htlc_claim_tx[0]);
assert_eq!(sorted_vec(vec![Balance::ClaimableAwaitingConfirmations {
// to_remote output in B's revoked commitment
- amount_satoshis: 1_000_000 - 11_000 - 3_000 - commitment_tx_fee - anchor_outputs_value,
+ amount_satoshis: 1_000_000 - 12_000 - 3_000 - commitment_tx_fee - anchor_outputs_value,
confirmation_height: to_remote_conf_height,
}, Balance::CounterpartyRevokedOutputClaimable {
// to_self output in B's revoked commitment
- amount_satoshis: 10_000,
+ amount_satoshis: 11_000,
}, Balance::CounterpartyRevokedOutputClaimable { // HTLC 2
amount_satoshis: 1_000,
}, Balance::ClaimableAwaitingConfirmations {
test_spendable_output(&nodes[0], &revoked_local_txn[0], false);
assert_eq!(sorted_vec(vec![Balance::CounterpartyRevokedOutputClaimable {
// to_self output to B
- amount_satoshis: 10_000,
+ amount_satoshis: 11_000,
}, Balance::CounterpartyRevokedOutputClaimable { // HTLC 2
amount_satoshis: 1_000,
}, Balance::ClaimableAwaitingConfirmations {
test_spendable_output(&nodes[0], &as_htlc_claim_tx[0], false);
assert_eq!(sorted_vec(vec![Balance::CounterpartyRevokedOutputClaimable {
// to_self output in B's revoked commitment
- amount_satoshis: 10_000,
+ amount_satoshis: 11_000,
}, Balance::CounterpartyRevokedOutputClaimable { // HTLC 2
amount_satoshis: 1_000,
}]),
connect_blocks(&nodes[0], ANTI_REORG_DELAY - 1);
assert_eq!(sorted_vec(vec![Balance::CounterpartyRevokedOutputClaimable {
// to_self output in B's revoked commitment
- amount_satoshis: 10_000,
+ amount_satoshis: 11_000,
}, Balance::CounterpartyRevokedOutputClaimable { // HTLC 2
amount_satoshis: 1_000,
}]),
mine_transaction(&nodes[0], &revoked_htlc_timeout_claim);
assert_eq!(sorted_vec(vec![Balance::CounterpartyRevokedOutputClaimable {
// to_self output in B's revoked commitment
- amount_satoshis: 10_000,
+ amount_satoshis: 11_000,
}, Balance::ClaimableAwaitingConfirmations {
amount_satoshis: revoked_htlc_timeout_claim.output[0].value,
confirmation_height: nodes[0].best_block_info().1 + ANTI_REORG_DELAY - 1,
// secret to the counterparty. However, because we always immediately take the revocation
// secret from the keys_manager, we would panic at broadcast as we're trying to sign a
// transaction which, from the point of view of our keys_manager, is revoked.
- chanmon_cfgs[1].keys_manager.disable_revocation_policy_check = true;
+ chanmon_cfgs[0].keys_manager.disable_revocation_policy_check = true;
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
let mut user_config = test_default_channel_config();
if anchors {
do_test_anchors_monitor_fixes_counterparty_payment_script_on_reload(false);
do_test_anchors_monitor_fixes_counterparty_payment_script_on_reload(true);
}
+
+#[cfg(not(feature = "_test_vectors"))]
+fn do_test_monitor_claims_with_random_signatures(anchors: bool, confirm_counterparty_commitment: bool) {
+ // Tests that our monitor claims will always use fresh random signatures (ensuring a unique
+ // wtxid) to prevent certain classes of transaction replacement at the bitcoin P2P layer.
+ let chanmon_cfgs = create_chanmon_cfgs(2);
+ let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
+ let mut user_config = test_default_channel_config();
+ if anchors {
+ user_config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;
+ user_config.manually_accept_inbound_channels = true;
+ }
+ let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(user_config), Some(user_config)]);
+ let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+
+ let coinbase_tx = Transaction {
+ version: 2,
+ lock_time: PackedLockTime::ZERO,
+ input: vec![TxIn { ..Default::default() }],
+ output: vec![
+ TxOut {
+ value: Amount::ONE_BTC.to_sat(),
+ script_pubkey: nodes[0].wallet_source.get_change_script().unwrap(),
+ },
+ ],
+ };
+ if anchors {
+ nodes[0].wallet_source.add_utxo(bitcoin::OutPoint { txid: coinbase_tx.txid(), vout: 0 }, coinbase_tx.output[0].value);
+ }
+
+ // Open a channel and route a payment. We'll let it timeout to claim it.
+ let (_, _, chan_id, funding_tx) = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
+ route_payment(&nodes[0], &[&nodes[1]], 1_000_000);
+
+ let (closing_node, other_node) = if confirm_counterparty_commitment {
+ (&nodes[1], &nodes[0])
+ } else {
+ (&nodes[0], &nodes[1])
+ };
+
+ closing_node.node.force_close_broadcasting_latest_txn(&chan_id, &other_node.node.get_our_node_id()).unwrap();
+
+ // The commitment transaction comes first.
+ let commitment_tx = {
+ let mut txn = closing_node.tx_broadcaster.unique_txn_broadcast();
+ assert_eq!(txn.len(), 1);
+ check_spends!(txn[0], funding_tx);
+ txn.pop().unwrap()
+ };
+
+ 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::HolderForceClosed, [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_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,
+ // we'll see another broadcast of the commitment transaction.
+ if anchors && !confirm_counterparty_commitment && nodes[0].connect_style.borrow().updates_best_block_first() {
+ let _ = nodes[0].tx_broadcaster.txn_broadcast();
+ }
+
+ // Then comes the HTLC timeout transaction.
+ if confirm_counterparty_commitment {
+ connect_blocks(&nodes[0], 5);
+ test_spendable_output(&nodes[0], &commitment_tx, false);
+ connect_blocks(&nodes[0], TEST_FINAL_CLTV - 5);
+ } else {
+ connect_blocks(&nodes[0], TEST_FINAL_CLTV);
+ }
+ if anchors && !confirm_counterparty_commitment {
+ handle_bump_htlc_event(&nodes[0], 1);
+ }
+ let htlc_timeout_tx = {
+ let mut txn = nodes[0].tx_broadcaster.txn_broadcast();
+ assert_eq!(txn.len(), 1);
+ let tx = if txn[0].input[0].previous_output.txid == commitment_tx.txid() {
+ txn[0].clone()
+ } else {
+ txn[1].clone()
+ };
+ check_spends!(tx, commitment_tx, coinbase_tx);
+ tx
+ };
+
+ // Check we rebroadcast it with a different wtxid.
+ nodes[0].chain_monitor.chain_monitor.rebroadcast_pending_claims();
+ if anchors && !confirm_counterparty_commitment {
+ handle_bump_htlc_event(&nodes[0], 1);
+ }
+ {
+ let mut txn = nodes[0].tx_broadcaster.txn_broadcast();
+ assert_eq!(txn.len(), 1);
+ assert_eq!(txn[0].txid(), htlc_timeout_tx.txid());
+ assert_ne!(txn[0].wtxid(), htlc_timeout_tx.wtxid());
+ }
+}
+
+#[cfg(not(feature = "_test_vectors"))]
+#[test]
+fn test_monitor_claims_with_random_signatures() {
+ do_test_monitor_claims_with_random_signatures(false, false);
+ do_test_monitor_claims_with_random_signatures(false, true);
+ do_test_monitor_claims_with_random_signatures(true, false);
+ do_test_monitor_claims_with_random_signatures(true, true);
+}
use bitcoin::secp256k1::ecdsa::Signature;
use bitcoin::{secp256k1, Witness};
use bitcoin::blockdata::script::Script;
-use bitcoin::hash_types::{Txid, BlockHash};
+use bitcoin::hash_types::Txid;
use crate::blinded_path::payment::ReceiveTlvs;
use crate::ln::{ChannelId, PaymentPreimage, PaymentHash, PaymentSecret};
use core::ops::Deref;
#[cfg(feature = "std")]
use core::str::FromStr;
+#[cfg(feature = "std")]
+use std::net::SocketAddr;
+use core::fmt::Display;
use crate::io::{self, Cursor, Read};
use crate::io_extras::read_to_end;
-use crate::events::{MessageSendEventsProvider, OnionMessageProvider};
+use crate::events::MessageSendEventsProvider;
use crate::util::chacha20poly1305rfc::ChaChaPolyReadAdapter;
use crate::util::logger;
use crate::util::ser::{LengthReadable, LengthReadableArgs, Readable, ReadableArgs, Writeable, Writer, WithoutLength, FixedLengthReader, HighZeroBytesDroppedBigSize, Hostname, TransactionU16LenLimited, BigSize};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct OpenChannel {
/// The genesis hash of the blockchain where the channel is to be opened
- pub chain_hash: BlockHash,
+ pub chain_hash: ChainHash,
/// A temporary channel ID, until the funding outpoint is announced
pub temporary_channel_id: ChannelId,
/// The channel value
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct OpenChannelV2 {
/// The genesis hash of the blockchain where the channel is to be opened
- pub chain_hash: BlockHash,
+ pub chain_hash: ChainHash,
/// A temporary channel ID derived using a zeroed out value for the channel acceptor's revocation basepoint
pub temporary_channel_id: ChannelId,
/// The feerate for the funding transaction set by the channel initiator
}
}
+#[cfg(feature = "std")]
+impl std::net::ToSocketAddrs for SocketAddress {
+ type Iter = std::vec::IntoIter<std::net::SocketAddr>;
+
+ fn to_socket_addrs(&self) -> std::io::Result<Self::Iter> {
+ match self {
+ SocketAddress::TcpIpV4 { addr, port } => {
+ let ip_addr = std::net::Ipv4Addr::from(*addr);
+ let socket_addr = SocketAddr::new(ip_addr.into(), *port);
+ Ok(vec![socket_addr].into_iter())
+ }
+ SocketAddress::TcpIpV6 { addr, port } => {
+ let ip_addr = std::net::Ipv6Addr::from(*addr);
+ let socket_addr = SocketAddr::new(ip_addr.into(), *port);
+ Ok(vec![socket_addr].into_iter())
+ }
+ SocketAddress::Hostname { ref hostname, port } => {
+ (hostname.as_str(), *port).to_socket_addrs()
+ }
+ SocketAddress::OnionV2(..) => {
+ Err(std::io::Error::new(std::io::ErrorKind::Other, "Resolution of OnionV2 \
+ addresses is currently unsupported."))
+ }
+ SocketAddress::OnionV3 { .. } => {
+ Err(std::io::Error::new(std::io::ErrorKind::Other, "Resolution of OnionV3 \
+ addresses is currently unsupported."))
+ }
+ }
+ }
+}
+
/// Parses an OnionV3 host and port into a [`SocketAddress::OnionV3`].
///
/// The host part must end with ".onion".
}
}
+impl Display for SocketAddress {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ match self {
+ SocketAddress::TcpIpV4{addr, port} => write!(
+ f, "{}.{}.{}.{}:{}", addr[0], addr[1], addr[2], addr[3], port)?,
+ SocketAddress::TcpIpV6{addr, port} => write!(
+ f,
+ "[{:02x}{:02x}:{:02x}{:02x}:{:02x}{:02x}:{:02x}{:02x}:{:02x}{:02x}:{:02x}{:02x}:{:02x}{:02x}:{:02x}{:02x}]:{}",
+ addr[0], addr[1], addr[2], addr[3], addr[4], addr[5], addr[6], addr[7], addr[8], addr[9], addr[10], addr[11], addr[12], addr[13], addr[14], addr[15], port
+ )?,
+ SocketAddress::OnionV2(bytes) => write!(f, "OnionV2({:?})", bytes)?,
+ SocketAddress::OnionV3 {
+ ed25519_pubkey,
+ checksum,
+ version,
+ port,
+ } => {
+ let [first_checksum_flag, second_checksum_flag] = checksum.to_be_bytes();
+ let mut addr = vec![*version, first_checksum_flag, second_checksum_flag];
+ addr.extend_from_slice(ed25519_pubkey);
+ let onion = base32::Alphabet::RFC4648 { padding: false }.encode(&addr);
+ write!(f, "{}.onion:{}", onion, port)?
+ },
+ SocketAddress::Hostname { hostname, port } => write!(f, "{}:{}", hostname, port)?,
+ }
+ Ok(())
+ }
+}
+
#[cfg(feature = "std")]
impl FromStr for SocketAddress {
type Err = SocketAddressParseError;
/// The advertised channel features
pub features: ChannelFeatures,
/// The genesis hash of the blockchain where the channel is to be opened
- pub chain_hash: BlockHash,
+ pub chain_hash: ChainHash,
/// The short channel ID
pub short_channel_id: u64,
/// One of the two `node_id`s which are endpoints of this channel
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct UnsignedChannelUpdate {
/// The genesis hash of the blockchain where the channel is to be opened
- pub chain_hash: BlockHash,
+ pub chain_hash: ChainHash,
/// The short channel ID
pub short_channel_id: u64,
/// A strictly monotonic announcement counter, with gaps allowed, specific to this channel
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct QueryChannelRange {
/// The genesis hash of the blockchain being queried
- pub chain_hash: BlockHash,
+ pub chain_hash: ChainHash,
/// The height of the first block for the channel UTXOs being queried
pub first_blocknum: u32,
/// The number of blocks to include in the query results
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ReplyChannelRange {
/// The genesis hash of the blockchain being queried
- pub chain_hash: BlockHash,
+ pub chain_hash: ChainHash,
/// The height of the first block in the range of the reply
pub first_blocknum: u32,
/// The number of blocks included in the range of the reply
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct QueryShortChannelIds {
/// The genesis hash of the blockchain being queried
- pub chain_hash: BlockHash,
+ pub chain_hash: ChainHash,
/// The short_channel_ids that are being queried
pub short_channel_ids: Vec<u64>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ReplyShortChannelIdsEnd {
/// The genesis hash of the blockchain that was queried
- pub chain_hash: BlockHash,
+ pub chain_hash: ChainHash,
/// Indicates if the query recipient maintains up-to-date channel
/// information for the `chain_hash`
pub full_information: bool,
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GossipTimestampFilter {
/// The genesis hash of the blockchain for channel and node information
- pub chain_hash: BlockHash,
+ pub chain_hash: ChainHash,
/// The starting unix timestamp
pub first_timestamp: u32,
/// The range of information in seconds
/// Note that this method is called before [`Self::peer_connected`].
fn provided_init_features(&self, their_node_id: &PublicKey) -> InitFeatures;
- /// Gets the genesis hashes for this `ChannelMessageHandler` indicating which chains it supports.
+ /// Gets the chain hashes for this `ChannelMessageHandler` indicating which chains it supports.
///
/// If it's `None`, then no particular network chain hash compatibility will be enforced when
/// connecting to peers.
- fn get_genesis_hashes(&self) -> Option<Vec<ChainHash>>;
+ fn get_chain_hashes(&self) -> Option<Vec<ChainHash>>;
}
/// A trait to describe an object which can receive routing messages.
fn provided_init_features(&self, their_node_id: &PublicKey) -> InitFeatures;
}
-/// A trait to describe an object that can receive onion messages.
-pub trait OnionMessageHandler : OnionMessageProvider {
+/// A handler for received [`OnionMessage`]s and for providing generated ones to send.
+pub trait OnionMessageHandler {
/// Handle an incoming `onion_message` message from the given peer.
fn handle_onion_message(&self, peer_node_id: &PublicKey, msg: &OnionMessage);
+
+ /// Returns the next pending onion message for the peer with the given node id.
+ fn next_onion_message_for_peer(&self, peer_node_id: PublicKey) -> Option<OnionMessage>;
+
/// Called when a connection is established with a peer. Can be used to track which peers
/// advertise onion message support and are online.
///
/// with us. Implementors should be somewhat conservative about doing so, however, as other
/// message handlers may still wish to communicate with this peer.
fn peer_connected(&self, their_node_id: &PublicKey, init: &Init, inbound: bool) -> Result<(), ()>;
+
/// Indicates a connection to the peer failed/an existing connection was lost. Allows handlers to
/// drop and refuse to forward onion messages to this peer.
fn peer_disconnected(&self, their_node_id: &PublicKey);
impl Readable for QueryShortChannelIds {
fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
- let chain_hash: BlockHash = Readable::read(r)?;
+ let chain_hash: ChainHash = Readable::read(r)?;
let encoding_len: u16 = Readable::read(r)?;
let encoding_type: u8 = Readable::read(r)?;
impl Readable for ReplyChannelRange {
fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
- let chain_hash: BlockHash = Readable::read(r)?;
+ let chain_hash: ChainHash = Readable::read(r)?;
let first_blocknum: u32 = Readable::read(r)?;
let number_of_blocks: u32 = Readable::read(r)?;
let sync_complete: bool = Readable::read(r)?;
#[cfg(test)]
mod tests {
use std::convert::TryFrom;
- use bitcoin::blockdata::constants::ChainHash;
use bitcoin::{Transaction, PackedLockTime, TxIn, Script, Sequence, Witness, TxOut};
use hex;
use crate::ln::{PaymentPreimage, PaymentHash, PaymentSecret};
use bitcoin::hashes::hex::FromHex;
use bitcoin::util::address::Address;
use bitcoin::network::constants::Network;
+ use bitcoin::blockdata::constants::ChainHash;
use bitcoin::blockdata::script::Builder;
use bitcoin::blockdata::opcodes;
- use bitcoin::hash_types::{Txid, BlockHash};
+ use bitcoin::hash_types::Txid;
use bitcoin::secp256k1::{PublicKey,SecretKey};
use bitcoin::secp256k1::{Secp256k1, Message};
use crate::chain::transaction::OutPoint;
#[cfg(feature = "std")]
- use std::net::{Ipv4Addr, Ipv6Addr};
+ use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs};
+ #[cfg(feature = "std")]
use crate::ln::msgs::SocketAddressParseError;
#[test]
}
let unsigned_channel_announcement = msgs::UnsignedChannelAnnouncement {
features,
- chain_hash: BlockHash::from_hex("6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000").unwrap(),
+ chain_hash: ChainHash::using_genesis_block(Network::Bitcoin),
short_channel_id: 2316138423780173,
node_id_1: NodeId::from_pubkey(&pubkey_1),
node_id_2: NodeId::from_pubkey(&pubkey_2),
} else {
target_value.append(&mut hex::decode("0000").unwrap());
}
- target_value.append(&mut hex::decode("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f").unwrap());
+ target_value.append(&mut hex::decode("6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000").unwrap());
target_value.append(&mut hex::decode("00083a840000034d031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f024d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d076602531fe6068134503d2723133227c867ac8fa6c83c537e9a44c3c5bdbdcb1fe33703462779ad4aad39514614751a71085f2f10e1c7a593e4e030efb5b8721ce55b0b").unwrap());
if excess_data {
target_value.append(&mut hex::decode("0a00001400001e000028").unwrap());
let (privkey_1, _) = get_keys_from!("0101010101010101010101010101010101010101010101010101010101010101", secp_ctx);
let sig_1 = get_sig_on!(privkey_1, secp_ctx, String::from("01010101010101010101010101010101"));
let unsigned_channel_update = msgs::UnsignedChannelUpdate {
- chain_hash: BlockHash::from_hex("6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000").unwrap(),
+ chain_hash: ChainHash::using_genesis_block(Network::Bitcoin),
short_channel_id: 2316138423780173,
timestamp: 20190119,
flags: if direction { 1 } else { 0 } | if disable { 1 << 1 } else { 0 },
};
let encoded_value = channel_update.encode();
let mut target_value = hex::decode("d977cb9b53d93a6ff64bb5f1e158b4094b66e798fb12911168a3ccdf80a83096340a6a95da0ae8d9f776528eecdbb747eb6b545495a4319ed5378e35b21e073a").unwrap();
- target_value.append(&mut hex::decode("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f").unwrap());
+ target_value.append(&mut hex::decode("6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000").unwrap());
target_value.append(&mut hex::decode("00083a840000034d013413a7").unwrap());
target_value.append(&mut hex::decode("01").unwrap());
target_value.append(&mut hex::decode("00").unwrap());
let (_, pubkey_5) = get_keys_from!("0505050505050505050505050505050505050505050505050505050505050505", secp_ctx);
let (_, pubkey_6) = get_keys_from!("0606060606060606060606060606060606060606060606060606060606060606", secp_ctx);
let open_channel = msgs::OpenChannel {
- chain_hash: BlockHash::from_hex("6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000").unwrap(),
+ chain_hash: ChainHash::using_genesis_block(Network::Bitcoin),
temporary_channel_id: ChannelId::from_bytes([2; 32]),
funding_satoshis: 1311768467284833366,
push_msat: 2536655962884945560,
};
let encoded_value = open_channel.encode();
let mut target_value = Vec::new();
- target_value.append(&mut hex::decode("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f").unwrap());
+ target_value.append(&mut hex::decode("6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000").unwrap());
target_value.append(&mut hex::decode("02020202020202020202020202020202020202020202020202020202020202021234567890123456233403289122369832144668701144767633030896203198784335490624111800083a840000034d000c89d4c0bcc0bc031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f024d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d076602531fe6068134503d2723133227c867ac8fa6c83c537e9a44c3c5bdbdcb1fe33703462779ad4aad39514614751a71085f2f10e1c7a593e4e030efb5b8721ce55b0b0362c0a046dacce86ddd0343c6d3c7c79c2208ba0d9c9cf24a6d046d21d21f90f703f006a18d5653c4edf5391ff23a61f03ff83d237e880ee61187fa9f379a028e0a").unwrap());
if random_bit {
target_value.append(&mut hex::decode("20").unwrap());
let (_, pubkey_6) = get_keys_from!("0606060606060606060606060606060606060606060606060606060606060606", secp_ctx);
let (_, pubkey_7) = get_keys_from!("0707070707070707070707070707070707070707070707070707070707070707", secp_ctx);
let open_channelv2 = msgs::OpenChannelV2 {
- chain_hash: BlockHash::from_hex("6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000").unwrap(),
+ chain_hash: ChainHash::using_genesis_block(Network::Bitcoin),
temporary_channel_id: ChannelId::from_bytes([2; 32]),
funding_feerate_sat_per_1000_weight: 821716,
commitment_feerate_sat_per_1000_weight: 821716,
};
let encoded_value = open_channelv2.encode();
let mut target_value = Vec::new();
- target_value.append(&mut hex::decode("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f").unwrap());
+ target_value.append(&mut hex::decode("6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000").unwrap());
target_value.append(&mut hex::decode("0202020202020202020202020202020202020202020202020202020202020202").unwrap());
target_value.append(&mut hex::decode("000c89d4").unwrap());
target_value.append(&mut hex::decode("000c89d4").unwrap());
#[test]
fn encoding_init() {
- let mainnet_hash = ChainHash::from_hex("6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000").unwrap();
+ let mainnet_hash = ChainHash::using_genesis_block(Network::Bitcoin);
assert_eq!(msgs::Init {
features: InitFeatures::from_le_bytes(vec![0xFF, 0xFF, 0xFF]),
networks: Some(vec![mainnet_hash]),
for (first_blocknum, number_of_blocks, expected) in tests.into_iter() {
let sut = msgs::QueryChannelRange {
- chain_hash: BlockHash::from_hex("06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f").unwrap(),
+ chain_hash: ChainHash::using_genesis_block(Network::Regtest),
first_blocknum,
number_of_blocks,
};
#[test]
fn encoding_query_channel_range() {
let mut query_channel_range = msgs::QueryChannelRange {
- chain_hash: BlockHash::from_hex("06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f").unwrap(),
+ chain_hash: ChainHash::using_genesis_block(Network::Regtest),
first_blocknum: 100000,
number_of_blocks: 1500,
};
let encoded_value = query_channel_range.encode();
- let target_value = hex::decode("0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206000186a0000005dc").unwrap();
+ let target_value = hex::decode("06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f000186a0000005dc").unwrap();
assert_eq!(encoded_value, target_value);
query_channel_range = Readable::read(&mut Cursor::new(&target_value[..])).unwrap();
}
fn do_encoding_reply_channel_range(encoding_type: u8) {
- let mut target_value = hex::decode("0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206000b8a06000005dc01").unwrap();
- let expected_chain_hash = BlockHash::from_hex("06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f").unwrap();
+ let mut target_value = hex::decode("06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f000b8a06000005dc01").unwrap();
+ let expected_chain_hash = ChainHash::using_genesis_block(Network::Regtest);
let mut reply_channel_range = msgs::ReplyChannelRange {
chain_hash: expected_chain_hash,
first_blocknum: 756230,
}
fn do_encoding_query_short_channel_ids(encoding_type: u8) {
- let mut target_value = hex::decode("0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206").unwrap();
- let expected_chain_hash = BlockHash::from_hex("06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f").unwrap();
+ let mut target_value = hex::decode("06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f").unwrap();
+ let expected_chain_hash = ChainHash::using_genesis_block(Network::Regtest);
let mut query_short_channel_ids = msgs::QueryShortChannelIds {
chain_hash: expected_chain_hash,
short_channel_ids: vec![0x0000000000008e, 0x0000000000003c69, 0x000000000045a6c4],
#[test]
fn encoding_reply_short_channel_ids_end() {
- let expected_chain_hash = BlockHash::from_hex("06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f").unwrap();
+ let expected_chain_hash = ChainHash::using_genesis_block(Network::Regtest);
let mut reply_short_channel_ids_end = msgs::ReplyShortChannelIdsEnd {
chain_hash: expected_chain_hash,
full_information: true,
};
let encoded_value = reply_short_channel_ids_end.encode();
- let target_value = hex::decode("0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e220601").unwrap();
+ let target_value = hex::decode("06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f01").unwrap();
assert_eq!(encoded_value, target_value);
reply_short_channel_ids_end = Readable::read(&mut Cursor::new(&target_value[..])).unwrap();
#[test]
fn encoding_gossip_timestamp_filter(){
- let expected_chain_hash = BlockHash::from_hex("06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f").unwrap();
+ let expected_chain_hash = ChainHash::using_genesis_block(Network::Regtest);
let mut gossip_timestamp_filter = msgs::GossipTimestampFilter {
chain_hash: expected_chain_hash,
first_timestamp: 1590000000,
timestamp_range: 0xffff_ffff,
};
let encoded_value = gossip_timestamp_filter.encode();
- let target_value = hex::decode("0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e22065ec57980ffffffff").unwrap();
+ let target_value = hex::decode("06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f5ec57980ffffffff").unwrap();
assert_eq!(encoded_value, target_value);
gossip_timestamp_filter = Readable::read(&mut Cursor::new(&target_value[..])).unwrap();
#[test]
#[cfg(feature = "std")]
fn test_socket_address_from_str() {
- assert_eq!(SocketAddress::TcpIpV4 {
+ let tcpip_v4 = SocketAddress::TcpIpV4 {
addr: Ipv4Addr::new(127, 0, 0, 1).octets(),
port: 1234,
- }, SocketAddress::from_str("127.0.0.1:1234").unwrap());
+ };
+ assert_eq!(tcpip_v4, SocketAddress::from_str("127.0.0.1:1234").unwrap());
+ assert_eq!(tcpip_v4, SocketAddress::from_str(&tcpip_v4.to_string()).unwrap());
- assert_eq!(SocketAddress::TcpIpV6 {
+ let tcpip_v6 = SocketAddress::TcpIpV6 {
addr: Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1).octets(),
port: 1234,
- }, SocketAddress::from_str("[0:0:0:0:0:0:0:1]:1234").unwrap());
- assert_eq!(
- SocketAddress::Hostname {
+ };
+ assert_eq!(tcpip_v6, SocketAddress::from_str("[0:0:0:0:0:0:0:1]:1234").unwrap());
+ assert_eq!(tcpip_v6, SocketAddress::from_str(&tcpip_v6.to_string()).unwrap());
+
+ let hostname = SocketAddress::Hostname {
hostname: Hostname::try_from("lightning-node.mydomain.com".to_string()).unwrap(),
port: 1234,
- }, SocketAddress::from_str("lightning-node.mydomain.com:1234").unwrap());
- assert_eq!(
- SocketAddress::Hostname {
- hostname: Hostname::try_from("example.com".to_string()).unwrap(),
- port: 1234,
- }, SocketAddress::from_str("example.com:1234").unwrap());
- assert_eq!(SocketAddress::OnionV3 {
+ };
+ assert_eq!(hostname, SocketAddress::from_str("lightning-node.mydomain.com:1234").unwrap());
+ assert_eq!(hostname, SocketAddress::from_str(&hostname.to_string()).unwrap());
+
+ let onion_v2 = SocketAddress::OnionV2 ([40, 4, 64, 185, 202, 19, 162, 75, 90, 200, 38, 7],);
+ assert_eq!("OnionV2([40, 4, 64, 185, 202, 19, 162, 75, 90, 200, 38, 7])", &onion_v2.to_string());
+ assert_eq!(Err(SocketAddressParseError::InvalidOnionV3), SocketAddress::from_str("FACEBOOKCOREWWWI.onion:9735"));
+
+ let onion_v3 = SocketAddress::OnionV3 {
ed25519_pubkey: [37, 24, 75, 5, 25, 73, 117, 194, 139, 102, 182, 107, 4, 105, 247, 246, 85,
111, 177, 172, 49, 137, 167, 155, 64, 221, 163, 47, 31, 33, 71, 3],
checksum: 48326,
version: 121,
port: 1234
- }, SocketAddress::from_str("pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion:1234").unwrap());
+ };
+ assert_eq!(onion_v3, SocketAddress::from_str("pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion:1234").unwrap());
+ assert_eq!(onion_v3, SocketAddress::from_str(&onion_v3.to_string()).unwrap());
+
assert_eq!(Err(SocketAddressParseError::InvalidOnionV3), SocketAddress::from_str("pg6mmjiyjmcrsslvykfwnntlaru7p5svn6.onion:1234"));
assert_eq!(Err(SocketAddressParseError::InvalidInput), SocketAddress::from_str("127.0.0.1@1234"));
assert_eq!(Err(SocketAddressParseError::InvalidInput), "".parse::<SocketAddress>());
assert!("invalid-address".parse::<SocketAddress>().is_err());
assert!(SocketAddress::from_str("pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion.onion:1234").is_err());
}
+
+ #[test]
+ #[cfg(feature = "std")]
+ fn test_socket_address_to_socket_addrs() {
+ assert_eq!(SocketAddress::TcpIpV4 {addr:[0u8; 4], port: 1337,}.to_socket_addrs().unwrap().next().unwrap(),
+ SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(0,0,0,0), 1337)));
+ assert_eq!(SocketAddress::TcpIpV6 {addr:[0u8; 16], port: 1337,}.to_socket_addrs().unwrap().next().unwrap(),
+ SocketAddr::V6(SocketAddrV6::new(Ipv6Addr::from([0u8; 16]), 1337, 0, 0)));
+ assert_eq!(SocketAddress::Hostname { hostname: Hostname::try_from("0.0.0.0".to_string()).unwrap(), port: 0 }
+ .to_socket_addrs().unwrap().next().unwrap(), SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::from([0u8; 4]),0)));
+ assert!(SocketAddress::OnionV2([0u8; 12]).to_socket_addrs().is_err());
+ assert!(SocketAddress::OnionV3{ ed25519_pubkey: [37, 24, 75, 5, 25, 73, 117, 194, 139, 102,
+ 182, 107, 4, 105, 247, 246, 85, 111, 177, 172, 49, 137, 167, 155, 64, 221, 163, 47, 31,
+ 33, 71, 3],
+ checksum: 48326,
+ version: 121,
+ port: 1234 }.to_socket_addrs().is_err());
+ }
}
use crate::util::config::{UserConfig, ChannelConfig, MaxDustHTLCExposure};
use crate::util::errors::APIError;
-use bitcoin::hash_types::BlockHash;
-
+use bitcoin::blockdata::constants::ChainHash;
use bitcoin::hashes::{Hash, HashEngine};
use bitcoin::hashes::hmac::{Hmac, HmacEngine};
use bitcoin::hashes::sha256::Hash as Sha256;
impl msgs::ChannelUpdate {
fn dummy(short_channel_id: u64) -> msgs::ChannelUpdate {
+ use bitcoin::hash_types::BlockHash;
use bitcoin::secp256k1::ffi::Signature as FFISignature;
use bitcoin::secp256k1::ecdsa::Signature;
msgs::ChannelUpdate {
signature: Signature::from(unsafe { FFISignature::new() }),
contents: msgs::UnsignedChannelUpdate {
- chain_hash: BlockHash::hash(&vec![0u8][..]),
+ chain_hash: ChainHash::from(BlockHash::hash(&vec![0u8][..]).as_ref()),
short_channel_id,
timestamp: 0,
flags: 0,
htlc_minimum_msat: None,
}])];
let payment_params = PaymentParameters::from_node_id(*channel_to_update_counterparty, TEST_FINAL_CLTV)
- .with_bolt11_features(nodes[2].node.invoice_features()).unwrap()
+ .with_bolt11_features(nodes[2].node.bolt11_invoice_features()).unwrap()
.with_route_hints(hop_hints).unwrap();
get_route_and_payment_hash!(nodes[0], nodes[2], payment_params, PAYMENT_AMT)
};
let phantom_pubkey = $nodes[1].keys_manager.get_node_id(Recipient::PhantomNode).unwrap();
let phantom_route_hint = $nodes[1].node.get_phantom_route_hints();
let payment_params = PaymentParameters::from_node_id(phantom_pubkey, TEST_FINAL_CLTV)
- .with_bolt11_features($nodes[1].node.invoice_features()).unwrap()
+ .with_bolt11_features($nodes[1].node.bolt11_invoice_features()).unwrap()
.with_route_hints(vec![RouteHint(vec![
RouteHintHop {
src_node_id: $nodes[0].node.get_our_node_id(),
}
/// Calculates a pubkey for the next hop, such as the next hop's packet pubkey or blinding point.
-pub(crate) fn next_hop_pubkey<T: secp256k1::Signing + secp256k1::Verification>(
+pub(crate) fn next_hop_pubkey<T: secp256k1::Verification>(
secp_ctx: &Secp256k1<T>, curr_pubkey: PublicKey, shared_secret: &[u8]
) -> Result<PublicKey, secp256k1::Error> {
let blinding_factor = {
use core::fmt::{self, Display, Formatter};
use core::ops::Deref;
+use core::time::Duration;
use crate::prelude::*;
use crate::sync::Mutex;
/// [`ChannelManager::timer_tick_occurred`]: crate::ln::channelmanager::ChannelManager::timer_tick_occurred
pub(crate) const IDEMPOTENCY_TIMEOUT_TICKS: u8 = 7;
-/// The number of ticks of [`ChannelManager::timer_tick_occurred`] until an invoice request without
-/// a response is timed out.
-///
-/// [`ChannelManager::timer_tick_occurred`]: crate::ln::channelmanager::ChannelManager::timer_tick_occurred
-const INVOICE_REQUEST_TIMEOUT_TICKS: u8 = 3;
-
/// Stores the session_priv for each part of a payment that is still pending. For versions 0.0.102
/// and later, also stores information for retrying the payment.
pub(crate) enum PendingOutboundPayment {
session_privs: HashSet<[u8; 32]>,
},
AwaitingInvoice {
- timer_ticks_without_response: u8,
+ expiration: StaleExpiration,
retry_strategy: Retry,
max_total_routing_fee_msat: Option<u64>,
},
}
}
+/// How long before a [`PendingOutboundPayment::AwaitingInvoice`] should be considered stale and
+/// candidate for removal in [`OutboundPayments::remove_stale_payments`].
+#[derive(Clone, Copy)]
+pub(crate) enum StaleExpiration {
+ /// Number of times [`OutboundPayments::remove_stale_payments`] is called.
+ TimerTicks(u64),
+ /// Duration since the Unix epoch.
+ AbsoluteTimeout(core::time::Duration),
+}
+
+impl_writeable_tlv_based_enum!(StaleExpiration,
+ ;
+ (0, TimerTicks),
+ (2, AbsoluteTimeout)
+);
+
/// Indicates an immediate error on [`ChannelManager::send_payment`]. Further errors may be
/// surfaced later via [`Event::PaymentPathFailed`] and [`Event::PaymentFailed`].
///
}
}
- #[allow(unused)]
pub(super) fn send_payment_for_bolt12_invoice<R: Deref, ES: Deref, NS: Deref, IH, SP, L: Deref>(
&self, invoice: &Bolt12Invoice, payment_id: PaymentId, router: &R,
first_hops: Vec<ChannelDetails>, inflight_htlcs: IH, entropy_source: &ES, node_signer: &NS,
SP: Fn(SendAlongPathArgs) -> Result<(), APIError>,
{
let payment_hash = invoice.payment_hash();
- let mut max_total_routing_fee_msat = None;
+ let max_total_routing_fee_msat;
match self.pending_outbound_payments.lock().unwrap().entry(payment_id) {
hash_map::Entry::Occupied(entry) => match entry.get() {
PendingOutboundPayment::AwaitingInvoice { retry_strategy, max_total_routing_fee_msat: max_total_fee, .. } => {
hash_map::Entry::Vacant(_) => return Err(Bolt12PaymentError::UnexpectedInvoice),
};
- let route_params = RouteParameters {
- payment_params: PaymentParameters::from_bolt12_invoice(&invoice),
- final_value_msat: invoice.amount_msats(),
- max_total_routing_fee_msat,
- };
+ let pay_params = PaymentParameters::from_bolt12_invoice(&invoice);
+ let amount_msat = invoice.amount_msats();
+ let mut route_params = RouteParameters::from_payment_params_and_value(pay_params, amount_msat);
+ if let Some(max_fee_msat) = max_total_routing_fee_msat {
+ route_params.max_total_routing_fee_msat = Some(max_fee_msat);
+ }
self.find_route_and_send_payment(
payment_hash, payment_id, route_params, router, first_hops, &inflight_htlcs,
RetryableSendFailure::RouteNotFound
})?;
- if let Some(route_route_params) = route.route_params.as_mut() {
- if route_route_params.final_value_msat != route_params.final_value_msat {
- debug_assert!(false,
- "Routers are expected to return a route which includes the requested final_value_msat");
- route_route_params.final_value_msat = route_params.final_value_msat;
- }
+ if route.route_params.as_ref() != Some(&route_params) {
+ debug_assert!(false,
+ "Routers are expected to return a Route which includes the requested RouteParameters");
+ route.route_params = Some(route_params.clone());
}
let onion_session_privs = self.add_new_pending_payment(payment_hash,
}
};
- if let Some(route_route_params) = route.route_params.as_mut() {
- if route_route_params.final_value_msat != route_params.final_value_msat {
- debug_assert!(false,
- "Routers are expected to return a route which includes the requested final_value_msat");
- route_route_params.final_value_msat = route_params.final_value_msat;
- }
+ if route.route_params.as_ref() != Some(&route_params) {
+ debug_assert!(false,
+ "Routers are expected to return a Route which includes the requested RouteParameters");
+ route.route_params = Some(route_params.clone());
}
for path in route.paths.iter() {
(payment, onion_session_privs)
}
- #[allow(unused)]
pub(super) fn add_new_awaiting_invoice(
- &self, payment_id: PaymentId, retry_strategy: Retry, max_total_routing_fee_msat: Option<u64>
+ &self, payment_id: PaymentId, expiration: StaleExpiration, retry_strategy: Retry,
+ max_total_routing_fee_msat: Option<u64>
) -> Result<(), ()> {
let mut pending_outbounds = self.pending_outbound_payments.lock().unwrap();
match pending_outbounds.entry(payment_id) {
hash_map::Entry::Occupied(_) => Err(()),
hash_map::Entry::Vacant(entry) => {
entry.insert(PendingOutboundPayment::AwaitingInvoice {
- timer_ticks_without_response: 0,
+ expiration,
retry_strategy,
max_total_routing_fee_msat,
});
}
pub(super) fn remove_stale_payments(
- &self, pending_events: &Mutex<VecDeque<(events::Event, Option<EventCompletionAction>)>>)
+ &self, duration_since_epoch: Duration,
+ pending_events: &Mutex<VecDeque<(events::Event, Option<EventCompletionAction>)>>)
{
let mut pending_outbound_payments = self.pending_outbound_payments.lock().unwrap();
- #[cfg(not(invreqfailed))]
- let pending_events = pending_events.lock().unwrap();
- #[cfg(invreqfailed)]
let mut pending_events = pending_events.lock().unwrap();
- pending_outbound_payments.retain(|payment_id, payment| {
+ pending_outbound_payments.retain(|payment_id, payment| match payment {
// If an outbound payment was completed, and no pending HTLCs remain, we should remove it
// from the map. However, if we did that immediately when the last payment HTLC is claimed,
// this could race the user making a duplicate send_payment call and our idempotency
// removal. This should be more than sufficient to ensure the idempotency of any
// `send_payment` calls that were made at the same time the `PaymentSent` event was being
// processed.
- if let PendingOutboundPayment::Fulfilled { session_privs, timer_ticks_without_htlcs, .. } = payment {
+ PendingOutboundPayment::Fulfilled { session_privs, timer_ticks_without_htlcs, .. } => {
let mut no_remaining_entries = session_privs.is_empty();
if no_remaining_entries {
for (ev, _) in pending_events.iter() {
*timer_ticks_without_htlcs = 0;
true
}
- } else if let PendingOutboundPayment::AwaitingInvoice { timer_ticks_without_response, .. } = payment {
- *timer_ticks_without_response += 1;
- if *timer_ticks_without_response <= INVOICE_REQUEST_TIMEOUT_TICKS {
- true
- } else {
- #[cfg(invreqfailed)]
+ },
+ PendingOutboundPayment::AwaitingInvoice { expiration, .. } => {
+ let is_stale = match expiration {
+ StaleExpiration::AbsoluteTimeout(absolute_expiry) => {
+ *absolute_expiry <= duration_since_epoch
+ },
+ StaleExpiration::TimerTicks(timer_ticks_remaining) => {
+ if *timer_ticks_remaining > 0 {
+ *timer_ticks_remaining -= 1;
+ false
+ } else {
+ true
+ }
+ },
+ };
+ if is_stale {
pending_events.push_back(
(events::Event::InvoiceRequestFailed { payment_id: *payment_id }, None)
);
false
+ } else {
+ true
}
- } else { true }
+ },
+ _ => true,
});
}
payment.remove();
}
} else if let PendingOutboundPayment::AwaitingInvoice { .. } = payment.get() {
- #[cfg(invreqfailed)]
pending_events.lock().unwrap().push_back((events::Event::InvoiceRequestFailed {
payment_id,
}, None));
(2, payment_hash, required),
},
(5, AwaitingInvoice) => {
- (0, timer_ticks_without_response, required),
+ (0, expiration, required),
(2, retry_strategy, required),
(4, max_total_routing_fee_msat, option),
},
use bitcoin::network::constants::Network;
use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
+ use core::time::Duration;
+
use crate::events::{Event, PathFailure, PaymentFailureReason};
use crate::ln::PaymentHash;
use crate::ln::channelmanager::{PaymentId, RecipientOnionFields};
use crate::ln::features::{ChannelFeatures, NodeFeatures};
use crate::ln::msgs::{ErrorAction, LightningError};
- use crate::ln::outbound_payment::{Bolt12PaymentError, OutboundPayments, Retry, RetryableSendFailure};
- #[cfg(invreqfailed)]
- use crate::ln::outbound_payment::INVOICE_REQUEST_TIMEOUT_TICKS;
+ use crate::ln::outbound_payment::{Bolt12PaymentError, OutboundPayments, Retry, RetryableSendFailure, StaleExpiration};
+ #[cfg(feature = "std")]
use crate::offers::invoice::DEFAULT_RELATIVE_EXPIRY;
use crate::offers::offer::OfferBuilder;
use crate::offers::test_utils::*;
router.expect_find_route(route_params.clone(), Ok(route.clone()));
let mut route_params_w_failed_scid = route_params.clone();
route_params_w_failed_scid.payment_params.previously_failed_channels.push(failed_scid);
- router.expect_find_route(route_params_w_failed_scid, Ok(route.clone()));
+ let mut route_w_failed_scid = route.clone();
+ route_w_failed_scid.route_params = Some(route_params_w_failed_scid.clone());
+ router.expect_find_route(route_params_w_failed_scid, Ok(route_w_failed_scid));
router.expect_find_route(route_params.clone(), Ok(route.clone()));
router.expect_find_route(route_params.clone(), Ok(route.clone()));
}
#[test]
- #[cfg(invreqfailed)]
- fn removes_stale_awaiting_invoice() {
+ fn removes_stale_awaiting_invoice_using_absolute_timeout() {
+ let pending_events = Mutex::new(VecDeque::new());
+ let outbound_payments = OutboundPayments::new();
+ let payment_id = PaymentId([0; 32]);
+ let absolute_expiry = 100;
+ let tick_interval = 10;
+ let expiration = StaleExpiration::AbsoluteTimeout(Duration::from_secs(absolute_expiry));
+
+ assert!(!outbound_payments.has_pending_payments());
+ assert!(
+ outbound_payments.add_new_awaiting_invoice(
+ payment_id, expiration, Retry::Attempts(0), None
+ ).is_ok()
+ );
+ assert!(outbound_payments.has_pending_payments());
+
+ for seconds_since_epoch in (0..absolute_expiry).step_by(tick_interval) {
+ let duration_since_epoch = Duration::from_secs(seconds_since_epoch);
+ outbound_payments.remove_stale_payments(duration_since_epoch, &pending_events);
+
+ assert!(outbound_payments.has_pending_payments());
+ assert!(pending_events.lock().unwrap().is_empty());
+ }
+
+ let duration_since_epoch = Duration::from_secs(absolute_expiry);
+ outbound_payments.remove_stale_payments(duration_since_epoch, &pending_events);
+
+ assert!(!outbound_payments.has_pending_payments());
+ assert!(!pending_events.lock().unwrap().is_empty());
+ assert_eq!(
+ pending_events.lock().unwrap().pop_front(),
+ Some((Event::InvoiceRequestFailed { payment_id }, None)),
+ );
+ assert!(pending_events.lock().unwrap().is_empty());
+
+ assert!(
+ outbound_payments.add_new_awaiting_invoice(
+ payment_id, expiration, Retry::Attempts(0), None
+ ).is_ok()
+ );
+ assert!(outbound_payments.has_pending_payments());
+
+ assert!(
+ outbound_payments.add_new_awaiting_invoice(
+ payment_id, expiration, Retry::Attempts(0), None
+ ).is_err()
+ );
+ }
+
+ #[test]
+ fn removes_stale_awaiting_invoice_using_timer_ticks() {
let pending_events = Mutex::new(VecDeque::new());
let outbound_payments = OutboundPayments::new();
let payment_id = PaymentId([0; 32]);
+ let timer_ticks = 3;
+ let expiration = StaleExpiration::TimerTicks(timer_ticks);
assert!(!outbound_payments.has_pending_payments());
assert!(
- outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0), None).is_ok()
+ outbound_payments.add_new_awaiting_invoice(
+ payment_id, expiration, Retry::Attempts(0), None
+ ).is_ok()
);
assert!(outbound_payments.has_pending_payments());
- for _ in 0..INVOICE_REQUEST_TIMEOUT_TICKS {
- outbound_payments.remove_stale_payments(&pending_events);
+ for i in 0..timer_ticks {
+ let duration_since_epoch = Duration::from_secs(i * 60);
+ outbound_payments.remove_stale_payments(duration_since_epoch, &pending_events);
+
assert!(outbound_payments.has_pending_payments());
assert!(pending_events.lock().unwrap().is_empty());
}
- outbound_payments.remove_stale_payments(&pending_events);
+ let duration_since_epoch = Duration::from_secs(timer_ticks * 60);
+ outbound_payments.remove_stale_payments(duration_since_epoch, &pending_events);
+
assert!(!outbound_payments.has_pending_payments());
assert!(!pending_events.lock().unwrap().is_empty());
assert_eq!(
assert!(pending_events.lock().unwrap().is_empty());
assert!(
- outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0), None).is_ok()
+ outbound_payments.add_new_awaiting_invoice(
+ payment_id, expiration, Retry::Attempts(0), None
+ ).is_ok()
);
assert!(outbound_payments.has_pending_payments());
assert!(
- outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0), None)
- .is_err()
+ outbound_payments.add_new_awaiting_invoice(
+ payment_id, expiration, Retry::Attempts(0), None
+ ).is_err()
);
}
#[test]
- #[cfg(invreqfailed)]
fn removes_abandoned_awaiting_invoice() {
let pending_events = Mutex::new(VecDeque::new());
let outbound_payments = OutboundPayments::new();
let payment_id = PaymentId([0; 32]);
+ let expiration = StaleExpiration::AbsoluteTimeout(Duration::from_secs(100));
assert!(!outbound_payments.has_pending_payments());
assert!(
- outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0), None).is_ok()
+ outbound_payments.add_new_awaiting_invoice(
+ payment_id, expiration, Retry::Attempts(0), None
+ ).is_ok()
);
assert!(outbound_payments.has_pending_payments());
let pending_events = Mutex::new(VecDeque::new());
let outbound_payments = OutboundPayments::new();
let payment_id = PaymentId([0; 32]);
+ let expiration = StaleExpiration::AbsoluteTimeout(Duration::from_secs(100));
assert!(
- outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0), None).is_ok()
+ outbound_payments.add_new_awaiting_invoice(
+ payment_id, expiration, Retry::Attempts(0), None
+ ).is_ok()
);
assert!(outbound_payments.has_pending_payments());
let pending_events = Mutex::new(VecDeque::new());
let outbound_payments = OutboundPayments::new();
let payment_id = PaymentId([0; 32]);
+ let expiration = StaleExpiration::AbsoluteTimeout(Duration::from_secs(100));
let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
.amount_msats(1000)
.build().unwrap()
.sign(recipient_sign).unwrap();
- assert!(outbound_payments.add_new_awaiting_invoice(
- payment_id, Retry::Attempts(0), Some(invoice.amount_msats() / 100 + 50_000))
- .is_ok()
+ assert!(
+ outbound_payments.add_new_awaiting_invoice(
+ payment_id, expiration, Retry::Attempts(0),
+ Some(invoice.amount_msats() / 100 + 50_000)
+ ).is_ok()
);
assert!(outbound_payments.has_pending_payments());
let pending_events = Mutex::new(VecDeque::new());
let outbound_payments = OutboundPayments::new();
let payment_id = PaymentId([0; 32]);
+ let expiration = StaleExpiration::AbsoluteTimeout(Duration::from_secs(100));
let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
.amount_msats(1000)
.build().unwrap()
.sign(recipient_sign).unwrap();
- assert!(outbound_payments.add_new_awaiting_invoice(
- payment_id, Retry::Attempts(0), Some(invoice.amount_msats() / 100 + 50_000))
- .is_ok()
+ assert!(
+ outbound_payments.add_new_awaiting_invoice(
+ payment_id, expiration, Retry::Attempts(0),
+ Some(invoice.amount_msats() / 100 + 50_000)
+ ).is_ok()
);
assert!(outbound_payments.has_pending_payments());
let pending_events = Mutex::new(VecDeque::new());
let outbound_payments = OutboundPayments::new();
let payment_id = PaymentId([0; 32]);
+ let expiration = StaleExpiration::AbsoluteTimeout(Duration::from_secs(100));
let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
.amount_msats(1000)
assert!(pending_events.lock().unwrap().is_empty());
assert!(
- outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0), Some(1234)).is_ok()
+ outbound_payments.add_new_awaiting_invoice(
+ payment_id, expiration, Retry::Attempts(0), Some(1234)
+ ).is_ok()
);
assert!(outbound_payments.has_pending_payments());
use crate::sign::EntropySource;
use crate::chain::transaction::OutPoint;
use crate::events::{ClosureReason, Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider, PathFailure, PaymentFailureReason, PaymentPurpose};
-use crate::ln::channel::EXPIRE_PREV_CONFIG_TICKS;
+use crate::ln::channel::{EXPIRE_PREV_CONFIG_TICKS, commit_tx_fee_msat, get_holder_selected_channel_reserve_satoshis, ANCHOR_OUTPUT_VALUE_SATOSHI};
use crate::ln::channelmanager::{BREAKDOWN_TIMEOUT, MPP_TIMEOUT_TICKS, MIN_CLTV_EXPIRY_DELTA, PaymentId, PaymentSendFailure, RecentPaymentDetails, RecipientOnionFields, HTLCForwardInfo, PendingHTLCRouting, PendingAddHTLCInfo};
-use crate::ln::features::Bolt11InvoiceFeatures;
+use crate::ln::features::{Bolt11InvoiceFeatures, ChannelTypeFeatures};
use crate::ln::{msgs, ChannelId, PaymentSecret, PaymentPreimage};
use crate::ln::msgs::ChannelMessageHandler;
use crate::ln::outbound_payment::{IDEMPOTENCY_TIMEOUT_TICKS, Retry};
let amt_msat = 1_000_000;
let max_total_routing_fee_msat = 50_000;
let payment_params = PaymentParameters::from_node_id(nodes[3].node.get_our_node_id(), TEST_FINAL_CLTV)
- .with_bolt11_features(nodes[3].node.invoice_features()).unwrap();
+ .with_bolt11_features(nodes[3].node.bolt11_invoice_features()).unwrap();
let (mut route, payment_hash, payment_preimage, payment_secret) = get_route_and_payment_hash!(
nodes[0], nodes[3], payment_params, amt_msat, Some(max_total_routing_fee_msat));
let path = route.paths[0].clone();
// Check the remaining max total routing fee for the second attempt is 50_000 - 1_000 msat fee
// used by the first path
route_params.max_total_routing_fee_msat = Some(max_total_routing_fee_msat - 1_000);
+ route.route_params = Some(route_params.clone());
nodes[0].router.expect_find_route(route_params, Ok(route));
nodes[0].node.process_pending_htlc_forwards();
check_added_monitors!(nodes[0], 1);
let max_total_routing_fee_msat = Some(1_000_000);
let payment_params = PaymentParameters::from_node_id(nodes[3].node.get_our_node_id(), TEST_FINAL_CLTV)
- .with_bolt11_features(nodes[3].node.invoice_features()).unwrap();
+ .with_bolt11_features(nodes[3].node.bolt11_invoice_features()).unwrap();
let (mut route, payment_hash, payment_preimage, payment_secret) = get_route_and_payment_hash!(
nodes[0], nodes[3], payment_params, amt_msat, max_total_routing_fee_msat);
route.paths.remove(0);
route_params.final_value_msat -= first_path_value;
- route.route_params.as_mut().map(|p| p.final_value_msat -= first_path_value);
route_params.payment_params.previously_failed_channels.push(chan_4_update.contents.short_channel_id);
-
// Check the remaining max total routing fee for the second attempt accounts only for 1_000 msat
// base fee, but not for overpaid value of the first try.
route_params.max_total_routing_fee_msat.as_mut().map(|m| *m -= 1000);
+
+ route.route_params = Some(route_params.clone());
nodes[0].router.expect_find_route(route_params, Ok(route));
nodes[0].node.process_pending_htlc_forwards();
let bs_reestablish = get_chan_reestablish_msgs!(nodes[1], nodes[0]).pop().unwrap();
nodes[0].node.handle_channel_reestablish(&nodes[1].node.get_our_node_id(), &bs_reestablish);
let as_err = nodes[0].node.get_and_clear_pending_msg_events();
- assert_eq!(as_err.len(), 1);
- match as_err[0] {
+ assert_eq!(as_err.len(), 2);
+ match as_err[1] {
MessageSendEvent::HandleError { node_id, action: msgs::ErrorAction::SendErrorMessage { ref msg } } => {
assert_eq!(node_id, nodes[1].node.get_our_node_id());
nodes[1].node.handle_error(&nodes[0].node.get_our_node_id(), msg);
let bs_reestablish = get_chan_reestablish_msgs!(nodes[1], nodes[0]).pop().unwrap();
nodes[0].node.handle_channel_reestablish(&nodes[1].node.get_our_node_id(), &bs_reestablish);
let as_err = nodes[0].node.get_and_clear_pending_msg_events();
- assert_eq!(as_err.len(), 1);
+ assert_eq!(as_err.len(), 2);
let bs_commitment_tx;
- match as_err[0] {
+ match as_err[1] {
MessageSendEvent::HandleError { node_id, action: msgs::ErrorAction::SendErrorMessage { ref msg } } => {
assert_eq!(node_id, nodes[1].node.get_our_node_id());
nodes[1].node.handle_error(&nodes[0].node.get_our_node_id(), msg);
let (payment_hash, payment_secret) = nodes[1].node.create_inbound_payment(Some(amt_msat), expiry_secs, None).unwrap();
let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), TEST_FINAL_CLTV)
- .with_bolt11_features(nodes[1].node.invoice_features()).unwrap();
+ .with_bolt11_features(nodes[1].node.bolt11_invoice_features()).unwrap();
let scorer = test_utils::TestScorer::new();
let keys_manager = test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
htlc_maximum_msat: None,
}])
]).unwrap()
- .with_bolt11_features(nodes[2].node.invoice_features()).unwrap();
- let route_params = RouteParameters::from_payment_params_and_value(payment_params, amt_msat,);
- let route = get_route(&nodes[0].node.get_our_node_id(), &route_params,
- &nodes[0].network_graph.read_only(), None, nodes[0].logger, &scorer, &Default::default(),
- &random_seed_bytes).unwrap();
+ .with_bolt11_features(nodes[2].node.bolt11_invoice_features()).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, amt_msat);
+ let route = get_route(
+ &nodes[0].node.get_our_node_id(), &route_params, &nodes[0].network_graph.read_only(), None,
+ nodes[0].logger, &scorer, &Default::default(), &random_seed_bytes
+ ).unwrap();
let (payment_hash, payment_secret) = nodes[2].node.create_inbound_payment(Some(amt_msat), 60 * 60, None).unwrap();
nodes[0].node.send_payment_with_route(&route, payment_hash,
}
let payment_params = PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), TEST_FINAL_CLTV)
.with_route_hints(route_hints).unwrap()
- .with_bolt11_features(nodes[2].node.invoice_features()).unwrap();
+ .with_bolt11_features(nodes[2].node.bolt11_invoice_features()).unwrap();
let route_params = RouteParameters::from_payment_params_and_value(payment_params, amt_msat);
let (payment_hash, payment_secret) = nodes[2].node.create_inbound_payment(Some(amt_msat), 60 * 60, None).unwrap();
nodes[0].node.send_payment(payment_hash, RecipientOnionFields::secret_only(payment_secret),
let mut retry_params = RouteParameters::from_payment_params_and_value(pay_params, 100_000_000);
retry_params.max_total_routing_fee_msat = None;
- route.route_params.as_mut().unwrap().final_value_msat = 100_000_000;
+ route.route_params = Some(retry_params.clone());
nodes[0].router.expect_find_route(retry_params, Ok(route.clone()));
{
maybe_announced_channel: true,
}], blinded_tail: None },
],
- route_params: Some(RouteParameters::from_payment_params_and_value(
- PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), TEST_FINAL_CLTV),
- 100_000_001)),
+ route_params: Some(route_params.clone()),
};
nodes[0].router.expect_find_route(route_params.clone(), Ok(route.clone()));
// On retry, split the payment across both channels.
route.paths[1].hops[0].fee_msat = 50_000_001;
let mut pay_params = route_params.payment_params.clone();
pay_params.previously_failed_channels.push(chans[0].short_channel_id.unwrap());
- nodes[0].router.expect_find_route(
- RouteParameters::from_payment_params_and_value(pay_params, amt_msat),
- Ok(route.clone()));
+ let retry_params = RouteParameters::from_payment_params_and_value(pay_params, amt_msat);
+ route.route_params = Some(retry_params.clone());
+ nodes[0].router.expect_find_route(retry_params, Ok(route.clone()));
nodes[0].node.send_payment(payment_hash, RecipientOnionFields::secret_only(payment_secret),
PaymentId(payment_hash.0), route_params, Retry::Attempts(1)).unwrap();
route.paths[0].hops[1].fee_msat = amt_msat;
let mut retry_params = RouteParameters::from_payment_params_and_value(second_payment_params, amt_msat);
retry_params.max_total_routing_fee_msat = None;
+ route.route_params = Some(retry_params.clone());
nodes[0].router.expect_find_route(retry_params, Ok(route.clone()));
nodes[0].node.send_payment(payment_hash, RecipientOnionFields::secret_only(payment_secret),
route.paths.remove(0);
let mut retry_params = RouteParameters::from_payment_params_and_value(second_payment_params, amt_msat / 2);
retry_params.max_total_routing_fee_msat = None;
- route.route_params.as_mut().unwrap().final_value_msat = amt_msat / 2;
+ route.route_params = Some(retry_params.clone());
nodes[0].router.expect_find_route(retry_params, Ok(route.clone()));
nodes[0].node.send_payment(payment_hash, RecipientOnionFields::secret_only(payment_secret),
// from here on out, the retry `RouteParameters` amount will be amt/1000
route_params.final_value_msat /= 1000;
- route.route_params.as_mut().unwrap().final_value_msat /= 1000;
+ route.route_params = Some(route_params.clone());
route.paths.pop();
let end_time = Instant::now() + Duration::from_secs(1);
new_route_params.payment_params.previously_failed_channels = previously_failed_channels.clone();
new_route_params.max_total_routing_fee_msat.as_mut().map(|m| *m -= 100_000);
route.paths[0].hops[1].short_channel_id += 1;
+ route.route_params = Some(new_route_params.clone());
nodes[0].router.expect_find_route(new_route_params, Ok(route.clone()));
let bs_fail_updates = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
create_announced_chan_between_nodes(&nodes, 2, 3);
let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash!(nodes[3]);
- let mut route_params = RouteParameters::from_payment_params_and_value(
- PaymentParameters::from_node_id(nodes[3].node.get_our_node_id(), TEST_FINAL_CLTV)
- .with_bolt11_features(nodes[1].node.invoice_features()).unwrap(),
- 10_000_000);
+ let payment_params = PaymentParameters::from_node_id(nodes[3].node.get_our_node_id(), TEST_FINAL_CLTV)
+ .with_bolt11_features(nodes[1].node.bolt11_invoice_features()).unwrap();
+ let mut route_params = RouteParameters::from_payment_params_and_value(payment_params, 10_000_000);
let mut route = nodes[0].router.find_route(&nodes[0].node.get_our_node_id(), &route_params,
None, nodes[0].node.compute_inflight_htlcs()).unwrap();
// Make sure the route is ordered as the B->D path before C->D
send_payment(&nodes[2], &vec!(&nodes[1])[..], 1_500_000);
let amt_msat = 1_000_000;
- let (route, payment_hash, payment_preimage, payment_secret) =
+ let (mut route, payment_hash, payment_preimage, payment_secret) =
get_route_and_payment_hash!(nodes[0], nodes[2], amt_msat);
// Initiate the payment
// Retry the payment and make sure it succeeds
route_params.payment_params.previously_failed_channels.push(chan_2_update.contents.short_channel_id);
+ route.route_params = Some(route_params.clone());
nodes[0].router.expect_find_route(route_params, Ok(route));
nodes[0].node.process_pending_htlc_forwards();
check_added_monitors!(nodes[0], 1);
let chan_2_3 = create_announced_chan_between_nodes_with_value(&nodes, 2, 3, 100_000, 0);
let payment_params = PaymentParameters::from_node_id(nodes[3].node.get_our_node_id(), TEST_FINAL_CLTV)
- .with_bolt11_features(nodes[3].node.invoice_features()).unwrap();
+ .with_bolt11_features(nodes[3].node.bolt11_invoice_features()).unwrap();
let mut route = get_route!(nodes[0], payment_params, 15_000_000).unwrap();
assert_eq!(route.paths.len(), 2);
route.paths.sort_by(|path_a, _| {
let payment_metadata = vec![44, 49, 52, 142];
let payment_params = PaymentParameters::from_node_id(nodes[3].node.get_our_node_id(), TEST_FINAL_CLTV)
- .with_bolt11_features(nodes[1].node.invoice_features()).unwrap();
+ .with_bolt11_features(nodes[1].node.bolt11_invoice_features()).unwrap();
let mut route_params = RouteParameters::from_payment_params_and_value(payment_params, amt_msat);
// Send the MPP payment, delivering the updated commitment state to nodes[1].
do_test_payment_metadata_consistency(false, true);
do_test_payment_metadata_consistency(false, false);
}
+
+#[test]
+fn test_htlc_forward_considers_anchor_outputs_value() {
+ // Tests that:
+ //
+ // 1) Forwarding nodes don't forward HTLCs that would cause their balance to dip below the
+ // reserve when considering the value of anchor outputs.
+ //
+ // 2) Recipients of `update_add_htlc` properly reject HTLCs that would cause the initiator's
+ // balance to dip below the reserve when considering the value of anchor outputs.
+ let mut config = test_default_channel_config();
+ config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;
+ config.manually_accept_inbound_channels = true;
+ config.channel_config.forwarding_fee_base_msat = 0;
+ config.channel_config.forwarding_fee_proportional_millionths = 0;
+
+ // Set up a test network of three nodes that replicates a production failure leading to the
+ // discovery of this bug.
+ 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(config), Some(config), Some(config)]);
+ let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
+
+ const CHAN_AMT: u64 = 1_000_000;
+ const PUSH_MSAT: u64 = 900_000_000;
+ create_announced_chan_between_nodes_with_value(&nodes, 0, 1, CHAN_AMT, 500_000_000);
+ let (_, _, chan_id_2, _) = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, CHAN_AMT, PUSH_MSAT);
+
+ let channel_reserve_msat = get_holder_selected_channel_reserve_satoshis(CHAN_AMT, &config) * 1000;
+ let commitment_fee_msat = commit_tx_fee_msat(
+ *nodes[1].fee_estimator.sat_per_kw.lock().unwrap(), 2, &ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies()
+ );
+ let anchor_outpus_value_msat = ANCHOR_OUTPUT_VALUE_SATOSHI * 2 * 1000;
+ let sendable_balance_msat = CHAN_AMT * 1000 - PUSH_MSAT - channel_reserve_msat - commitment_fee_msat - anchor_outpus_value_msat;
+ let channel_details = nodes[1].node.list_channels().into_iter().find(|channel| channel.channel_id == chan_id_2).unwrap();
+ assert!(sendable_balance_msat >= channel_details.next_outbound_htlc_minimum_msat);
+ assert!(sendable_balance_msat <= channel_details.next_outbound_htlc_limit_msat);
+
+ send_payment(&nodes[0], &[&nodes[1], &nodes[2]], sendable_balance_msat);
+ send_payment(&nodes[2], &[&nodes[1], &nodes[0]], sendable_balance_msat);
+
+ // Send out an HTLC that would cause the forwarding node to dip below its reserve when
+ // considering the value of anchor outputs.
+ let (route, payment_hash, _, payment_secret) = get_route_and_payment_hash!(
+ nodes[0], nodes[2], sendable_balance_msat + anchor_outpus_value_msat
+ );
+ nodes[0].node.send_payment_with_route(
+ &route, payment_hash, RecipientOnionFields::secret_only(payment_secret), 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 mut update_add_htlc = if let MessageSendEvent::UpdateHTLCs { updates, .. } = events.pop().unwrap() {
+ nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &updates.update_add_htlcs[0]);
+ check_added_monitors(&nodes[1], 0);
+ commitment_signed_dance!(nodes[1], nodes[0], &updates.commitment_signed, false);
+ updates.update_add_htlcs[0].clone()
+ } else {
+ panic!("Unexpected event");
+ };
+
+ // The forwarding node should reject forwarding it as expected.
+ expect_pending_htlcs_forwardable!(nodes[1]);
+ expect_pending_htlcs_forwardable_and_htlc_handling_failed!(&nodes[1], vec![HTLCDestination::NextHopChannel {
+ node_id: Some(nodes[2].node.get_our_node_id()),
+ channel_id: chan_id_2
+ }]);
+ check_added_monitors(&nodes[1], 1);
+
+ let mut events = nodes[1].node.get_and_clear_pending_msg_events();
+ assert_eq!(events.len(), 1);
+ if let MessageSendEvent::UpdateHTLCs { updates, .. } = events.pop().unwrap() {
+ nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &updates.update_fail_htlcs[0]);
+ check_added_monitors(&nodes[0], 0);
+ commitment_signed_dance!(nodes[0], nodes[1], &updates.commitment_signed, false);
+ } else {
+ panic!("Unexpected event");
+ }
+
+ expect_payment_failed!(nodes[0], payment_hash, false);
+
+ // Assume that the forwarding node did forward it, and make sure the recipient rejects it as an
+ // invalid update and closes the channel.
+ update_add_htlc.channel_id = chan_id_2;
+ nodes[2].node.handle_update_add_htlc(&nodes[1].node.get_our_node_id(), &update_add_htlc);
+ check_closed_event(&nodes[2], 1, ClosureReason::ProcessingError {
+ err: "Remote HTLC add would put them under remote reserve value".to_owned()
+ }, false, &[nodes[1].node.get_our_node_id()], 1_000_000);
+ check_closed_broadcast(&nodes[2], 1, true);
+ check_added_monitors(&nodes[2], 1);
+}
use bitcoin::secp256k1::{self, Secp256k1, SecretKey, PublicKey};
use crate::sign::{KeysManager, NodeSigner, Recipient};
-use crate::events::{MessageSendEvent, MessageSendEventsProvider, OnionMessageProvider};
+use crate::events::{MessageSendEvent, MessageSendEventsProvider};
use crate::ln::ChannelId;
use crate::ln::features::{InitFeatures, NodeFeatures};
use crate::ln::msgs;
use crate::ln::msgs::{ChannelMessageHandler, LightningError, SocketAddress, OnionMessageHandler, RoutingMessageHandler};
+#[cfg(not(c_bindings))]
use crate::ln::channelmanager::{SimpleArcChannelManager, SimpleRefChannelManager};
use crate::util::ser::{VecWriter, Writeable, Writer};
use crate::ln::peer_channel_encryptor::{PeerChannelEncryptor,NextNoiseStep};
use crate::ln::wire;
use crate::ln::wire::{Encode, Type};
-use crate::onion_message::{CustomOnionMessageContents, CustomOnionMessageHandler, OffersMessage, OffersMessageHandler, SimpleArcOnionMessenger, SimpleRefOnionMessenger};
+#[cfg(not(c_bindings))]
+use crate::onion_message::{SimpleArcOnionMessenger, SimpleRefOnionMessenger};
+use crate::onion_message::{CustomOnionMessageHandler, OffersMessage, OffersMessageHandler, OnionMessageContents, PendingOnionMessage};
use crate::routing::gossip::{NetworkGraph, P2PGossipSync, NodeId, NodeAlias};
use crate::util::atomic_counter::AtomicCounter;
use crate::util::logger::Logger;
}
fn processing_queue_high(&self) -> bool { false }
}
-impl OnionMessageProvider for IgnoringMessageHandler {
- fn next_onion_message_for_peer(&self, _peer_node_id: PublicKey) -> Option<msgs::OnionMessage> { None }
-}
impl OnionMessageHandler for IgnoringMessageHandler {
fn handle_onion_message(&self, _their_node_id: &PublicKey, _msg: &msgs::OnionMessage) {}
+ fn next_onion_message_for_peer(&self, _peer_node_id: PublicKey) -> Option<msgs::OnionMessage> { None }
fn peer_connected(&self, _their_node_id: &PublicKey, _init: &msgs::Init, _inbound: bool) -> Result<(), ()> { Ok(()) }
fn peer_disconnected(&self, _their_node_id: &PublicKey) {}
fn provided_node_features(&self) -> NodeFeatures { NodeFeatures::empty() }
fn read_custom_message<R: io::Read>(&self, _msg_type: u64, _buffer: &mut R) -> Result<Option<Infallible>, msgs::DecodeError> where Self: Sized {
Ok(None)
}
+ fn release_pending_custom_messages(&self) -> Vec<PendingOnionMessage<Infallible>> {
+ vec![]
+ }
}
-impl CustomOnionMessageContents for Infallible {
+impl OnionMessageContents for Infallible {
fn tlv_type(&self) -> u64 { unreachable!(); }
}
features
}
- fn get_genesis_hashes(&self) -> Option<Vec<ChainHash>> {
+ fn get_chain_hashes(&self) -> Option<Vec<ChainHash>> {
// We don't enforce any chains upon peer connection for `ErroringMessageHandler` and leave it up
// to users of `ErroringMessageHandler` to make decisions on network compatiblility.
// There's not really any way to pull in specific networks here, and hardcoding can cause breakages.
/// SimpleRefPeerManager is the more appropriate type. Defining these type aliases prevents
/// issues such as overly long function definitions.
///
-/// This is not exported to bindings users as `Arc`s don't make sense in bindings.
+/// This is not exported to bindings users as type aliases aren't supported in most languages.
+#[cfg(not(c_bindings))]
pub type SimpleArcPeerManager<SD, M, T, F, C, L> = PeerManager<
SD,
Arc<SimpleArcChannelManager<M, T, F, L>>,
Arc<P2PGossipSync<Arc<NetworkGraph<Arc<L>>>, C, Arc<L>>>,
- Arc<SimpleArcOnionMessenger<L>>,
+ Arc<SimpleArcOnionMessenger<M, T, F, L>>,
Arc<L>,
IgnoringMessageHandler,
Arc<KeysManager>
/// But if this is not necessary, using a reference is more efficient. Defining these type aliases
/// helps with issues such as long function definitions.
///
-/// This is not exported to bindings users as general type aliases don't make sense in bindings.
+/// This is not exported to bindings users as type aliases aren't supported in most languages.
+#[cfg(not(c_bindings))]
pub type SimpleRefPeerManager<
- 'a, 'b, 'c, 'd, 'e, 'f, 'logger, 'h, 'i, 'j, 'graph, SD, M, T, F, C, L
+ 'a, 'b, 'c, 'd, 'e, 'f, 'logger, 'h, 'i, 'j, 'graph, 'k, SD, M, T, F, C, L
> = PeerManager<
SD,
&'j SimpleRefChannelManager<'a, 'b, 'c, 'd, 'e, 'graph, 'logger, 'i, M, T, F, L>,
&'f P2PGossipSync<&'graph NetworkGraph<&'logger L>, C, &'logger L>,
- &'h SimpleRefOnionMessenger<'logger, 'i, 'j, L>,
+ &'h SimpleRefOnionMessenger<'a, 'b, 'c, 'd, 'e, 'graph, 'logger, 'i, 'j, 'k, M, T, F, L>,
&'logger L,
IgnoringMessageHandler,
&'c KeysManager
peer.set_their_node_id(their_node_id);
insert_node_id!();
let features = self.init_features(&their_node_id);
- let networks = self.message_handler.chan_handler.get_genesis_hashes();
+ let networks = self.message_handler.chan_handler.get_chain_hashes();
let resp = msgs::Init { features, networks, remote_network_address: filter_addresses(peer.their_socket_address.clone()) };
self.enqueue_message(peer, &resp);
peer.awaiting_pong_timer_tick_intervals = 0;
peer.set_their_node_id(their_node_id);
insert_node_id!();
let features = self.init_features(&their_node_id);
- let networks = self.message_handler.chan_handler.get_genesis_hashes();
+ let networks = self.message_handler.chan_handler.get_chain_hashes();
let resp = msgs::Init { features, networks, remote_network_address: filter_addresses(peer.their_socket_address.clone()) };
self.enqueue_message(peer, &resp);
peer.awaiting_pong_timer_tick_intervals = 0;
if let wire::Message::Init(msg) = message {
// Check if we have any compatible chains if the `networks` field is specified.
if let Some(networks) = &msg.networks {
- if let Some(our_chains) = self.message_handler.chan_handler.get_genesis_hashes() {
+ if let Some(our_chains) = self.message_handler.chan_handler.get_chain_hashes() {
let mut have_compatible_chains = false;
'our_chains: for our_chain in our_chains.iter() {
for their_chain in networks {
let flush_read_disabled = self.gossip_processing_backlog_lifted.swap(false, Ordering::Relaxed);
let mut peers_to_disconnect = HashMap::new();
- let mut events_generated = self.message_handler.chan_handler.get_and_clear_pending_msg_events();
- events_generated.append(&mut self.message_handler.route_handler.get_and_clear_pending_msg_events());
{
- // TODO: There are some DoS attacks here where you can flood someone's outbound send
- // buffer by doing things like announcing channels on another node. We should be willing to
- // drop optional-ish messages when send buffers get full!
-
let peers_lock = self.peers.read().unwrap();
+
+ let mut events_generated = self.message_handler.chan_handler.get_and_clear_pending_msg_events();
+ events_generated.append(&mut self.message_handler.route_handler.get_and_clear_pending_msg_events());
+
let peers = &*peers_lock;
macro_rules! get_peer_for_forwarding {
($node_id: expr) => {
use crate::ln::functional_test_utils::*;
-use bitcoin::blockdata::constants::genesis_block;
+use bitcoin::blockdata::constants::ChainHash;
use bitcoin::network::constants::Network;
#[test]
}]);
let last_hops = vec![route_hint];
let payment_params = PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), TEST_FINAL_CLTV)
- .with_bolt11_features(nodes[2].node.invoice_features()).unwrap()
+ .with_bolt11_features(nodes[2].node.bolt11_invoice_features()).unwrap()
.with_route_hints(last_hops).unwrap();
let (route, our_payment_hash, our_payment_preimage, our_payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[2], payment_params, 10_000);
htlc_minimum_msat: None,
}])];
let payment_params = PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), 42)
- .with_bolt11_features(nodes[2].node.invoice_features()).unwrap()
+ .with_bolt11_features(nodes[2].node.bolt11_invoice_features()).unwrap()
.with_route_hints(hop_hints).unwrap();
let (route, payment_hash, payment_preimage, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[2], payment_params, 100_000);
assert_eq!(route.paths[0].hops[1].short_channel_id, last_hop[0].inbound_scid_alias.unwrap());
htlc_minimum_msat: None,
}])];
let payment_params = PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), 42)
- .with_bolt11_features(nodes[2].node.invoice_features()).unwrap()
+ .with_bolt11_features(nodes[2].node.bolt11_invoice_features()).unwrap()
.with_route_hints(hop_hints.clone()).unwrap();
let (route, payment_hash, payment_preimage, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[2], payment_params, 100_000);
assert_eq!(route.paths[0].hops[1].short_channel_id, last_hop[0].inbound_scid_alias.unwrap());
hop_hints[0].0[0].short_channel_id = last_hop[0].short_channel_id.unwrap();
let payment_params_2 = PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), 42)
- .with_bolt11_features(nodes[2].node.invoice_features()).unwrap()
+ .with_bolt11_features(nodes[2].node.bolt11_invoice_features()).unwrap()
.with_route_hints(hop_hints).unwrap();
let (route_2, payment_hash_2, _, payment_secret_2) = get_route_and_payment_hash!(nodes[0], nodes[2], payment_params_2, 100_000);
assert_eq!(route_2.paths[0].hops[1].short_channel_id, last_hop[0].short_channel_id.unwrap());
htlc_minimum_msat: None,
}])];
let payment_params = PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), 42)
- .with_bolt11_features(nodes[2].node.invoice_features()).unwrap()
+ .with_bolt11_features(nodes[2].node.bolt11_invoice_features()).unwrap()
.with_route_hints(hop_hints).unwrap();
let (mut route, payment_hash, _, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[2], payment_params, 10_000);
assert_eq!(route.paths[0].hops[1].short_channel_id, nodes[2].node.list_usable_channels()[0].inbound_scid_alias.unwrap());
// Build the expected channel update
let contents = msgs::UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: last_hop[0].inbound_scid_alias.unwrap(),
timestamp: 21,
flags: 1,
if let MessageSendEvent::BroadcastChannelUpdate { .. } = msg {
} else if let MessageSendEvent::HandleError { ref action, .. } = msg {
match action {
- &ErrorAction::SendErrorMessage { ref msg } => {
- assert_eq!(msg.data, "Channel force-closed");
+ &ErrorAction::DisconnectPeer { ref msg } => {
+ assert_eq!(msg.as_ref().unwrap().data, "Channel force-closed");
},
_ => panic!("Unexpected event!"),
}
nodes[0].node.handle_channel_reestablish(&nodes[1].node.get_our_node_id(), &retry_reestablish[0]);
let mut err_msgs_0 = Vec::with_capacity(1);
- for msg in nodes[0].node.get_and_clear_pending_msg_events() {
- if let MessageSendEvent::HandleError { ref action, .. } = msg {
- match action {
- &ErrorAction::SendErrorMessage { ref msg } => {
- assert_eq!(msg.data, format!("Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}", &nodes[1].node.get_our_node_id()));
- err_msgs_0.push(msg.clone());
- },
- _ => panic!("Unexpected event!"),
- }
- } else {
- panic!("Unexpected event!");
+ if let MessageSendEvent::HandleError { ref action, .. } = nodes[0].node.get_and_clear_pending_msg_events()[1] {
+ match action {
+ &ErrorAction::SendErrorMessage { ref msg } => {
+ assert_eq!(msg.data, format!("Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}", &nodes[1].node.get_our_node_id()));
+ err_msgs_0.push(msg.clone());
+ },
+ _ => panic!("Unexpected event!"),
}
+ } else {
+ panic!("Unexpected event!");
}
assert_eq!(err_msgs_0.len(), 1);
nodes[1].node.handle_error(&nodes[0].node.get_our_node_id(), &err_msgs_0[0]);
let (_, payment_hash, payment_secret) = get_payment_preimage_hash!(nodes[0]);
- let payment_params_1 = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), TEST_FINAL_CLTV).with_bolt11_features(nodes[1].node.invoice_features()).unwrap();
+ let payment_params_1 = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), TEST_FINAL_CLTV)
+ .with_bolt11_features(nodes[1].node.bolt11_invoice_features()).unwrap();
let route_params = RouteParameters::from_payment_params_and_value(payment_params_1, 100_000);
let route_1 = get_route(&nodes[0].node.get_our_node_id(), &route_params,
&nodes[0].network_graph.read_only(), None, &logger, &scorer, &Default::default(), &random_seed_bytes).unwrap();
- let payment_params_2 = PaymentParameters::from_node_id(nodes[0].node.get_our_node_id(),
- TEST_FINAL_CLTV).with_bolt11_features(nodes[0].node.invoice_features()).unwrap();
+ let payment_params_2 = PaymentParameters::from_node_id(nodes[0].node.get_our_node_id(), TEST_FINAL_CLTV)
+ .with_bolt11_features(nodes[0].node.bolt11_invoice_features()).unwrap();
let route_params = RouteParameters::from_payment_params_and_value(payment_params_2, 100_000);
let route_2 = get_route(&nodes[1].node.get_our_node_id(), &route_params,
&nodes[1].network_graph.read_only(), None, &logger, &scorer, &Default::default(), &random_seed_bytes).unwrap();
nodes[0].node.handle_channel_reestablish(&nodes[1].node.get_our_node_id(), &node_1_2nd_reestablish);
let msg_events = nodes[0].node.get_and_clear_pending_msg_events();
- assert_eq!(msg_events.len(), 1);
- if let MessageSendEvent::HandleError { ref action, .. } = msg_events[0] {
+ assert_eq!(msg_events.len(), 2);
+ if let MessageSendEvent::HandleError { ref action, .. } = msg_events[1] {
match action {
&ErrorAction::SendErrorMessage { ref msg } => {
nodes[1].node.handle_error(&nodes[0].node.get_our_node_id(), &msg);
invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>,
created_at: Duration, payment_hash: PaymentHash
) -> Result<Self, Bolt12SemanticError> {
- let amount_msats = Self::check_amount_msats(invoice_request)?;
+ let amount_msats = Self::amount_msats(invoice_request)?;
let signing_pubkey = invoice_request.contents.inner.offer.signing_pubkey();
let contents = InvoiceContents::ForOffer {
invoice_request: invoice_request.contents.clone(),
invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>,
created_at: Duration, payment_hash: PaymentHash, keys: KeyPair
) -> Result<Self, Bolt12SemanticError> {
- let amount_msats = Self::check_amount_msats(invoice_request)?;
+ let amount_msats = Self::amount_msats(invoice_request)?;
let signing_pubkey = invoice_request.contents.inner.offer.signing_pubkey();
let contents = InvoiceContents::ForOffer {
invoice_request: invoice_request.contents.clone(),
}
impl<'a, S: SigningPubkeyStrategy> InvoiceBuilder<'a, S> {
- fn check_amount_msats(invoice_request: &InvoiceRequest) -> Result<u64, Bolt12SemanticError> {
+ pub(crate) fn amount_msats(
+ invoice_request: &InvoiceRequest
+ ) -> Result<u64, Bolt12SemanticError> {
match invoice_request.amount_msats() {
Some(amount_msats) => Ok(amount_msats),
None => match invoice_request.contents.inner.offer.amount() {
}
}
+ #[cfg(not(feature = "std"))] {
+ if self.invoice.is_offer_or_refund_expired_no_std(self.invoice.created_at()) {
+ return Err(Bolt12SemanticError::AlreadyExpired);
+ }
+ }
+
let InvoiceBuilder { invreq_bytes, invoice, .. } = self;
Ok(UnsignedBolt12Invoice::new(invreq_bytes, invoice))
}
}
}
+ #[cfg(not(feature = "std"))] {
+ if self.invoice.is_offer_or_refund_expired_no_std(self.invoice.created_at()) {
+ return Err(Bolt12SemanticError::AlreadyExpired);
+ }
+ }
+
let InvoiceBuilder {
invreq_bytes, invoice, signing_pubkey_strategy: DerivedSigningPubkey(keys)
} = self;
}
}
+ #[cfg(not(feature = "std"))]
+ fn is_offer_or_refund_expired_no_std(&self, duration_since_epoch: Duration) -> bool {
+ match self {
+ InvoiceContents::ForOffer { invoice_request, .. } =>
+ invoice_request.inner.offer.is_expired_no_std(duration_since_epoch),
+ InvoiceContents::ForRefund { refund, .. } =>
+ refund.is_expired_no_std(duration_since_epoch),
+ }
+ }
+
fn offer_chains(&self) -> Option<Vec<ChainHash>> {
match self {
InvoiceContents::ForOffer { invoice_request, .. } =>
pub suggested_value: Option<Vec<u8>>,
}
+impl InvoiceError {
+ /// Creates an [`InvoiceError`] with the given message.
+ pub fn from_string(s: String) -> Self {
+ Self {
+ erroneous_field: None,
+ message: UntrustedString(s),
+ }
+ }
+}
+
impl core::fmt::Display for InvoiceError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
self.message.fmt(f)
/// by the offer.
///
/// Successive calls to this method will override the previous setting.
- pub fn chain(mut self, network: Network) -> Result<Self, Bolt12SemanticError> {
- let chain = ChainHash::using_genesis_block(network);
+ pub fn chain(self, network: Network) -> Result<Self, Bolt12SemanticError> {
+ self.chain_hash(ChainHash::using_genesis_block(network))
+ }
+
+ /// Sets the [`InvoiceRequest::chain`] for paying an invoice. If not called, the chain hash of
+ /// [`Network::Bitcoin`] is assumed. Errors if the chain for `network` is not supported by the
+ /// offer.
+ ///
+ /// Successive calls to this method will override the previous setting.
+ pub(crate) fn chain_hash(mut self, chain: ChainHash) -> Result<Self, Bolt12SemanticError> {
if !self.offer.supports_chain(chain) {
return Err(Bolt12SemanticError::UnsupportedChain);
}
//! published as a QR code to be scanned by a customer. The customer uses the offer to request an
//! invoice from the merchant to be paid.
//!
+//! # Example
+//!
//! ```
//! extern crate bitcoin;
//! extern crate core;
//! # Ok(())
//! # }
//! ```
+//!
+//! # Note
+//!
+//! If constructing an [`Offer`] for use with a [`ChannelManager`], use
+//! [`ChannelManager::create_offer_builder`] instead of [`OfferBuilder::new`].
+//!
+//! [`ChannelManager`]: crate::ln::channelmanager::ChannelManager
+//! [`ChannelManager::create_offer_builder`]: crate::ln::channelmanager::ChannelManager::create_offer_builder
use bitcoin::blockdata::constants::ChainHash;
use bitcoin::network::constants::Network;
/// while the offer is valid.
///
/// Use a different pubkey per offer to avoid correlating offers.
+ ///
+ /// # Note
+ ///
+ /// If constructing an [`Offer`] for use with a [`ChannelManager`], use
+ /// [`ChannelManager::create_offer_builder`] instead of [`OfferBuilder::new`].
+ ///
+ /// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager
+ /// [`ChannelManager::create_offer_builder`]: crate::ln::channelmanager::ChannelManager::create_offer_builder
pub fn new(description: String, signing_pubkey: PublicKey) -> Self {
OfferBuilder {
offer: OfferContents {
/// See [`Offer::chains`] on how this relates to the payment currency.
///
/// Successive calls to this method will add another chain hash.
- pub fn chain(mut self, network: Network) -> Self {
+ pub fn chain(self, network: Network) -> Self {
+ self.chain_hash(ChainHash::using_genesis_block(network))
+ }
+
+ /// Adds the [`ChainHash`] to [`Offer::chains`]. If not called, the chain hash of
+ /// [`Network::Bitcoin`] is assumed to be the only one supported.
+ ///
+ /// See [`Offer::chains`] on how this relates to the payment currency.
+ ///
+ /// Successive calls to this method will add another chain hash.
+ pub(crate) fn chain_hash(mut self, chain: ChainHash) -> Self {
let chains = self.offer.chains.get_or_insert_with(Vec::new);
- let chain = ChainHash::using_genesis_block(network);
if !chains.contains(&chain) {
chains.push(chain);
}
self.contents.is_expired()
}
+ /// Whether the offer has expired given the duration since the Unix epoch.
+ pub fn is_expired_no_std(&self, duration_since_epoch: Duration) -> bool {
+ self.contents.is_expired_no_std(duration_since_epoch)
+ }
+
/// Returns whether the given quantity is valid for the offer.
pub fn is_valid_quantity(&self, quantity: u64) -> bool {
self.contents.is_valid_quantity(quantity)
#[cfg(feature = "std")]
pub(super) fn is_expired(&self) -> bool {
- match self.absolute_expiry {
- Some(seconds_from_epoch) => match SystemTime::UNIX_EPOCH.elapsed() {
- Ok(elapsed) => elapsed > seconds_from_epoch,
- Err(_) => false,
- },
- None => false,
- }
+ SystemTime::UNIX_EPOCH
+ .elapsed()
+ .map(|duration_since_epoch| self.is_expired_no_std(duration_since_epoch))
+ .unwrap_or(false)
+ }
+
+ pub(super) fn is_expired_no_std(&self, duration_since_epoch: Duration) -> bool {
+ self.absolute_expiry
+ .map(|absolute_expiry| duration_since_epoch > absolute_expiry)
+ .unwrap_or(false)
}
pub fn issuer(&self) -> Option<PrintableString> {
fn builds_offer_with_absolute_expiry() {
let future_expiry = Duration::from_secs(u64::max_value());
let past_expiry = Duration::from_secs(0);
+ let now = future_expiry - Duration::from_secs(1_000);
let offer = OfferBuilder::new("foo".into(), pubkey(42))
.absolute_expiry(future_expiry)
.unwrap();
#[cfg(feature = "std")]
assert!(!offer.is_expired());
+ assert!(!offer.is_expired_no_std(now));
assert_eq!(offer.absolute_expiry(), Some(future_expiry));
assert_eq!(offer.as_tlv_stream().absolute_expiry, Some(future_expiry.as_secs()));
.unwrap();
#[cfg(feature = "std")]
assert!(offer.is_expired());
+ assert!(offer.is_expired_no_std(now));
assert_eq!(offer.absolute_expiry(), Some(past_expiry));
assert_eq!(offer.as_tlv_stream().absolute_expiry, Some(past_expiry.as_secs()));
}
}
#[cfg(test)]
-mod bech32_tests {
- use super::{Bolt12ParseError, Offer};
- use bitcoin::bech32;
+mod bolt12_tests {
+ use super::{Bolt12ParseError, Bolt12SemanticError, Offer};
use crate::ln::msgs::DecodeError;
- #[test]
- fn encodes_offer_as_bech32_without_checksum() {
- let encoded_offer = "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg";
- let offer = dbg!(encoded_offer.parse::<Offer>().unwrap());
- let reencoded_offer = offer.to_string();
- dbg!(reencoded_offer.parse::<Offer>().unwrap());
- assert_eq!(reencoded_offer, encoded_offer);
- }
-
#[test]
fn parses_bech32_encoded_offers() {
let offers = [
- // BOLT 12 test vectors
- "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
- "l+no1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
- "lno1pqps7sjqpgt+yzm3qv4uxzmtsd3jjqer9wd3hy6tsw3+5k7msjzfpy7nz5yqcn+ygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd+5xvxg",
- "lno1pqps7sjqpgt+ yzm3qv4uxzmtsd3jjqer9wd3hy6tsw3+ 5k7msjzfpy7nz5yqcn+\nygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd+\r\n 5xvxg",
+ // Minimal bolt12 offer
+ "lno1pgx9getnwss8vetrw3hhyuckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
+
+ // for testnet
+ "lno1qgsyxjtl6luzd9t3pr62xr7eemp6awnejusgf6gw45q75vcfqqqqqqq2p32x2um5ypmx2cm5dae8x93pqthvwfzadd7jejes8q9lhc4rvjxd022zv5l44g6qah82ru5rdpnpj",
+
+ // for bitcoin (redundant)
+ "lno1qgsxlc5vp2m0rvmjcxn2y34wv0m5lyc7sdj7zksgn35dvxgqqqqqqqq2p32x2um5ypmx2cm5dae8x93pqthvwfzadd7jejes8q9lhc4rvjxd022zv5l44g6qah82ru5rdpnpj",
+
+ // for bitcoin or liquidv1
+ "lno1qfqpge38tqmzyrdjj3x2qkdr5y80dlfw56ztq6yd9sme995g3gsxqqm0u2xq4dh3kdevrf4zg6hx8a60jv0gxe0ptgyfc6xkryqqqqqqqq9qc4r9wd6zqan9vd6x7unnzcss9mk8y3wkklfvevcrszlmu23kfrxh49px20665dqwmn4p72pksese",
+
+ // with metadata
+ "lno1qsgqqqqqqqqqqqqqqqqqqqqqqqqqqzsv23jhxapqwejkxar0wfe3vggzamrjghtt05kvkvpcp0a79gmy3nt6jsn98ad2xs8de6sl9qmgvcvs",
+
+ // with amount
+ "lno1pqpzwyq2p32x2um5ypmx2cm5dae8x93pqthvwfzadd7jejes8q9lhc4rvjxd022zv5l44g6qah82ru5rdpnpj",
+
+ // with currency
+ "lno1qcp4256ypqpzwyq2p32x2um5ypmx2cm5dae8x93pqthvwfzadd7jejes8q9lhc4rvjxd022zv5l44g6qah82ru5rdpnpj",
+
+ // with expiry
+ "lno1pgx9getnwss8vetrw3hhyucwq3ay997czcss9mk8y3wkklfvevcrszlmu23kfrxh49px20665dqwmn4p72pksese",
+
+ // with issuer
+ "lno1pgx9getnwss8vetrw3hhyucjy358garswvaz7tmzdak8gvfj9ehhyeeqgf85c4p3xgsxjmnyw4ehgunfv4e3vggzamrjghtt05kvkvpcp0a79gmy3nt6jsn98ad2xs8de6sl9qmgvcvs",
+
+ // with quantity
+ "lno1pgx9getnwss8vetrw3hhyuc5qyz3vggzamrjghtt05kvkvpcp0a79gmy3nt6jsn98ad2xs8de6sl9qmgvcvs",
+
+ // with unlimited (or unknown) quantity
+ "lno1pgx9getnwss8vetrw3hhyuc5qqtzzqhwcuj966ma9n9nqwqtl032xeyv6755yeflt235pmww58egx6rxry",
+
+ // with single quantity (weird but valid)
+ "lno1pgx9getnwss8vetrw3hhyuc5qyq3vggzamrjghtt05kvkvpcp0a79gmy3nt6jsn98ad2xs8de6sl9qmgvcvs",
+
+ // with feature
+ "lno1pgx9getnwss8vetrw3hhyucvp5yqqqqqqqqqqqqqqqqqqqqkyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
+
+ // with blinded path via Bob (0x424242...), blinding 020202...
+ "lno1pgx9getnwss8vetrw3hhyucs5ypjgef743p5fzqq9nqxh0ah7y87rzv3ud0eleps9kl2d5348hq2k8qzqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgqpqqqqqqqqqqqqqqqqqqqqqqqqqqqzqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqqzq3zyg3zyg3zyg3vggzamrjghtt05kvkvpcp0a79gmy3nt6jsn98ad2xs8de6sl9qmgvcvs",
+
+ // ... and with second blinded path via Carol (0x434343...), blinding 020202...
+ "lno1pgx9getnwss8vetrw3hhyucsl5q5yqeyv5l2cs6y3qqzesrth7mlzrlp3xg7xhulusczm04x6g6nms9trspqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqqsqqqqqqqqqqqqqqqqqqqqqqqqqqpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsqpqg3zyg3zyg3zygz0uc7h32x9s0aecdhxlk075kn046aafpuuyw8f5j652t3vha2yqrsyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsqzqqqqqqqqqqqqqqqqqqqqqqqqqqqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqqyzyg3zyg3zyg3zzcss9mk8y3wkklfvevcrszlmu23kfrxh49px20665dqwmn4p72pksese",
+
+ // unknown odd field
+ "lno1pgx9getnwss8vetrw3hhyuckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxfppf5x2mrvdamk7unvvs",
];
for encoded_offer in &offers {
if let Err(e) = encoded_offer.parse::<Offer>() {
}
#[test]
- fn fails_parsing_bech32_encoded_offers_with_invalid_continuations() {
- let offers = [
- // BOLT 12 test vectors
- "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg+",
- "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg+ ",
- "+lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
- "+ lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
- "ln++o1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
- ];
- for encoded_offer in &offers {
- match encoded_offer.parse::<Offer>() {
- Ok(_) => panic!("Valid offer: {}", encoded_offer),
- Err(e) => assert_eq!(e, Bolt12ParseError::InvalidContinuation),
- }
- }
+ fn fails_parsing_bech32_encoded_offers() {
+ // Malformed: fields out of order
+ assert_eq!(
+ "lno1zcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszpgz5znzfgdzs".parse::<Offer>(),
+ Err(Bolt12ParseError::Decode(DecodeError::InvalidValue)),
+ );
- }
+ // Malformed: unknown even TLV type 78
+ assert_eq!(
+ "lno1pgz5znzfgdz3vggzqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpysgr0u2xq4dh3kdevrf4zg6hx8a60jv0gxe0ptgyfc6xkryqqqqqqqq".parse::<Offer>(),
+ Err(Bolt12ParseError::Decode(DecodeError::UnknownRequiredFeature)),
+ );
- #[test]
- fn fails_parsing_bech32_encoded_offer_with_invalid_hrp() {
- let encoded_offer = "lni1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg";
- match encoded_offer.parse::<Offer>() {
- Ok(_) => panic!("Valid offer: {}", encoded_offer),
- Err(e) => assert_eq!(e, Bolt12ParseError::InvalidBech32Hrp),
- }
- }
+ // Malformed: empty
+ assert_eq!(
+ "lno1".parse::<Offer>(),
+ Err(Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingDescription)),
+ );
- #[test]
- fn fails_parsing_bech32_encoded_offer_with_invalid_bech32_data() {
- let encoded_offer = "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxo";
- match encoded_offer.parse::<Offer>() {
- Ok(_) => panic!("Valid offer: {}", encoded_offer),
- Err(e) => assert_eq!(e, Bolt12ParseError::Bech32(bech32::Error::InvalidChar('o'))),
- }
- }
+ // Malformed: truncated at type
+ assert_eq!(
+ "lno1pg".parse::<Offer>(),
+ Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
+ );
- #[test]
- fn fails_parsing_bech32_encoded_offer_with_invalid_tlv_data() {
- let encoded_offer = "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxgqqqqq";
- match encoded_offer.parse::<Offer>() {
- Ok(_) => panic!("Valid offer: {}", encoded_offer),
- Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::InvalidValue)),
- }
+ // Malformed: truncated in length
+ assert_eq!(
+ "lno1pt7s".parse::<Offer>(),
+ Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
+ );
+
+ // Malformed: truncated after length
+ assert_eq!(
+ "lno1pgpq".parse::<Offer>(),
+ Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
+ );
+
+ // Malformed: truncated in description
+ assert_eq!(
+ "lno1pgpyz".parse::<Offer>(),
+ Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
+ );
+
+ // Malformed: invalid offer_chains length
+ assert_eq!(
+ "lno1qgqszzs9g9xyjs69zcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsz".parse::<Offer>(),
+ Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
+ );
+
+ // Malformed: truncated currency UTF-8
+ assert_eq!(
+ "lno1qcqcqzs9g9xyjs69zcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsz".parse::<Offer>(),
+ Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
+ );
+
+ // Malformed: invalid currency UTF-8
+ assert_eq!(
+ "lno1qcpgqsg2q4q5cj2rg5tzzqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqg".parse::<Offer>(),
+ Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
+ );
+
+ // Malformed: truncated description UTF-8
+ assert_eq!(
+ "lno1pgqcq93pqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqy".parse::<Offer>(),
+ Err(Bolt12ParseError::Decode(DecodeError::InvalidValue)),
+ );
+
+ // Malformed: invalid description UTF-8
+ assert_eq!(
+ "lno1pgpgqsgkyypqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqs".parse::<Offer>(),
+ Err(Bolt12ParseError::Decode(DecodeError::InvalidValue)),
+ );
+
+ // Malformed: truncated offer_paths
+ assert_eq!(
+ "lno1pgz5znzfgdz3qqgpzcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsz".parse::<Offer>(),
+ Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
+ );
+
+ // Malformed: zero num_hops in blinded_path
+ assert_eq!(
+ "lno1pgz5znzfgdz3qqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsqzcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsz".parse::<Offer>(),
+ Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
+ );
+
+ // Malformed: truncated onionmsg_hop in blinded_path
+ assert_eq!(
+ "lno1pgz5znzfgdz3qqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqspqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqgkyypqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqs".parse::<Offer>(),
+ Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
+ );
+
+ // Malformed: bad first_node_id in blinded_path
+ assert_eq!(
+ "lno1pgz5znzfgdz3qqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqspqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqgqzcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsz".parse::<Offer>(),
+ Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
+ );
+
+ // Malformed: bad blinding in blinded_path
+ assert_eq!(
+ "lno1pgz5znzfgdz3qqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcpqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqgqzcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsz".parse::<Offer>(),
+ Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
+ );
+
+ // Malformed: bad blinded_node_id in onionmsg_hop
+ assert_eq!(
+ "lno1pgz5znzfgdz3qqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqspqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqgqzcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsz".parse::<Offer>(),
+ Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
+ );
+
+ // Malformed: truncated issuer UTF-8
+ assert_eq!(
+ "lno1pgz5znzfgdz3yqvqzcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsz".parse::<Offer>(),
+ Err(Bolt12ParseError::Decode(DecodeError::InvalidValue)),
+ );
+
+ // Malformed: invalid issuer UTF-8
+ assert_eq!(
+ "lno1pgz5znzfgdz3yq5qgytzzqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqg".parse::<Offer>(),
+ Err(Bolt12ParseError::Decode(DecodeError::InvalidValue)),
+ );
+
+ // Malformed: invalid offer_node_id
+ assert_eq!(
+ "lno1pgz5znzfgdz3vggzqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvps".parse::<Offer>(),
+ Err(Bolt12ParseError::Decode(DecodeError::InvalidValue)),
+ );
+
+ // Contains type >= 80
+ assert_eq!(
+ "lno1pgz5znzfgdz3vggzqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgp9qgr0u2xq4dh3kdevrf4zg6hx8a60jv0gxe0ptgyfc6xkryqqqqqqqq".parse::<Offer>(),
+ Err(Bolt12ParseError::Decode(DecodeError::InvalidValue)),
+ );
+
+ // TODO: Resolved in spec https://github.com/lightning/bolts/pull/798/files#r1334851959
+ // Contains unknown feature 22
+ assert!(
+ "lno1pgx9getnwss8vetrw3hhyucvqdqqqqqkyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg".parse::<Offer>().is_ok()
+ );
+
+ // Missing offer_description
+ assert_eq!(
+ "lno1zcss9mk8y3wkklfvevcrszlmu23kfrxh49px20665dqwmn4p72pksese".parse::<Offer>(),
+ Err(Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingDescription)),
+ );
+
+ // Missing offer_node_id"
+ assert_eq!(
+ "lno1pgx9getnwss8vetrw3hhyuc".parse::<Offer>(),
+ Err(Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingSigningPubkey)),
+ );
}
}
MissingPayerMetadata,
/// A payer id was expected but was missing.
MissingPayerId,
+ /// The payment id for a refund or request is already in use.
+ DuplicatePaymentId,
/// Blinded paths were expected but were missing.
MissingPaths,
/// The blinded payinfo given does not match the number of blinded path hops.
Self::InvalidSignature(error)
}
}
+
+#[cfg(test)]
+mod bolt12_tests {
+ use super::Bolt12ParseError;
+ use crate::offers::offer::Offer;
+
+ #[test]
+ fn encodes_offer_as_bech32_without_checksum() {
+ let encoded_offer = "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg";
+ let offer = dbg!(encoded_offer.parse::<Offer>().unwrap());
+ let reencoded_offer = offer.to_string();
+ dbg!(reencoded_offer.parse::<Offer>().unwrap());
+ assert_eq!(reencoded_offer, encoded_offer);
+ }
+
+ #[test]
+ fn parses_bech32_encoded_offers() {
+ let offers = [
+ // A complete string is valid
+ "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
+
+ // + can join anywhere
+ "l+no1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
+
+ // Multiple + can join
+ "lno1pqps7sjqpgt+yzm3qv4uxzmtsd3jjqer9wd3hy6tsw3+5k7msjzfpy7nz5yqcn+ygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd+5xvxg",
+
+ // + can be followed by whitespace
+ "lno1pqps7sjqpgt+ yzm3qv4uxzmtsd3jjqer9wd3hy6tsw3+ 5k7msjzfpy7nz5yqcn+\nygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd+\r\n 5xvxg",
+ ];
+ for encoded_offer in &offers {
+ if let Err(e) = encoded_offer.parse::<Offer>() {
+ panic!("Invalid offer ({:?}): {}", e, encoded_offer);
+ }
+ }
+ }
+
+ #[test]
+ fn fails_parsing_bech32_encoded_offers_with_invalid_continuations() {
+ let offers = [
+ // + must be surrounded by bech32 characters
+ "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg+",
+ "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg+ ",
+ "+lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
+ "+ lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
+ "ln++o1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
+ ];
+ for encoded_offer in &offers {
+ match encoded_offer.parse::<Offer>() {
+ Ok(_) => panic!("Valid offer: {}", encoded_offer),
+ Err(e) => assert_eq!(e, Bolt12ParseError::InvalidContinuation),
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::Bolt12ParseError;
+ use bitcoin::bech32;
+ use crate::ln::msgs::DecodeError;
+ use crate::offers::offer::Offer;
+
+ #[test]
+ fn fails_parsing_bech32_encoded_offer_with_invalid_hrp() {
+ let encoded_offer = "lni1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg";
+ match encoded_offer.parse::<Offer>() {
+ Ok(_) => panic!("Valid offer: {}", encoded_offer),
+ Err(e) => assert_eq!(e, Bolt12ParseError::InvalidBech32Hrp),
+ }
+ }
+
+ #[test]
+ fn fails_parsing_bech32_encoded_offer_with_invalid_bech32_data() {
+ let encoded_offer = "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxo";
+ match encoded_offer.parse::<Offer>() {
+ Ok(_) => panic!("Valid offer: {}", encoded_offer),
+ Err(e) => assert_eq!(e, Bolt12ParseError::Bech32(bech32::Error::InvalidChar('o'))),
+ }
+ }
+
+ #[test]
+ fn fails_parsing_bech32_encoded_offer_with_invalid_tlv_data() {
+ let encoded_offer = "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxgqqqqq";
+ match encoded_offer.parse::<Offer>() {
+ Ok(_) => panic!("Valid offer: {}", encoded_offer),
+ Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::InvalidValue)),
+ }
+ }
+}
//! [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
//! [`Offer`]: crate::offers::offer::Offer
//!
+//! # Example
+//!
//! ```
//! extern crate bitcoin;
//! extern crate core;
//! # Ok(())
//! # }
//! ```
+//!
+//! # Note
+//!
+//! If constructing a [`Refund`] for use with a [`ChannelManager`], use
+//! [`ChannelManager::create_refund_builder`] instead of [`RefundBuilder::new`].
+//!
+//! [`ChannelManager`]: crate::ln::channelmanager::ChannelManager
+//! [`ChannelManager::create_refund_builder`]: crate::ln::channelmanager::ChannelManager::create_refund_builder
use bitcoin::blockdata::constants::ChainHash;
use bitcoin::network::constants::Network;
///
/// Additionally, sets the required [`Refund::description`], [`Refund::payer_metadata`], and
/// [`Refund::amount_msats`].
+ ///
+ /// # Note
+ ///
+ /// If constructing a [`Refund`] for use with a [`ChannelManager`], use
+ /// [`ChannelManager::create_refund_builder`] instead of [`RefundBuilder::new`].
+ ///
+ /// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager
+ /// [`ChannelManager::create_refund_builder`]: crate::ln::channelmanager::ChannelManager::create_refund_builder
pub fn new(
description: String, metadata: Vec<u8>, payer_id: PublicKey, amount_msats: u64
) -> Result<Self, Bolt12SemanticError> {
/// called, [`Network::Bitcoin`] is assumed.
///
/// Successive calls to this method will override the previous setting.
- pub fn chain(mut self, network: Network) -> Self {
- self.refund.chain = Some(ChainHash::using_genesis_block(network));
+ pub fn chain(self, network: Network) -> Self {
+ self.chain_hash(ChainHash::using_genesis_block(network))
+ }
+
+ /// Sets the [`Refund::chain`] of the given [`ChainHash`] for paying an invoice. If not called,
+ /// [`Network::Bitcoin`] is assumed.
+ ///
+ /// Successive calls to this method will override the previous setting.
+ pub(crate) fn chain_hash(mut self, chain: ChainHash) -> Self {
+ self.refund.chain = Some(chain);
self
}
self.contents.is_expired()
}
+ /// Whether the refund has expired given the duration since the Unix epoch.
+ pub fn is_expired_no_std(&self, duration_since_epoch: Duration) -> bool {
+ self.contents.is_expired_no_std(duration_since_epoch)
+ }
+
/// 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<PrintableString> {
#[cfg(feature = "std")]
pub(super) fn is_expired(&self) -> bool {
- match self.absolute_expiry {
- Some(seconds_from_epoch) => match SystemTime::UNIX_EPOCH.elapsed() {
- Ok(elapsed) => elapsed > seconds_from_epoch,
- Err(_) => false,
- },
- None => false,
- }
+ SystemTime::UNIX_EPOCH
+ .elapsed()
+ .map(|duration_since_epoch| self.is_expired_no_std(duration_since_epoch))
+ .unwrap_or(false)
+ }
+
+ pub(super) fn is_expired_no_std(&self, duration_since_epoch: Duration) -> bool {
+ self.absolute_expiry
+ .map(|absolute_expiry| duration_since_epoch > absolute_expiry)
+ .unwrap_or(false)
}
pub fn issuer(&self) -> Option<PrintableString> {
fn builds_refund_with_absolute_expiry() {
let future_expiry = Duration::from_secs(u64::max_value());
let past_expiry = Duration::from_secs(0);
+ let now = future_expiry - Duration::from_secs(1_000);
let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
.absolute_expiry(future_expiry)
let (_, tlv_stream, _) = refund.as_tlv_stream();
#[cfg(feature = "std")]
assert!(!refund.is_expired());
+ assert!(!refund.is_expired_no_std(now));
assert_eq!(refund.absolute_expiry(), Some(future_expiry));
assert_eq!(tlv_stream.absolute_expiry, Some(future_expiry.as_secs()));
let (_, tlv_stream, _) = refund.as_tlv_stream();
#[cfg(feature = "std")]
assert!(refund.is_expired());
+ assert!(refund.is_expired_no_std(now));
assert_eq!(refund.absolute_expiry(), Some(past_expiry));
assert_eq!(tlv_stream.absolute_expiry, Some(past_expiry.as_secs()));
}
//! Onion message testing and test utilities live here.
use crate::blinded_path::BlindedPath;
-use crate::events::OnionMessageProvider;
use crate::ln::features::InitFeatures;
use crate::ln::msgs::{self, DecodeError, OnionMessageHandler};
use crate::sign::{NodeSigner, Recipient};
use crate::util::ser::{FixedLengthReader, LengthReadable, Writeable, Writer};
use crate::util::test_utils;
-use super::{CustomOnionMessageContents, CustomOnionMessageHandler, Destination, MessageRouter, OffersMessage, OffersMessageHandler, OnionMessageContents, OnionMessagePath, OnionMessenger, SendError};
+use super::{CustomOnionMessageHandler, Destination, MessageRouter, OffersMessage, OffersMessageHandler, OnionMessageContents, OnionMessagePath, OnionMessenger, PendingOnionMessage, SendError};
use bitcoin::network::constants::Network;
use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
const CUSTOM_REQUEST_MESSAGE_CONTENTS: [u8; 32] = [42; 32];
const CUSTOM_RESPONSE_MESSAGE_CONTENTS: [u8; 32] = [43; 32];
-impl CustomOnionMessageContents for TestCustomMessage {
+impl OnionMessageContents for TestCustomMessage {
fn tlv_type(&self) -> u64 {
match self {
TestCustomMessage::Request => CUSTOM_REQUEST_MESSAGE_TYPE,
_ => Ok(None),
}
}
+ fn release_pending_custom_messages(&self) -> Vec<PendingOnionMessage<Self::CustomMessage>> {
+ vec![]
+ }
}
fn create_nodes(num_messengers: u8) -> Vec<MessengerNode> {
}
#[test]
-fn one_hop() {
+fn one_unblinded_hop() {
let nodes = create_nodes(2);
- let test_msg = OnionMessageContents::Custom(TestCustomMessage::Response);
+ let test_msg = TestCustomMessage::Response;
let path = OnionMessagePath {
intermediate_nodes: vec![],
#[test]
fn two_unblinded_hops() {
let nodes = create_nodes(3);
- let test_msg = OnionMessageContents::Custom(TestCustomMessage::Response);
+ let test_msg = TestCustomMessage::Response;
let path = OnionMessagePath {
intermediate_nodes: vec![nodes[1].get_node_pk()],
pass_along_path(&nodes);
}
+#[test]
+fn one_blinded_hop() {
+ let nodes = create_nodes(2);
+ let test_msg = TestCustomMessage::Response;
+
+ let secp_ctx = Secp256k1::new();
+ let blinded_path = BlindedPath::new_for_message(&[nodes[1].get_node_pk()], &*nodes[1].keys_manager, &secp_ctx).unwrap();
+ let path = OnionMessagePath {
+ intermediate_nodes: vec![],
+ destination: Destination::BlindedPath(blinded_path),
+ };
+ nodes[0].messenger.send_onion_message(path, test_msg, None).unwrap();
+ nodes[1].custom_message_handler.expect_message(TestCustomMessage::Response);
+ pass_along_path(&nodes);
+}
+
#[test]
fn two_unblinded_two_blinded() {
let nodes = create_nodes(5);
- let test_msg = OnionMessageContents::Custom(TestCustomMessage::Response);
+ let test_msg = TestCustomMessage::Response;
let secp_ctx = Secp256k1::new();
let blinded_path = BlindedPath::new_for_message(&[nodes[3].get_node_pk(), nodes[4].get_node_pk()], &*nodes[4].keys_manager, &secp_ctx).unwrap();
#[test]
fn three_blinded_hops() {
let nodes = create_nodes(4);
- let test_msg = OnionMessageContents::Custom(TestCustomMessage::Response);
+ let test_msg = TestCustomMessage::Response;
let secp_ctx = Secp256k1::new();
let blinded_path = BlindedPath::new_for_message(&[nodes[1].get_node_pk(), nodes[2].get_node_pk(), nodes[3].get_node_pk()], &*nodes[3].keys_manager, &secp_ctx).unwrap();
fn too_big_packet_error() {
// Make sure we error as expected if a packet is too big to send.
let nodes = create_nodes(2);
- let test_msg = OnionMessageContents::Custom(TestCustomMessage::Response);
+ let test_msg = TestCustomMessage::Response;
let hop_node_id = nodes[1].get_node_pk();
let hops = vec![hop_node_id; 400];
destination: Destination::BlindedPath(blinded_path),
};
- nodes[0].messenger.send_onion_message(path, OnionMessageContents::Custom(test_msg.clone()), None).unwrap();
+ nodes[0].messenger.send_onion_message(path, test_msg.clone(), None).unwrap();
nodes[2].custom_message_handler.expect_message(TestCustomMessage::Response);
pass_along_path(&nodes);
intermediate_nodes: vec![],
destination: Destination::BlindedPath(blinded_path),
};
- nodes[0].messenger.send_onion_message(path, OnionMessageContents::Custom(test_msg), None).unwrap();
+ nodes[0].messenger.send_onion_message(path, test_msg, None).unwrap();
nodes[1].custom_message_handler.expect_message(TestCustomMessage::Response);
nodes.remove(2);
pass_along_path(&nodes);
intermediate_nodes: vec![],
destination: Destination::BlindedPath(blinded_path),
};
- let err = nodes[0].messenger.send_onion_message(path, OnionMessageContents::Custom(test_msg.clone()), None).unwrap_err();
- assert_eq!(err, SendError::TooFewBlindedHops);
-
- // 1 hop
- let mut blinded_path = BlindedPath::new_for_message(&[nodes[1].get_node_pk(), nodes[2].get_node_pk()], &*nodes[2].keys_manager, &secp_ctx).unwrap();
- blinded_path.blinded_hops.remove(0);
- assert_eq!(blinded_path.blinded_hops.len(), 1);
- let path = OnionMessagePath {
- intermediate_nodes: vec![],
- destination: Destination::BlindedPath(blinded_path),
- };
- let err = nodes[0].messenger.send_onion_message(path, OnionMessageContents::Custom(test_msg), None).unwrap_err();
+ let err = nodes[0].messenger.send_onion_message(path, test_msg.clone(), None).unwrap_err();
assert_eq!(err, SendError::TooFewBlindedHops);
}
destination: Destination::Node(nodes[3].get_node_pk()),
};
let reply_path = BlindedPath::new_for_message(&[nodes[2].get_node_pk(), nodes[1].get_node_pk(), nodes[0].get_node_pk()], &*nodes[0].keys_manager, &secp_ctx).unwrap();
- nodes[0].messenger.send_onion_message(path, OnionMessageContents::Custom(test_msg.clone()), Some(reply_path)).unwrap();
+ nodes[0].messenger.send_onion_message(path, test_msg.clone(), Some(reply_path)).unwrap();
nodes[3].custom_message_handler.expect_message(TestCustomMessage::Request);
pass_along_path(&nodes);
// Make sure the last node successfully decoded the reply path.
};
let reply_path = BlindedPath::new_for_message(&[nodes[2].get_node_pk(), nodes[1].get_node_pk(), nodes[0].get_node_pk()], &*nodes[0].keys_manager, &secp_ctx).unwrap();
- nodes[0].messenger.send_onion_message(path, OnionMessageContents::Custom(test_msg), Some(reply_path)).unwrap();
+ nodes[0].messenger.send_onion_message(path, test_msg, Some(reply_path)).unwrap();
nodes[3].custom_message_handler.expect_message(TestCustomMessage::Request);
pass_along_path(&nodes);
let nodes = create_nodes(2);
struct InvalidCustomMessage{}
- impl CustomOnionMessageContents for InvalidCustomMessage {
+ impl OnionMessageContents for InvalidCustomMessage {
fn tlv_type(&self) -> u64 {
// Onion message contents must have a TLV >= 64.
63
fn write<W: Writer>(&self, _w: &mut W) -> Result<(), io::Error> { unreachable!() }
}
- let test_msg = OnionMessageContents::Custom(InvalidCustomMessage {});
+ let test_msg = InvalidCustomMessage {};
let path = OnionMessagePath {
intermediate_nodes: vec![],
destination: Destination::Node(nodes[1].get_node_pk()),
destination: Destination::Node(nodes[1].get_node_pk()),
};
for _ in 0..188 { // Based on MAX_PER_PEER_BUFFER_SIZE in OnionMessenger
- nodes[0].messenger.send_onion_message(path.clone(), OnionMessageContents::Custom(test_msg.clone()), None).unwrap();
+ nodes[0].messenger.send_onion_message(path.clone(), test_msg.clone(), None).unwrap();
}
- let err = nodes[0].messenger.send_onion_message(path, OnionMessageContents::Custom(test_msg), None).unwrap_err();
+ let err = nodes[0].messenger.send_onion_message(path, test_msg, None).unwrap_err();
assert_eq!(err, SendError::BufferFull);
}
intermediate_nodes,
destination: Destination::Node(nodes[num_nodes-1].get_node_pk()),
};
- nodes[0].messenger.send_onion_message(path, OnionMessageContents::Custom(test_msg), None).unwrap();
+ nodes[0].messenger.send_onion_message(path, test_msg, None).unwrap();
nodes[num_nodes-1].custom_message_handler.expect_message(TestCustomMessage::Response);
pass_along_path(&nodes);
}
use crate::blinded_path::message::{advance_path_by_one, ForwardTlvs, ReceiveTlvs};
use crate::blinded_path::utils;
use crate::sign::{EntropySource, KeysManager, NodeSigner, Recipient};
-use crate::events::OnionMessageProvider;
+#[cfg(not(c_bindings))]
+use crate::ln::channelmanager::{SimpleArcChannelManager, SimpleRefChannelManager};
use crate::ln::features::{InitFeatures, NodeFeatures};
-use crate::ln::msgs::{self, OnionMessageHandler};
+use crate::ln::msgs::{self, OnionMessage, OnionMessageHandler};
use crate::ln::onion_utils;
use crate::ln::peer_handler::IgnoringMessageHandler;
-pub use super::packet::{CustomOnionMessageContents, OnionMessageContents};
+pub use super::packet::OnionMessageContents;
+use super::packet::ParsedOnionMessageContents;
use super::offers::OffersMessageHandler;
use super::packet::{BIG_PACKET_HOP_DATA_LEN, ForwardControlTlvs, Packet, Payload, ReceiveControlTlvs, SMALL_PACKET_HOP_DATA_LEN};
use crate::util::logger::Logger;
use crate::util::ser::Writeable;
+use core::fmt;
use core::ops::Deref;
use crate::io;
use crate::sync::{Arc, Mutex};
use crate::prelude::*;
-/// A sender, receiver and forwarder of onion messages. In upcoming releases, this object will be
-/// used to retrieve invoices and fulfill invoice requests from [offers]. Currently, only sending
-/// and receiving custom onion messages is supported.
+/// A sender, receiver and forwarder of [`OnionMessage`]s.
+///
+/// # Handling Messages
+///
+/// `OnionMessenger` implements [`OnionMessageHandler`], making it responsible for either forwarding
+/// messages to peers or delegating to the appropriate handler for the message type. Currently, the
+/// available handlers are:
+/// * [`OffersMessageHandler`], for responding to [`InvoiceRequest`]s and paying [`Bolt12Invoice`]s
+/// * [`CustomOnionMessageHandler`], for handling user-defined message types
+///
+/// # Sending Messages
+///
+/// [`OnionMessage`]s are sent initially using [`OnionMessenger::send_onion_message`]. When handling
+/// a message, the matched handler may return a response message which `OnionMessenger` will send
+/// on its behalf.
///
/// # Example
///
/// # use lightning::blinded_path::BlindedPath;
/// # use lightning::sign::KeysManager;
/// # use lightning::ln::peer_handler::IgnoringMessageHandler;
-/// # use lightning::onion_message::{CustomOnionMessageContents, Destination, MessageRouter, OnionMessageContents, OnionMessagePath, OnionMessenger};
+/// # use lightning::onion_message::{OnionMessageContents, Destination, MessageRouter, OnionMessagePath, OnionMessenger};
/// # use lightning::util::logger::{Logger, Record};
/// # use lightning::util::ser::{Writeable, Writer};
/// # use lightning::io;
/// // Write your custom onion message to `w`
/// }
/// }
-/// impl CustomOnionMessageContents for YourCustomMessage {
+/// impl OnionMessageContents for YourCustomMessage {
/// fn tlv_type(&self) -> u64 {
/// # let your_custom_message_type = 42;
/// your_custom_message_type
/// destination: Destination::Node(destination_node_id),
/// };
/// let reply_path = None;
-/// # let your_custom_message = YourCustomMessage {};
-/// let message = OnionMessageContents::Custom(your_custom_message);
+/// # let message = YourCustomMessage {};
/// onion_messenger.send_onion_message(path, message, reply_path);
///
/// // Create a blinded path to yourself, for someone to send an onion message to.
/// destination: Destination::BlindedPath(blinded_path),
/// };
/// let reply_path = None;
-/// # let your_custom_message = YourCustomMessage {};
-/// let message = OnionMessageContents::Custom(your_custom_message);
+/// # let message = YourCustomMessage {};
/// onion_messenger.send_onion_message(path, message, reply_path);
/// ```
///
-/// [offers]: <https://github.com/lightning/bolts/pull/798>
-/// [`OnionMessenger`]: crate::onion_message::OnionMessenger
+/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
+/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
pub struct OnionMessenger<ES: Deref, NS: Deref, L: Deref, MR: Deref, OMH: Deref, CMH: Deref>
where
ES::Target: EntropySource,
entropy_source: ES,
node_signer: NS,
logger: L,
- pending_messages: Mutex<HashMap<PublicKey, VecDeque<msgs::OnionMessage>>>,
+ pending_messages: Mutex<HashMap<PublicKey, VecDeque<OnionMessage>>>,
secp_ctx: Secp256k1<secp256k1::All>,
message_router: MR,
offers_handler: OMH,
custom_handler: CMH,
}
-/// A trait defining behavior for routing an [`OnionMessage`].
+/// An [`OnionMessage`] for [`OnionMessenger`] to send.
///
-/// [`OnionMessage`]: msgs::OnionMessage
+/// These are obtained when released from [`OnionMessenger`]'s handlers after which they are
+/// enqueued for sending.
+#[cfg(not(c_bindings))]
+pub struct PendingOnionMessage<T: OnionMessageContents> {
+ /// The message contents to send in an [`OnionMessage`].
+ pub contents: T,
+
+ /// The destination of the message.
+ pub destination: Destination,
+
+ /// A reply path to include in the [`OnionMessage`] for a response.
+ pub reply_path: Option<BlindedPath>,
+}
+
+#[cfg(c_bindings)]
+/// An [`OnionMessage`] for [`OnionMessenger`] to send.
+///
+/// These are obtained when released from [`OnionMessenger`]'s handlers after which they are
+/// enqueued for sending.
+pub type PendingOnionMessage<T: OnionMessageContents> = (T, Destination, Option<BlindedPath>);
+
+pub(crate) fn new_pending_onion_message<T: OnionMessageContents>(
+ contents: T, destination: Destination, reply_path: Option<BlindedPath>
+) -> PendingOnionMessage<T> {
+ #[cfg(not(c_bindings))]
+ return PendingOnionMessage { contents, destination, reply_path };
+ #[cfg(c_bindings)]
+ return (contents, destination, reply_path);
+}
+
+/// A trait defining behavior for routing an [`OnionMessage`].
pub trait MessageRouter {
/// Returns a route for sending an [`OnionMessage`] to the given [`Destination`].
- ///
- /// [`OnionMessage`]: msgs::OnionMessage
fn find_path(
&self, sender: PublicKey, peers: Vec<PublicKey>, destination: Destination
) -> Result<OnionMessagePath, ()>;
}
-/// A [`MessageRouter`] that always fails.
+/// A [`MessageRouter`] that can only route to a directly connected [`Destination`].
pub struct DefaultMessageRouter;
impl MessageRouter for DefaultMessageRouter {
fn find_path(
- &self, _sender: PublicKey, _peers: Vec<PublicKey>, _destination: Destination
+ &self, _sender: PublicKey, peers: Vec<PublicKey>, destination: Destination
) -> Result<OnionMessagePath, ()> {
- Err(())
+ if peers.contains(&destination.first_node()) {
+ Ok(OnionMessagePath { intermediate_nodes: vec![], destination })
+ } else {
+ Err(())
+ }
}
}
-/// A path for sending an [`msgs::OnionMessage`].
+/// A path for sending an [`OnionMessage`].
#[derive(Clone)]
pub struct OnionMessagePath {
/// Nodes on the path between the sender and the destination.
Destination::BlindedPath(BlindedPath { blinded_hops, .. }) => blinded_hops.len(),
}
}
+
+ fn first_node(&self) -> PublicKey {
+ match self {
+ Destination::Node(node_id) => *node_id,
+ Destination::BlindedPath(BlindedPath { introduction_node_id: node_id, .. }) => *node_id,
+ }
+ }
}
/// Errors that may occur when [sending an onion message].
/// Because implementations such as Eclair will drop onion messages where the message packet
/// exceeds 32834 bytes, we refuse to send messages where the packet exceeds this size.
TooBigPacket,
- /// The provided [`Destination`] was an invalid [`BlindedPath`], due to having fewer than two
- /// blinded hops.
+ /// The provided [`Destination`] was an invalid [`BlindedPath`] due to not having any blinded
+ /// hops.
TooFewBlindedHops,
/// Our next-hop peer was offline or does not support onion message forwarding.
InvalidFirstHop,
pub trait CustomOnionMessageHandler {
/// The message known to the handler. To support multiple message types, you may want to make this
/// an enum with a variant for each supported message.
- type CustomMessage: CustomOnionMessageContents;
+ type CustomMessage: OnionMessageContents;
/// Called with the custom message that was received, returning a response to send, if any.
+ ///
+ /// The returned [`Self::CustomMessage`], if any, is enqueued to be sent by [`OnionMessenger`].
fn handle_custom_message(&self, msg: Self::CustomMessage) -> Option<Self::CustomMessage>;
/// Read a custom message of type `message_type` from `buffer`, returning `Ok(None)` if the
/// message type is unknown.
fn read_custom_message<R: io::Read>(&self, message_type: u64, buffer: &mut R) -> Result<Option<Self::CustomMessage>, msgs::DecodeError>;
+
+ /// Releases any [`Self::CustomMessage`]s that need to be sent.
+ ///
+ /// Typically, this is used for messages initiating a message flow rather than in response to
+ /// another message. The latter should use the return value of [`Self::handle_custom_message`].
+ #[cfg(not(c_bindings))]
+ fn release_pending_custom_messages(&self) -> Vec<PendingOnionMessage<Self::CustomMessage>>;
+
+ /// Releases any [`Self::CustomMessage`]s that need to be sent.
+ ///
+ /// Typically, this is used for messages initiating a message flow rather than in response to
+ /// another message. The latter should use the return value of [`Self::handle_custom_message`].
+ #[cfg(c_bindings)]
+ fn release_pending_custom_messages(&self) -> Vec<(Self::CustomMessage, Destination, Option<BlindedPath>)>;
}
+/// A processed incoming onion message, containing either a Forward (another onion message)
+/// or a Receive payload with decrypted contents.
+pub enum PeeledOnion<T: OnionMessageContents> {
+ /// Forwarded onion, with the next node id and a new onion
+ Forward(PublicKey, OnionMessage),
+ /// Received onion message, with decrypted contents, path_id, and reply path
+ Receive(ParsedOnionMessageContents<T>, Option<[u8; 32]>, Option<BlindedPath>)
+}
-/// Create an onion message with contents `message` to the destination of `path`.
-/// Returns (introduction_node_id, onion_msg)
-pub fn create_onion_message<ES: Deref, NS: Deref, T: CustomOnionMessageContents>(
+/// Creates an [`OnionMessage`] with the given `contents` for sending to the destination of
+/// `path`.
+///
+/// Returns both the node id of the peer to send the message to and the message itself.
+pub fn create_onion_message<ES: Deref, NS: Deref, T: OnionMessageContents>(
entropy_source: &ES, node_signer: &NS, secp_ctx: &Secp256k1<secp256k1::All>,
- path: OnionMessagePath, message: OnionMessageContents<T>, reply_path: Option<BlindedPath>,
-) -> Result<(PublicKey, msgs::OnionMessage), SendError>
+ path: OnionMessagePath, contents: T, reply_path: Option<BlindedPath>,
+) -> Result<(PublicKey, OnionMessage), SendError>
where
ES::Target: EntropySource,
NS::Target: NodeSigner,
{
let OnionMessagePath { intermediate_nodes, mut destination } = path;
if let Destination::BlindedPath(BlindedPath { ref blinded_hops, .. }) = destination {
- if blinded_hops.len() < 2 {
+ if blinded_hops.is_empty() {
return Err(SendError::TooFewBlindedHops);
}
}
- if message.tlv_type() < 64 { return Err(SendError::InvalidMessage) }
+ if contents.tlv_type() < 64 { return Err(SendError::InvalidMessage) }
// If we are sending straight to a blinded path and we are the introduction node, we need to
// advance the blinded path by 1 hop so the second hop is the new introduction node.
let blinding_secret_bytes = entropy_source.get_secure_random_bytes();
let blinding_secret = SecretKey::from_slice(&blinding_secret_bytes[..]).expect("RNG is busted");
- let (introduction_node_id, blinding_point) = if intermediate_nodes.len() != 0 {
- (intermediate_nodes[0], PublicKey::from_secret_key(&secp_ctx, &blinding_secret))
+ let (first_node_id, blinding_point) = if let Some(first_node_id) = intermediate_nodes.first() {
+ (*first_node_id, PublicKey::from_secret_key(&secp_ctx, &blinding_secret))
} else {
match destination {
Destination::Node(pk) => (pk, PublicKey::from_secret_key(&secp_ctx, &blinding_secret)),
}
};
let (packet_payloads, packet_keys) = packet_payloads_and_keys(
- &secp_ctx, &intermediate_nodes, destination, message, reply_path, &blinding_secret)
+ &secp_ctx, &intermediate_nodes, destination, contents, reply_path, &blinding_secret)
.map_err(|e| SendError::Secp256k1(e))?;
let prng_seed = entropy_source.get_secure_random_bytes();
let onion_routing_packet = construct_onion_message_packet(
packet_payloads, packet_keys, prng_seed).map_err(|()| SendError::TooBigPacket)?;
- Ok((introduction_node_id, msgs::OnionMessage {
+ Ok((first_node_id, OnionMessage {
blinding_point,
onion_routing_packet
}))
}
+/// Decode one layer of an incoming [`OnionMessage`].
+///
+/// Returns either the next layer of the onion for forwarding or the decrypted content for the
+/// receiver.
+pub fn peel_onion_message<NS: Deref, L: Deref, CMH: Deref>(
+ msg: &OnionMessage, secp_ctx: &Secp256k1<secp256k1::All>, node_signer: NS, logger: L,
+ custom_handler: CMH,
+) -> Result<PeeledOnion<<<CMH>::Target as CustomOnionMessageHandler>::CustomMessage>, ()>
+where
+ NS::Target: NodeSigner,
+ L::Target: Logger,
+ CMH::Target: CustomOnionMessageHandler,
+{
+ let control_tlvs_ss = match node_signer.ecdh(Recipient::Node, &msg.blinding_point, None) {
+ Ok(ss) => ss,
+ Err(e) => {
+ log_error!(logger, "Failed to retrieve node secret: {:?}", e);
+ return Err(());
+ }
+ };
+ let onion_decode_ss = {
+ let blinding_factor = {
+ let mut hmac = HmacEngine::<Sha256>::new(b"blinded_node_id");
+ hmac.input(control_tlvs_ss.as_ref());
+ Hmac::from_engine(hmac).into_inner()
+ };
+ match node_signer.ecdh(Recipient::Node, &msg.onion_routing_packet.public_key,
+ Some(&Scalar::from_be_bytes(blinding_factor).unwrap()))
+ {
+ Ok(ss) => ss.secret_bytes(),
+ Err(()) => {
+ log_trace!(logger, "Failed to compute onion packet shared secret");
+ return Err(());
+ }
+ }
+ };
+ match onion_utils::decode_next_untagged_hop(
+ onion_decode_ss, &msg.onion_routing_packet.hop_data[..], msg.onion_routing_packet.hmac,
+ (control_tlvs_ss, custom_handler.deref(), logger.deref())
+ ) {
+ Ok((Payload::Receive::<ParsedOnionMessageContents<<<CMH as Deref>::Target as CustomOnionMessageHandler>::CustomMessage>> {
+ message, control_tlvs: ReceiveControlTlvs::Unblinded(ReceiveTlvs { path_id }), reply_path,
+ }, None)) => {
+ Ok(PeeledOnion::Receive(message, path_id, reply_path))
+ },
+ Ok((Payload::Forward(ForwardControlTlvs::Unblinded(ForwardTlvs {
+ next_node_id, next_blinding_override
+ })), Some((next_hop_hmac, new_packet_bytes)))) => {
+ // TODO: we need to check whether `next_node_id` is our node, in which case this is a dummy
+ // blinded hop and this onion message is destined for us. In this situation, we should keep
+ // unwrapping the onion layers to get to the final payload. Since we don't have the option
+ // of creating blinded paths with dummy hops currently, we should be ok to not handle this
+ // for now.
+ let new_pubkey = match onion_utils::next_hop_pubkey(&secp_ctx, msg.onion_routing_packet.public_key, &onion_decode_ss) {
+ Ok(pk) => pk,
+ Err(e) => {
+ log_trace!(logger, "Failed to compute next hop packet pubkey: {}", e);
+ return Err(())
+ }
+ };
+ let outgoing_packet = Packet {
+ version: 0,
+ public_key: new_pubkey,
+ hop_data: new_packet_bytes,
+ hmac: next_hop_hmac,
+ };
+ let onion_message = OnionMessage {
+ blinding_point: match next_blinding_override {
+ Some(blinding_point) => blinding_point,
+ None => {
+ match onion_utils::next_hop_pubkey(
+ &secp_ctx, msg.blinding_point, control_tlvs_ss.as_ref()
+ ) {
+ Ok(bp) => bp,
+ Err(e) => {
+ log_trace!(logger, "Failed to compute next blinding point: {}", e);
+ return Err(())
+ }
+ }
+ }
+ },
+ onion_routing_packet: outgoing_packet,
+ };
+
+ Ok(PeeledOnion::Forward(next_node_id, onion_message))
+ },
+ Err(e) => {
+ log_trace!(logger, "Errored decoding onion message packet: {:?}", e);
+ Err(())
+ },
+ _ => {
+ log_trace!(logger, "Received bogus onion message packet, either the sender encoded a final hop as a forwarding hop or vice versa");
+ Err(())
+ },
+ }
+}
+
impl<ES: Deref, NS: Deref, L: Deref, MR: Deref, OMH: Deref, CMH: Deref>
OnionMessenger<ES, NS, L, MR, OMH, CMH>
where
}
}
- /// Send an onion message with contents `message` to the destination of `path`.
+ /// Sends an [`OnionMessage`] with the given `contents` for sending to the destination of
+ /// `path`.
///
/// See [`OnionMessenger`] for example usage.
- pub fn send_onion_message<T: CustomOnionMessageContents>(
- &self, path: OnionMessagePath, message: OnionMessageContents<T>,
- reply_path: Option<BlindedPath>
+ pub fn send_onion_message<T: OnionMessageContents>(
+ &self, path: OnionMessagePath, contents: T, reply_path: Option<BlindedPath>
) -> Result<(), SendError> {
- let (introduction_node_id, onion_msg) = create_onion_message(
- &self.entropy_source, &self.node_signer, &self.secp_ctx,
- path, message, reply_path
+ let (first_node_id, onion_msg) = create_onion_message(
+ &self.entropy_source, &self.node_signer, &self.secp_ctx, path, contents, reply_path
)?;
let mut pending_per_peer_msgs = self.pending_messages.lock().unwrap();
- if outbound_buffer_full(&introduction_node_id, &pending_per_peer_msgs) { return Err(SendError::BufferFull) }
- match pending_per_peer_msgs.entry(introduction_node_id) {
+ if outbound_buffer_full(&first_node_id, &pending_per_peer_msgs) { return Err(SendError::BufferFull) }
+ match pending_per_peer_msgs.entry(first_node_id) {
hash_map::Entry::Vacant(_) => Err(SendError::InvalidFirstHop),
hash_map::Entry::Occupied(mut e) => {
e.get_mut().push_back(onion_msg);
}
}
- fn respond_with_onion_message<T: CustomOnionMessageContents>(
- &self, response: OnionMessageContents<T>, path_id: Option<[u8; 32]>,
- reply_path: Option<BlindedPath>
+ fn handle_onion_message_response<T: OnionMessageContents>(
+ &self, response: Option<T>, reply_path: Option<BlindedPath>, log_suffix: fmt::Arguments
+ ) {
+ if let Some(response) = response {
+ match reply_path {
+ Some(reply_path) => {
+ self.find_path_and_enqueue_onion_message(
+ response, Destination::BlindedPath(reply_path), None, log_suffix
+ );
+ },
+ None => {
+ log_trace!(self.logger, "Missing reply path {}", log_suffix);
+ },
+ }
+ }
+ }
+
+ fn find_path_and_enqueue_onion_message<T: OnionMessageContents>(
+ &self, contents: T, destination: Destination, reply_path: Option<BlindedPath>,
+ log_suffix: fmt::Arguments
) {
let sender = match self.node_signer.get_node_id(Recipient::Node) {
Ok(node_id) => node_id,
Err(_) => {
- log_warn!(
- self.logger, "Unable to retrieve node id when responding to onion message with \
- path_id {:02x?}", path_id
- );
+ log_warn!(self.logger, "Unable to retrieve node id {}", log_suffix);
return;
}
};
let peers = self.pending_messages.lock().unwrap().keys().copied().collect();
-
- let destination = match reply_path {
- Some(reply_path) => Destination::BlindedPath(reply_path),
- None => {
- log_trace!(
- self.logger, "Missing reply path when responding to onion message with path_id \
- {:02x?}", path_id
- );
- return;
- },
- };
-
let path = match self.message_router.find_path(sender, peers, destination) {
Ok(path) => path,
Err(()) => {
- log_trace!(
- self.logger, "Failed to find path when responding to onion message with \
- path_id {:02x?}", path_id
- );
+ log_trace!(self.logger, "Failed to find path {}", log_suffix);
return;
},
};
- log_trace!(self.logger, "Responding to onion message with path_id {:02x?}", path_id);
+ log_trace!(self.logger, "Sending onion message {}", log_suffix);
- if let Err(e) = self.send_onion_message(path, response, None) {
- log_trace!(
- self.logger, "Failed responding to onion message with path_id {:02x?}: {:?}",
- path_id, e
- );
+ if let Err(e) = self.send_onion_message(path, contents, reply_path) {
+ log_trace!(self.logger, "Failed sending onion message {}: {:?}", log_suffix, e);
return;
}
}
#[cfg(test)]
- pub(super) fn release_pending_msgs(&self) -> HashMap<PublicKey, VecDeque<msgs::OnionMessage>> {
+ pub(super) fn release_pending_msgs(&self) -> HashMap<PublicKey, VecDeque<OnionMessage>> {
let mut pending_msgs = self.pending_messages.lock().unwrap();
let mut msgs = HashMap::new();
// We don't want to disconnect the peers by removing them entirely from the original map, so we
}
}
-fn outbound_buffer_full(peer_node_id: &PublicKey, buffer: &HashMap<PublicKey, VecDeque<msgs::OnionMessage>>) -> bool {
+fn outbound_buffer_full(peer_node_id: &PublicKey, buffer: &HashMap<PublicKey, VecDeque<OnionMessage>>) -> bool {
const MAX_TOTAL_BUFFER_SIZE: usize = (1 << 20) * 128;
const MAX_PER_PEER_BUFFER_SIZE: usize = (1 << 10) * 256;
let mut total_buffered_bytes = 0;
OMH::Target: OffersMessageHandler,
CMH::Target: CustomOnionMessageHandler,
{
- /// Handle an incoming onion message. Currently, if a message was destined for us we will log, but
- /// soon we'll delegate the onion message to a handler that can generate invoices or send
- /// payments.
- fn handle_onion_message(&self, _peer_node_id: &PublicKey, msg: &msgs::OnionMessage) {
- let control_tlvs_ss = match self.node_signer.ecdh(Recipient::Node, &msg.blinding_point, None) {
- Ok(ss) => ss,
- Err(e) => {
- log_error!(self.logger, "Failed to retrieve node secret: {:?}", e);
- return
- }
- };
- let onion_decode_ss = {
- let blinding_factor = {
- let mut hmac = HmacEngine::<Sha256>::new(b"blinded_node_id");
- hmac.input(control_tlvs_ss.as_ref());
- Hmac::from_engine(hmac).into_inner()
- };
- match self.node_signer.ecdh(Recipient::Node, &msg.onion_routing_packet.public_key,
- Some(&Scalar::from_be_bytes(blinding_factor).unwrap()))
- {
- Ok(ss) => ss.secret_bytes(),
- Err(()) => {
- log_trace!(self.logger, "Failed to compute onion packet shared secret");
- return
- }
- }
- };
- match onion_utils::decode_next_untagged_hop(
- onion_decode_ss, &msg.onion_routing_packet.hop_data[..], msg.onion_routing_packet.hmac,
- (control_tlvs_ss, &*self.custom_handler, &*self.logger)
+ fn handle_onion_message(&self, _peer_node_id: &PublicKey, msg: &OnionMessage) {
+ match peel_onion_message(
+ msg, &self.secp_ctx, &*self.node_signer, &*self.logger, &*self.custom_handler
) {
- Ok((Payload::Receive::<<<CMH as Deref>::Target as CustomOnionMessageHandler>::CustomMessage> {
- message, control_tlvs: ReceiveControlTlvs::Unblinded(ReceiveTlvs { path_id }), reply_path,
- }, None)) => {
+ Ok(PeeledOnion::Receive(message, path_id, reply_path)) => {
log_trace!(self.logger,
"Received an onion message with path_id {:02x?} and {} reply_path",
path_id, if reply_path.is_some() { "a" } else { "no" });
- let response = match message {
- OnionMessageContents::Offers(msg) => {
- self.offers_handler.handle_message(msg)
- .map(|msg| OnionMessageContents::Offers(msg))
+ match message {
+ ParsedOnionMessageContents::Offers(msg) => {
+ let response = self.offers_handler.handle_message(msg);
+ self.handle_onion_message_response(
+ response, reply_path, format_args!(
+ "when responding to Offers onion message with path_id {:02x?}",
+ path_id
+ )
+ );
},
- OnionMessageContents::Custom(msg) => {
- self.custom_handler.handle_custom_message(msg)
- .map(|msg| OnionMessageContents::Custom(msg))
+ ParsedOnionMessageContents::Custom(msg) => {
+ let response = self.custom_handler.handle_custom_message(msg);
+ self.handle_onion_message_response(
+ response, reply_path, format_args!(
+ "when responding to Custom onion message with path_id {:02x?}",
+ path_id
+ )
+ );
},
- };
-
- if let Some(response) = response {
- self.respond_with_onion_message(response, path_id, reply_path);
}
},
- Ok((Payload::Forward(ForwardControlTlvs::Unblinded(ForwardTlvs {
- next_node_id, next_blinding_override
- })), Some((next_hop_hmac, new_packet_bytes)))) => {
- // TODO: we need to check whether `next_node_id` is our node, in which case this is a dummy
- // blinded hop and this onion message is destined for us. In this situation, we should keep
- // unwrapping the onion layers to get to the final payload. Since we don't have the option
- // of creating blinded paths with dummy hops currently, we should be ok to not handle this
- // for now.
- let new_pubkey = match onion_utils::next_hop_pubkey(&self.secp_ctx, msg.onion_routing_packet.public_key, &onion_decode_ss) {
- Ok(pk) => pk,
- Err(e) => {
- log_trace!(self.logger, "Failed to compute next hop packet pubkey: {}", e);
- return
- }
- };
- let outgoing_packet = Packet {
- version: 0,
- public_key: new_pubkey,
- hop_data: new_packet_bytes,
- hmac: next_hop_hmac,
- };
- let onion_message = msgs::OnionMessage {
- blinding_point: match next_blinding_override {
- Some(blinding_point) => blinding_point,
- None => {
- match onion_utils::next_hop_pubkey(
- &self.secp_ctx, msg.blinding_point, control_tlvs_ss.as_ref()
- ) {
- Ok(bp) => bp,
- Err(e) => {
- log_trace!(self.logger, "Failed to compute next blinding point: {}", e);
- return
- }
- }
- }
- },
- onion_routing_packet: outgoing_packet,
- };
-
+ Ok(PeeledOnion::Forward(next_node_id, onion_message)) => {
let mut pending_per_peer_msgs = self.pending_messages.lock().unwrap();
if outbound_buffer_full(&next_node_id, &pending_per_peer_msgs) {
log_trace!(self.logger, "Dropping forwarded onion message to peer {:?}: outbound buffer full", next_node_id);
e.get_mut().push_back(onion_message);
log_trace!(self.logger, "Forwarding an onion message to peer {}", next_node_id);
}
- };
+ }
},
Err(e) => {
- log_trace!(self.logger, "Errored decoding onion message packet: {:?}", e);
- },
- _ => {
- log_trace!(self.logger, "Received bogus onion message packet, either the sender encoded a final hop as a forwarding hop or vice versa");
- },
- };
+ log_error!(self.logger, "Failed to process onion message {:?}", e);
+ }
+ }
}
fn peer_connected(&self, their_node_id: &PublicKey, init: &msgs::Init, _inbound: bool) -> Result<(), ()> {
features.set_onion_messages_optional();
features
}
-}
-impl<ES: Deref, NS: Deref, L: Deref, MR: Deref, OMH: Deref, CMH: Deref> OnionMessageProvider
-for OnionMessenger<ES, NS, L, MR, OMH, CMH>
-where
- ES::Target: EntropySource,
- NS::Target: NodeSigner,
- L::Target: Logger,
- MR::Target: MessageRouter,
- OMH::Target: OffersMessageHandler,
- CMH::Target: CustomOnionMessageHandler,
-{
- fn next_onion_message_for_peer(&self, peer_node_id: PublicKey) -> Option<msgs::OnionMessage> {
+ // Before returning any messages to send for the peer, this method will see if any messages were
+ // enqueued in the handler by users, find a path to the corresponding blinded path's introduction
+ // node, and then enqueue the message for sending to the first peer in the full path.
+ fn next_onion_message_for_peer(&self, peer_node_id: PublicKey) -> Option<OnionMessage> {
+ // Enqueue any initiating `OffersMessage`s to send.
+ for message in self.offers_handler.release_pending_messages() {
+ #[cfg(not(c_bindings))]
+ let PendingOnionMessage { contents, destination, reply_path } = message;
+ #[cfg(c_bindings)]
+ let (contents, destination, reply_path) = message;
+ self.find_path_and_enqueue_onion_message(
+ contents, destination, reply_path, format_args!("when sending OffersMessage")
+ );
+ }
+
+ // Enqueue any initiating `CustomMessage`s to send.
+ for message in self.custom_handler.release_pending_custom_messages() {
+ #[cfg(not(c_bindings))]
+ let PendingOnionMessage { contents, destination, reply_path } = message;
+ #[cfg(c_bindings)]
+ let (contents, destination, reply_path) = message;
+ self.find_path_and_enqueue_onion_message(
+ contents, destination, reply_path, format_args!("when sending CustomMessage")
+ );
+ }
+
let mut pending_msgs = self.pending_messages.lock().unwrap();
if let Some(msgs) = pending_msgs.get_mut(&peer_node_id) {
return msgs.pop_front()
/// Useful for simplifying the parameters of [`SimpleArcChannelManager`] and
/// [`SimpleArcPeerManager`]. See their docs for more details.
///
-/// This is not exported to bindings users as `Arc`s don't make sense in bindings.
+/// This is not exported to bindings users as type aliases aren't supported in most languages.
///
/// [`SimpleArcChannelManager`]: crate::ln::channelmanager::SimpleArcChannelManager
/// [`SimpleArcPeerManager`]: crate::ln::peer_handler::SimpleArcPeerManager
-pub type SimpleArcOnionMessenger<L> = OnionMessenger<
+#[cfg(not(c_bindings))]
+pub type SimpleArcOnionMessenger<M, T, F, L> = OnionMessenger<
Arc<KeysManager>,
Arc<KeysManager>,
Arc<L>,
Arc<DefaultMessageRouter>,
- IgnoringMessageHandler,
+ Arc<SimpleArcChannelManager<M, T, F, L>>,
IgnoringMessageHandler
>;
/// Useful for simplifying the parameters of [`SimpleRefChannelManager`] and
/// [`SimpleRefPeerManager`]. See their docs for more details.
///
-/// This is not exported to bindings users as general type aliases don't make sense in bindings.
+/// This is not exported to bindings users as type aliases aren't supported in most languages.
///
/// [`SimpleRefChannelManager`]: crate::ln::channelmanager::SimpleRefChannelManager
/// [`SimpleRefPeerManager`]: crate::ln::peer_handler::SimpleRefPeerManager
-pub type SimpleRefOnionMessenger<'a, 'b, 'c, L> = OnionMessenger<
+#[cfg(not(c_bindings))]
+pub type SimpleRefOnionMessenger<
+ 'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, 'i, 'j, M, T, F, L
+> = OnionMessenger<
&'a KeysManager,
&'a KeysManager,
&'b L,
- &'c DefaultMessageRouter,
- IgnoringMessageHandler,
+ &'i DefaultMessageRouter,
+ &'j SimpleRefChannelManager<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, M, T, F, L>,
IgnoringMessageHandler
>;
/// Construct onion packet payloads and keys for sending an onion message along the given
/// `unblinded_path` to the given `destination`.
-fn packet_payloads_and_keys<T: CustomOnionMessageContents, S: secp256k1::Signing + secp256k1::Verification>(
- secp_ctx: &Secp256k1<S>, unblinded_path: &[PublicKey], destination: Destination,
- message: OnionMessageContents<T>, mut reply_path: Option<BlindedPath>, session_priv: &SecretKey
+fn packet_payloads_and_keys<T: OnionMessageContents, S: secp256k1::Signing + secp256k1::Verification>(
+ secp_ctx: &Secp256k1<S>, unblinded_path: &[PublicKey], destination: Destination, message: T,
+ mut reply_path: Option<BlindedPath>, session_priv: &SecretKey
) -> Result<(Vec<(Payload<T>, [u8; 32])>, Vec<onion_utils::OnionKeys>), secp256k1::Error> {
let num_hops = unblinded_path.len() + destination.num_hops();
let mut payloads = Vec::with_capacity(num_hops);
}
/// Errors if the serialized payload size exceeds onion_message::BIG_PACKET_HOP_DATA_LEN
-fn construct_onion_message_packet<T: CustomOnionMessageContents>(payloads: Vec<(Payload<T>, [u8; 32])>, onion_keys: Vec<onion_utils::OnionKeys>, prng_seed: [u8; 32]) -> Result<Packet, ()> {
+fn construct_onion_message_packet<T: OnionMessageContents>(payloads: Vec<(Payload<T>, [u8; 32])>, onion_keys: Vec<onion_utils::OnionKeys>, prng_seed: [u8; 32]) -> Result<Packet, ()> {
// Spec rationale:
// "`len` allows larger messages to be sent than the standard 1300 bytes allowed for an HTLC
// onion, but this should be used sparingly as it is reduces anonymity set, hence the
mod functional_tests;
// Re-export structs so they can be imported with just the `onion_message::` module prefix.
-pub use self::messenger::{CustomOnionMessageContents, CustomOnionMessageHandler, DefaultMessageRouter, Destination, MessageRouter, OnionMessageContents, OnionMessagePath, OnionMessenger, SendError, SimpleArcOnionMessenger, SimpleRefOnionMessenger};
+pub use self::messenger::{CustomOnionMessageHandler, DefaultMessageRouter, Destination, MessageRouter, OnionMessageContents, OnionMessagePath, OnionMessenger, PeeledOnion, PendingOnionMessage, SendError};
+#[cfg(not(c_bindings))]
+pub use self::messenger::{SimpleArcOnionMessenger, SimpleRefOnionMessenger};
pub use self::offers::{OffersMessage, OffersMessageHandler};
-pub use self::packet::Packet;
+pub use self::packet::{Packet, ParsedOnionMessageContents};
pub(crate) use self::packet::ControlTlvs;
+pub(crate) use self::messenger::new_pending_onion_message;
use crate::offers::invoice_request::InvoiceRequest;
use crate::offers::invoice::Bolt12Invoice;
use crate::offers::parse::Bolt12ParseError;
+use crate::onion_message::OnionMessageContents;
+use crate::onion_message::messenger::PendingOnionMessage;
use crate::util::logger::Logger;
use crate::util::ser::{Readable, ReadableArgs, Writeable, Writer};
pub trait OffersMessageHandler {
/// Handles the given message by either responding with an [`Bolt12Invoice`], sending a payment,
/// or replying with an error.
+ ///
+ /// The returned [`OffersMessage`], if any, is enqueued to be sent by [`OnionMessenger`].
+ ///
+ /// [`OnionMessenger`]: crate::onion_message::OnionMessenger
fn handle_message(&self, message: OffersMessage) -> Option<OffersMessage>;
+
+ /// Releases any [`OffersMessage`]s that need to be sent.
+ ///
+ /// Typically, this is used for messages initiating a payment flow rather than in response to
+ /// another message. The latter should use the return value of [`Self::handle_message`].
+ #[cfg(not(c_bindings))]
+ fn release_pending_messages(&self) -> Vec<PendingOnionMessage<OffersMessage>> { vec![] }
+
+ /// Releases any [`OffersMessage`]s that need to be sent.
+ ///
+ /// Typically, this is used for messages initiating a payment flow rather than in response to
+ /// another message. The latter should use the return value of [`Self::handle_message`].
+ #[cfg(c_bindings)]
+ fn release_pending_messages(&self) -> Vec<(OffersMessage, crate::onion_message::Destination, Option<crate::blinded_path::BlindedPath>)> { vec![] }
}
/// Possible BOLT 12 Offers messages sent and received via an [`OnionMessage`].
}
}
- /// The TLV record type for the message as used in an `onionmsg_tlv` TLV stream.
- pub fn tlv_type(&self) -> u64 {
- match self {
- OffersMessage::InvoiceRequest(_) => INVOICE_REQUEST_TLV_TYPE,
- OffersMessage::Invoice(_) => INVOICE_TLV_TYPE,
- OffersMessage::InvoiceError(_) => INVOICE_ERROR_TLV_TYPE,
- }
- }
-
fn parse(tlv_type: u64, bytes: Vec<u8>) -> Result<Self, Bolt12ParseError> {
match tlv_type {
INVOICE_REQUEST_TLV_TYPE => Ok(Self::InvoiceRequest(InvoiceRequest::try_from(bytes)?)),
}
}
+impl OnionMessageContents for OffersMessage {
+ fn tlv_type(&self) -> u64 {
+ match self {
+ OffersMessage::InvoiceRequest(_) => INVOICE_REQUEST_TLV_TYPE,
+ OffersMessage::Invoice(_) => INVOICE_TLV_TYPE,
+ OffersMessage::InvoiceError(_) => INVOICE_ERROR_TLV_TYPE,
+ }
+ }
+}
+
impl Writeable for OffersMessage {
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
match self {
/// Onion message payloads contain "control" TLVs and "data" TLVs. Control TLVs are used to route
/// the onion message from hop to hop and for path verification, whereas data TLVs contain the onion
/// message content itself, such as an invoice request.
-pub(super) enum Payload<T: CustomOnionMessageContents> {
+pub(super) enum Payload<T: OnionMessageContents> {
/// This payload is for an intermediate hop.
Forward(ForwardControlTlvs),
/// This payload is for the final hop.
Receive {
control_tlvs: ReceiveControlTlvs,
reply_path: Option<BlindedPath>,
- message: OnionMessageContents<T>,
+ message: T,
}
}
+/// The contents of an [`OnionMessage`] as read from the wire.
+///
+/// [`OnionMessage`]: crate::ln::msgs::OnionMessage
#[derive(Debug)]
-/// The contents of an onion message. In the context of offers, this would be the invoice, invoice
-/// request, or invoice error.
-pub enum OnionMessageContents<T: CustomOnionMessageContents> {
+pub enum ParsedOnionMessageContents<T: OnionMessageContents> {
/// A message related to BOLT 12 Offers.
Offers(OffersMessage),
/// A custom onion message specified by the user.
Custom(T),
}
-impl<T: CustomOnionMessageContents> OnionMessageContents<T> {
+impl<T: OnionMessageContents> OnionMessageContents for ParsedOnionMessageContents<T> {
/// Returns the type that was used to decode the message payload.
///
/// This is not exported to bindings users as methods on non-cloneable enums are not currently exportable
- pub fn tlv_type(&self) -> u64 {
+ fn tlv_type(&self) -> u64 {
match self {
- &OnionMessageContents::Offers(ref msg) => msg.tlv_type(),
- &OnionMessageContents::Custom(ref msg) => msg.tlv_type(),
+ &ParsedOnionMessageContents::Offers(ref msg) => msg.tlv_type(),
+ &ParsedOnionMessageContents::Custom(ref msg) => msg.tlv_type(),
}
}
}
-/// This is not exported to bindings users as methods on non-cloneable enums are not currently exportable
-impl<T: CustomOnionMessageContents> Writeable for OnionMessageContents<T> {
+impl<T: OnionMessageContents> Writeable for ParsedOnionMessageContents<T> {
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
match self {
- OnionMessageContents::Offers(msg) => Ok(msg.write(w)?),
- OnionMessageContents::Custom(msg) => Ok(msg.write(w)?),
+ ParsedOnionMessageContents::Offers(msg) => Ok(msg.write(w)?),
+ ParsedOnionMessageContents::Custom(msg) => Ok(msg.write(w)?),
}
}
}
-/// The contents of a custom onion message.
-pub trait CustomOnionMessageContents: Writeable {
+/// The contents of an onion message.
+pub trait OnionMessageContents: Writeable {
/// Returns the TLV type identifying the message contents. MUST be >= 64.
fn tlv_type(&self) -> u64;
}
}
// Uses the provided secret to simultaneously encode and encrypt the unblinded control TLVs.
-impl<T: CustomOnionMessageContents> Writeable for (Payload<T>, [u8; 32]) {
+impl<T: OnionMessageContents> Writeable for (Payload<T>, [u8; 32]) {
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
match &self.0 {
Payload::Forward(ForwardControlTlvs::Blinded(encrypted_bytes)) => {
}
// Uses the provided secret to simultaneously decode and decrypt the control TLVs and data TLV.
-impl<H: CustomOnionMessageHandler + ?Sized, L: Logger + ?Sized>
-ReadableArgs<(SharedSecret, &H, &L)> for Payload<<H as CustomOnionMessageHandler>::CustomMessage> {
+impl<H: CustomOnionMessageHandler + ?Sized, L: Logger + ?Sized> ReadableArgs<(SharedSecret, &H, &L)>
+for Payload<ParsedOnionMessageContents<<H as CustomOnionMessageHandler>::CustomMessage>> {
fn read<R: Read>(r: &mut R, args: (SharedSecret, &H, &L)) -> Result<Self, DecodeError> {
let (encrypted_tlvs_ss, handler, logger) = args;
match msg_type {
tlv_type if OffersMessage::is_known_type(tlv_type) => {
let msg = OffersMessage::read(msg_reader, (tlv_type, logger))?;
- message = Some(OnionMessageContents::Offers(msg));
+ message = Some(ParsedOnionMessageContents::Offers(msg));
Ok(true)
},
_ => match handler.read_custom_message(msg_type, msg_reader)? {
Some(msg) => {
- message = Some(OnionMessageContents::Custom(msg));
+ message = Some(ParsedOnionMessageContents::Custom(msg));
Ok(true)
},
None => Ok(false),
//! The [`NetworkGraph`] stores the network gossip and [`P2PGossipSync`] fetches it from peers
+use bitcoin::blockdata::constants::ChainHash;
+
use bitcoin::secp256k1::constants::PUBLIC_KEY_SIZE;
use bitcoin::secp256k1::{PublicKey, Verification};
use bitcoin::secp256k1::Secp256k1;
use bitcoin::hashes::sha256d::Hash as Sha256dHash;
use bitcoin::hashes::Hash;
use bitcoin::hashes::hex::FromHex;
-use bitcoin::hash_types::BlockHash;
use bitcoin::network::constants::Network;
-use bitcoin::blockdata::constants::genesis_block;
use crate::events::{MessageSendEvent, MessageSendEventsProvider};
use crate::ln::ChannelId;
pub struct NetworkGraph<L: Deref> where L::Target: Logger {
secp_ctx: Secp256k1<secp256k1::VerifyOnly>,
last_rapid_gossip_sync_timestamp: Mutex<Option<u32>>,
- genesis_hash: BlockHash,
+ chain_hash: ChainHash,
logger: L,
// Lock order: channels -> nodes
channels: RwLock<IndexedMap<u64, ChannelInfo>>,
impl<L: Deref> NetworkGraph<L> where L::Target: Logger {
/// Handles any network updates originating from [`Event`]s.
+ //
+ /// Note that this will skip applying any [`NetworkUpdate::ChannelUpdateMessage`] to avoid
+ /// leaking possibly identifying information of the sender to the public network.
///
/// [`Event`]: crate::events::Event
pub fn handle_network_update(&self, network_update: &NetworkUpdate) {
let short_channel_id = msg.contents.short_channel_id;
let is_enabled = msg.contents.flags & (1 << 1) != (1 << 1);
let status = if is_enabled { "enabled" } else { "disabled" };
- log_debug!(self.logger, "Updating channel with channel_update from a payment failure. Channel {} is {}.", short_channel_id, status);
- let _ = self.update_channel(msg);
+ log_debug!(self.logger, "Skipping application of a channel update from a payment failure. Channel {} is {}.", short_channel_id, status);
},
NetworkUpdate::ChannelFailure { short_channel_id, is_permanent } => {
if is_permanent {
}
}
- /// Gets the genesis hash for this network graph.
- pub fn get_genesis_hash(&self) -> BlockHash {
- self.genesis_hash
+ /// Gets the chain hash for this network graph.
+ pub fn get_chain_hash(&self) -> ChainHash {
+ self.chain_hash
}
}
pending_events.push(MessageSendEvent::SendGossipTimestampFilter {
node_id: their_node_id.clone(),
msg: GossipTimestampFilter {
- chain_hash: self.network_graph.genesis_hash,
+ chain_hash: self.network_graph.chain_hash,
first_timestamp: gossip_start_time as u32, // 2106 issue!
timestamp_range: u32::max_value(),
},
let exclusive_end_scid = scid_from_parts(cmp::min(msg.end_blocknum() as u64, MAX_SCID_BLOCK), 0, 0);
// Per spec, we must reply to a query. Send an empty message when things are invalid.
- if msg.chain_hash != self.network_graph.genesis_hash || inclusive_start_scid.is_err() || exclusive_end_scid.is_err() || msg.number_of_blocks == 0 {
+ if msg.chain_hash != self.network_graph.chain_hash || inclusive_start_scid.is_err() || exclusive_end_scid.is_err() || msg.number_of_blocks == 0 {
let mut pending_events = self.pending_events.lock().unwrap();
pending_events.push(MessageSendEvent::SendReplyChannelRange {
node_id: their_node_id.clone(),
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
write_ver_prefix!(writer, SERIALIZATION_VERSION, MIN_SERIALIZATION_VERSION);
- self.genesis_hash.write(writer)?;
+ self.chain_hash.write(writer)?;
let channels = self.channels.read().unwrap();
(channels.len() as u64).write(writer)?;
for (ref chan_id, ref chan_info) in channels.unordered_iter() {
fn read<R: io::Read>(reader: &mut R, logger: L) -> Result<NetworkGraph<L>, DecodeError> {
let _ver = read_ver_prefix!(reader, SERIALIZATION_VERSION);
- let genesis_hash: BlockHash = Readable::read(reader)?;
+ let chain_hash: ChainHash = Readable::read(reader)?;
let channels_count: u64 = Readable::read(reader)?;
let mut channels = IndexedMap::new();
for _ in 0..channels_count {
Ok(NetworkGraph {
secp_ctx: Secp256k1::verification_only(),
- genesis_hash,
+ chain_hash,
logger,
channels: RwLock::new(channels),
nodes: RwLock::new(nodes),
let b = if ord { (&other.channels, &other.nodes) } else { (&self.channels, &self.nodes) };
let (channels_a, channels_b) = (a.0.unsafe_well_ordered_double_lock_self(), b.0.unsafe_well_ordered_double_lock_self());
let (nodes_a, nodes_b) = (a.1.unsafe_well_ordered_double_lock_self(), b.1.unsafe_well_ordered_double_lock_self());
- self.genesis_hash.eq(&other.genesis_hash) && channels_a.eq(&channels_b) && nodes_a.eq(&nodes_b)
+ self.chain_hash.eq(&other.chain_hash) && channels_a.eq(&channels_b) && nodes_a.eq(&nodes_b)
}
}
pub fn new(network: Network, logger: L) -> NetworkGraph<L> {
Self {
secp_ctx: Secp256k1::verification_only(),
- genesis_hash: genesis_block(network).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(network),
logger,
channels: RwLock::new(IndexedMap::new()),
nodes: RwLock::new(IndexedMap::new()),
return Err(LightningError{err: "Channel announcement node had a channel with itself".to_owned(), action: ErrorAction::IgnoreError});
}
- if msg.chain_hash != self.genesis_hash {
+ if msg.chain_hash != self.chain_hash {
return Err(LightningError {
err: "Channel announcement chain hash does not match genesis hash".to_owned(),
action: ErrorAction::IgnoreAndLog(Level::Debug),
/// For an already known (from announcement) channel, update info about one of the directions
/// of the channel.
///
- /// You probably don't want to call this directly, instead relying on a P2PGossipSync's
- /// RoutingMessageHandler implementation to call it indirectly. This may be useful to accept
+ /// You probably don't want to call this directly, instead relying on a [`P2PGossipSync`]'s
+ /// [`RoutingMessageHandler`] implementation to call it indirectly. This may be useful to accept
/// routing messages from a source using a protocol other than the lightning P2P protocol.
///
/// If built with `no-std`, any updates with a timestamp more than two weeks in the past or
/// materially in the future will be rejected.
pub fn update_channel(&self, msg: &msgs::ChannelUpdate) -> Result<(), LightningError> {
- self.update_channel_intern(&msg.contents, Some(&msg), Some(&msg.signature))
+ self.update_channel_internal(&msg.contents, Some(&msg), Some(&msg.signature), false)
}
/// For an already known (from announcement) channel, update info about one of the directions
/// If built with `no-std`, any updates with a timestamp more than two weeks in the past or
/// materially in the future will be rejected.
pub fn update_channel_unsigned(&self, msg: &msgs::UnsignedChannelUpdate) -> Result<(), LightningError> {
- self.update_channel_intern(msg, None, None)
+ self.update_channel_internal(msg, None, None, false)
}
- fn update_channel_intern(&self, msg: &msgs::UnsignedChannelUpdate, full_msg: Option<&msgs::ChannelUpdate>, sig: Option<&secp256k1::ecdsa::Signature>) -> Result<(), LightningError> {
+ /// For an already known (from announcement) channel, verify the given [`ChannelUpdate`].
+ ///
+ /// This checks whether the update currently is applicable by [`Self::update_channel`].
+ ///
+ /// If built with `no-std`, any updates with a timestamp more than two weeks in the past or
+ /// materially in the future will be rejected.
+ pub fn verify_channel_update(&self, msg: &msgs::ChannelUpdate) -> Result<(), LightningError> {
+ self.update_channel_internal(&msg.contents, Some(&msg), Some(&msg.signature), true)
+ }
+
+ fn update_channel_internal(&self, msg: &msgs::UnsignedChannelUpdate,
+ full_msg: Option<&msgs::ChannelUpdate>, sig: Option<&secp256k1::ecdsa::Signature>,
+ only_verify: bool) -> Result<(), LightningError>
+ {
let chan_enabled = msg.flags & (1 << 1) != (1 << 1);
- if msg.chain_hash != self.genesis_hash {
+ if msg.chain_hash != self.chain_hash {
return Err(LightningError {
err: "Channel update chain hash does not match genesis hash".to_owned(),
action: ErrorAction::IgnoreAndLog(Level::Debug),
action: ErrorAction::IgnoreAndLog(Level::Debug)
})?, "channel_update");
}
- channel.two_to_one = get_new_channel_info!();
+ if !only_verify {
+ channel.two_to_one = get_new_channel_info!();
+ }
} else {
check_update_latest!(channel.one_to_two);
if let Some(sig) = sig {
action: ErrorAction::IgnoreAndLog(Level::Debug)
})?, "channel_update");
}
- channel.one_to_two = get_new_channel_info!();
+ if !only_verify {
+ channel.one_to_two = get_new_channel_info!();
+ }
}
}
}
use bitcoin::hashes::sha256d::Hash as Sha256dHash;
use bitcoin::hashes::Hash;
use bitcoin::network::constants::Network;
- use bitcoin::blockdata::constants::genesis_block;
+ use bitcoin::blockdata::constants::ChainHash;
use bitcoin::blockdata::script::Script;
use bitcoin::blockdata::transaction::TxOut;
let mut unsigned_announcement = UnsignedChannelAnnouncement {
features: channelmanager::provided_channel_features(&UserConfig::default()),
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 0,
node_id_1: NodeId::from_pubkey(&node_id_1),
node_id_2: NodeId::from_pubkey(&node_id_2),
pub(crate) fn get_signed_channel_update<F: Fn(&mut UnsignedChannelUpdate)>(f: F, node_key: &SecretKey, secp_ctx: &Secp256k1<secp256k1::All>) -> ChannelUpdate {
let mut unsigned_channel_update = UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 0,
timestamp: 100,
flags: 0,
// Test that channel announcements with the wrong chain hash are ignored (network graph is testnet,
// announcement is mainnet).
let incorrect_chain_announcement = get_signed_channel_announcement(|unsigned_announcement| {
- unsigned_announcement.chain_hash = genesis_block(Network::Bitcoin).header.block_hash();
+ unsigned_announcement.chain_hash = ChainHash::using_genesis_block(Network::Bitcoin);
}, node_1_privkey, node_2_privkey, &secp_ctx);
match gossip_sync.handle_channel_announcement(&incorrect_chain_announcement) {
Ok(_) => panic!(),
}
let valid_channel_update = get_signed_channel_update(|_| {}, node_1_privkey, &secp_ctx);
+ network_graph.verify_channel_update(&valid_channel_update).unwrap();
match gossip_sync.handle_channel_update(&valid_channel_update) {
Ok(res) => assert!(res),
_ => panic!(),
// Test that channel updates with the wrong chain hash are ignored (network graph is testnet, channel
// update is mainet).
let incorrect_chain_update = get_signed_channel_update(|unsigned_channel_update| {
- unsigned_channel_update.chain_hash = genesis_block(Network::Bitcoin).header.block_hash();
+ unsigned_channel_update.chain_hash = ChainHash::using_genesis_block(Network::Bitcoin);
}, node_1_privkey, &secp_ctx);
match gossip_sync.handle_channel_update(&incorrect_chain_update) {
let short_channel_id;
{
- // Announce a channel we will update
+ // Check we won't apply an update via `handle_network_update` for privacy reasons, but
+ // can continue fine if we manually apply it.
let valid_channel_announcement = get_signed_channel_announcement(|_| {}, node_1_privkey, node_2_privkey, &secp_ctx);
short_channel_id = valid_channel_announcement.contents.short_channel_id;
let chain_source: Option<&test_utils::TestChainSource> = None;
assert!(network_graph.read_only().channels().get(&short_channel_id).unwrap().one_to_two.is_none());
network_graph.handle_network_update(&NetworkUpdate::ChannelUpdateMessage {
- msg: valid_channel_update,
+ msg: valid_channel_update.clone(),
});
- assert!(network_graph.read_only().channels().get(&short_channel_id).unwrap().one_to_two.is_some());
+ assert!(network_graph.read_only().channels().get(&short_channel_id).unwrap().one_to_two.is_none());
+ network_graph.update_channel(&valid_channel_update).unwrap();
}
// Non-permanent failure doesn't touch the channel at all
let node_privkey_1 = &SecretKey::from_slice(&[42; 32]).unwrap();
let node_id_1 = PublicKey::from_secret_key(&secp_ctx, node_privkey_1);
- let chain_hash = genesis_block(Network::Testnet).header.block_hash();
+ let chain_hash = ChainHash::using_genesis_block(Network::Testnet);
// It should ignore if gossip_queries feature is not enabled
{
let network_graph = create_network_graph();
let (secp_ctx, gossip_sync) = create_gossip_sync(&network_graph);
- let chain_hash = genesis_block(Network::Testnet).header.block_hash();
+ let chain_hash = ChainHash::using_genesis_block(Network::Testnet);
let node_1_privkey = &SecretKey::from_slice(&[42; 32]).unwrap();
let node_2_privkey = &SecretKey::from_slice(&[41; 32]).unwrap();
let node_id_2 = PublicKey::from_secret_key(&secp_ctx, node_2_privkey);
&gossip_sync,
&node_id_2,
QueryChannelRange {
- chain_hash: genesis_block(Network::Bitcoin).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Bitcoin),
first_blocknum: 0,
number_of_blocks: 0xffff_ffff,
},
false,
vec![ReplyChannelRange {
- chain_hash: genesis_block(Network::Bitcoin).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Bitcoin),
first_blocknum: 0,
number_of_blocks: 0xffff_ffff,
sync_complete: true,
let node_privkey = &SecretKey::from_slice(&[41; 32]).unwrap();
let node_id = PublicKey::from_secret_key(&secp_ctx, node_privkey);
- let chain_hash = genesis_block(Network::Testnet).header.block_hash();
+ let chain_hash = ChainHash::using_genesis_block(Network::Testnet);
let result = gossip_sync.handle_query_short_channel_ids(&node_id, QueryShortChannelIds {
chain_hash,
&self, payer: &PublicKey, route_params: &RouteParameters,
first_hops: Option<&[&ChannelDetails]>, inflight_htlcs: InFlightHtlcs
) -> Result<Route, LightningError>;
+
/// Finds a [`Route`] for a payment between the given `payer` and a payee.
///
/// The `payee` and the payment's value are given in [`RouteParameters::payment_params`]
use crate::chain::transaction::OutPoint;
use crate::sign::EntropySource;
use crate::ln::ChannelId;
- use crate::ln::features::{BlindedHopFeatures, Bolt12InvoiceFeatures, ChannelFeatures, InitFeatures, NodeFeatures};
+ use crate::ln::features::{BlindedHopFeatures, ChannelFeatures, InitFeatures, NodeFeatures};
use crate::ln::msgs::{ErrorAction, LightningError, UnsignedChannelUpdate, MAX_VALUE_MSAT};
use crate::ln::channelmanager;
use crate::offers::invoice::BlindedPayInfo;
use bitcoin::hashes::Hash;
use bitcoin::network::constants::Network;
- use bitcoin::blockdata::constants::genesis_block;
+ use bitcoin::blockdata::constants::ChainHash;
use bitcoin::blockdata::script::Builder;
use bitcoin::blockdata::opcodes;
use bitcoin::blockdata::transaction::TxOut;
// Disable other paths
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 12,
timestamp: 2,
flags: 2, // to disable
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 3,
timestamp: 2,
flags: 2, // to disable
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[7], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 13,
timestamp: 2,
flags: 2, // to disable
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 6,
timestamp: 2,
flags: 2, // to disable
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 7,
timestamp: 2,
flags: 2, // to disable
// Check against amount_to_transfer_over_msat.
// Set minimal HTLC of 200_000_000 msat.
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 2,
timestamp: 3,
flags: 0,
// Second hop only allows to forward 199_999_999 at most, thus not allowing the first hop to
// be used.
update_channel(&gossip_sync, &secp_ctx, &privkeys[1], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 4,
timestamp: 3,
flags: 0,
// Lift the restriction on the first hop.
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 2,
timestamp: 4,
flags: 0,
let (secp_ctx, network_graph, gossip_sync, _, logger) = build_graph();
let (our_privkey, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
let config = UserConfig::default();
- let payment_params = PaymentParameters::from_node_id(nodes[2], 42).with_bolt11_features(channelmanager::provided_invoice_features(&config)).unwrap();
+ let payment_params = PaymentParameters::from_node_id(nodes[2], 42)
+ .with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config))
+ .unwrap();
let scorer = ln_test_utils::TestScorer::new();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
// One path allows transferring 35-40 sats, another one also allows 35-40 sats.
// Thus, they can't send 60 without overpaying.
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 2,
timestamp: 2,
flags: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 12,
timestamp: 3,
flags: 0,
// Make 0 fee.
update_channel(&gossip_sync, &secp_ctx, &privkeys[7], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 13,
timestamp: 2,
flags: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[1], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 4,
timestamp: 2,
flags: 0,
// Disable other paths
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 1,
timestamp: 3,
flags: 2, // to disable
// Now, test that if there are 2 paths, a "cheaper" by fee path wouldn't be prioritized
// while taking even more fee to match htlc_minimum_msat.
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 12,
timestamp: 4,
flags: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 2,
timestamp: 3,
flags: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[1], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 4,
timestamp: 4,
flags: 0,
let (secp_ctx, network_graph, gossip_sync, _, logger) = build_graph();
let (_, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
let config = UserConfig::default();
- let payment_params = PaymentParameters::from_node_id(nodes[2], 42).with_bolt11_features(channelmanager::provided_invoice_features(&config)).unwrap();
+ let payment_params = PaymentParameters::from_node_id(nodes[2], 42).with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config)).unwrap();
let scorer = ln_test_utils::TestScorer::new();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
// First disable all paths except the us -> node1 -> node2 path
update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 13,
timestamp: 2,
flags: 3,
// Set channel 4 to free but with a high htlc_minimum_msat
update_channel(&gossip_sync, &secp_ctx, &privkeys[1], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 4,
timestamp: 2,
flags: 0,
// // Disable channels 4 and 12 by flags=2
update_channel(&gossip_sync, &secp_ctx, &privkeys[1], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 4,
timestamp: 2,
flags: 2, // to disable
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 12,
timestamp: 2,
flags: 2, // to disable
// Disabling channels 6 & 7 by flags=2
update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 6,
timestamp: 2,
flags: 2, // to disable
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 7,
timestamp: 2,
flags: 2, // to disable
// Disabling channels 6 & 7 by flags=2
update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 6,
timestamp: 2,
flags: 2, // to disable
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 7,
timestamp: 2,
flags: 2, // to disable
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let config = UserConfig::default();
- let payment_params = PaymentParameters::from_node_id(nodes[2], 42).with_bolt11_features(channelmanager::provided_invoice_features(&config)).unwrap();
+ let payment_params = PaymentParameters::from_node_id(nodes[2], 42)
+ .with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config))
+ .unwrap();
// We will use a simple single-path route from
// our node to node2 via node0: channels {1, 3}.
// First disable all other paths.
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 2,
timestamp: 2,
flags: 2,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 12,
timestamp: 2,
flags: 2,
// Make the first channel (#1) very permissive,
// and we will be testing all limits on the second channel.
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 1,
timestamp: 2,
flags: 0,
// First, let's see if routing works if we have absolutely no idea about the available amount.
// In this case, it should be set to 250_000 sats.
update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 3,
timestamp: 2,
flags: 0,
// Check that setting next_outbound_htlc_limit_msat in first_hops limits the channels.
// Disable channel #1 and use another first hop.
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 1,
timestamp: 3,
flags: 2,
// Enable channel #1 back.
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 1,
timestamp: 4,
flags: 0,
// Now let's see if routing works if we know only htlc_maximum_msat.
update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 3,
timestamp: 3,
flags: 0,
// We can't change UTXO capacity on the fly, so we'll disable
// the existing channel and add another one with the capacity we need.
update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 3,
timestamp: 4,
flags: 2,
add_channel(&gossip_sync, &secp_ctx, &privkeys[0], &privkeys[2], ChannelFeatures::from_le_bytes(id_to_feature_flags(3)), 333);
update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 333,
timestamp: 1,
flags: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 333,
timestamp: 1,
flags: 1,
// Now let's see if routing chooses htlc_maximum_msat over UTXO capacity.
update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 333,
timestamp: 6,
flags: 0,
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let config = UserConfig::default();
- let payment_params = PaymentParameters::from_node_id(nodes[3], 42).with_bolt11_features(channelmanager::provided_invoice_features(&config)).unwrap();
+ let payment_params = PaymentParameters::from_node_id(nodes[3], 42)
+ .with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config))
+ .unwrap();
// Path via {node7, node2, node4} is channels {12, 13, 6, 11}.
// {12, 13, 11} have the capacities of 100, {6} has a capacity of 50.
// Disable other potential paths.
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 2,
timestamp: 2,
flags: 2,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 7,
timestamp: 2,
flags: 2,
// Limit capacities
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 12,
timestamp: 2,
flags: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[7], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 13,
timestamp: 2,
flags: 0,
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 6,
timestamp: 2,
flags: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[4], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 11,
timestamp: 2,
flags: 0,
// Path via node0 is channels {1, 3}. Limit them to 100 and 50 sats (total limit 50).
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 1,
timestamp: 2,
flags: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 3,
timestamp: 2,
flags: 0,
let (_, _, _, nodes) = get_nodes(&secp_ctx);
let config = UserConfig::default();
let clear_payment_params = PaymentParameters::from_node_id(nodes[2], 42)
- .with_bolt11_features(channelmanager::provided_invoice_features(&config)).unwrap();
+ .with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config))
+ .unwrap();
do_simple_mpp_route_test(clear_payment_params);
// MPP to a 1-hop blinded path for nodes[2]
- let bolt12_features: Bolt12InvoiceFeatures = channelmanager::provided_invoice_features(&config).to_context();
+ let bolt12_features = channelmanager::provided_bolt12_invoice_features(&config);
let blinded_path = BlindedPath {
introduction_node_id: nodes[2],
blinding_point: ln_test_utils::pubkey(42),
// Path via node0 is channels {1, 3}. Limit them to 100 and 50 sats (total limit 50).
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 1,
timestamp: 2,
flags: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 3,
timestamp: 2,
flags: 0,
// Path via node7 is channels {12, 13}. Limit them to 60 and 60 sats
// (total limit 60).
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 12,
timestamp: 2,
flags: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[7], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 13,
timestamp: 2,
flags: 0,
// Path via node1 is channels {2, 4}. Limit them to 200 and 180 sats
// (total capacity 180 sats).
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 2,
timestamp: 2,
flags: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[1], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 4,
timestamp: 2,
flags: 0,
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let config = UserConfig::default();
let payment_params = PaymentParameters::from_node_id(nodes[3], 42)
- .with_bolt11_features(channelmanager::provided_invoice_features(&config)).unwrap();
+ .with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config))
+ .unwrap();
// We need a route consisting of 3 paths:
// From our node to node3 via {node0, node2}, {node7, node2, node4} and {node7, node2}.
// Disable other potential paths.
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 2,
timestamp: 2,
flags: 2,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 7,
timestamp: 2,
flags: 2,
// Path via {node0, node2} is channels {1, 3, 5}.
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 1,
timestamp: 2,
flags: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 3,
timestamp: 2,
flags: 0,
// Capacity of 200 sats because this channel will be used by 3rd path as well.
add_channel(&gossip_sync, &secp_ctx, &privkeys[2], &privkeys[3], ChannelFeatures::from_le_bytes(id_to_feature_flags(5)), 5);
update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 5,
timestamp: 2,
flags: 0,
// Add 100 sats to the capacities of {12, 13}, because these channels
// are also used for 3rd path. 100 sats for the rest. Total capacity: 100 sats.
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 12,
timestamp: 2,
flags: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[7], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 13,
timestamp: 2,
flags: 0,
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 6,
timestamp: 2,
flags: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[4], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 11,
timestamp: 2,
flags: 0,
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let config = UserConfig::default();
- let payment_params = PaymentParameters::from_node_id(nodes[3], 42).with_bolt11_features(channelmanager::provided_invoice_features(&config)).unwrap();
+ let payment_params = PaymentParameters::from_node_id(nodes[3], 42)
+ .with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config))
+ .unwrap();
// This test checks that if we have two cheaper paths and one more expensive path,
// so that liquidity-wise any 2 of 3 combination is sufficient,
// Disable other potential paths.
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 2,
timestamp: 2,
flags: 2,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 7,
timestamp: 2,
flags: 2,
// Path via {node0, node2} is channels {1, 3, 5}.
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 1,
timestamp: 2,
flags: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 3,
timestamp: 2,
flags: 0,
// Capacity of 200 sats because this channel will be used by 3rd path as well.
add_channel(&gossip_sync, &secp_ctx, &privkeys[2], &privkeys[3], ChannelFeatures::from_le_bytes(id_to_feature_flags(5)), 5);
update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 5,
timestamp: 2,
flags: 0,
// Add 100 sats to the capacities of {12, 13}, because these channels
// are also used for 3rd path. 100 sats for the rest. Total capacity: 100 sats.
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 12,
timestamp: 2,
flags: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[7], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 13,
timestamp: 2,
flags: 0,
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 6,
timestamp: 2,
flags: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[4], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 11,
timestamp: 2,
flags: 0,
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let config = UserConfig::default();
- let payment_params = PaymentParameters::from_node_id(nodes[3], 42).with_bolt11_features(channelmanager::provided_invoice_features(&config)).unwrap();
+ let payment_params = PaymentParameters::from_node_id(nodes[3], 42)
+ .with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config))
+ .unwrap();
// We need a route consisting of 2 paths:
// From our node to node3 via {node0, node2} and {node7, node2, node4}.
// Disable other potential paths.
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 2,
timestamp: 2,
flags: 2,
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 7,
timestamp: 2,
flags: 2,
// Path via {node0, node2} is channels {1, 3, 5}.
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 1,
timestamp: 2,
flags: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 3,
timestamp: 2,
flags: 0,
add_channel(&gossip_sync, &secp_ctx, &privkeys[2], &privkeys[3], ChannelFeatures::from_le_bytes(id_to_feature_flags(5)), 5);
update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 5,
timestamp: 2,
flags: 0,
// - fee for channel 6 is 150 sats
// Let's test this by enforcing these 2 conditions and removing other limits.
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 12,
timestamp: 2,
flags: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[7], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 13,
timestamp: 2,
flags: 0,
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 6,
timestamp: 2,
flags: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[4], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 11,
timestamp: 2,
flags: 0,
max_total_routing_fee_msat: Some(149_999) };
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
&our_id, &route_params, &network_graph.read_only(), None, Arc::clone(&logger),
- &scorer, &(), &random_seed_bytes) {
+ &scorer, &Default::default(), &random_seed_bytes) {
assert_eq!(err, "Failed to find a sufficient route to the given destination");
} else { panic!(); }
}
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let config = UserConfig::default();
- let payment_params = PaymentParameters::from_node_id(PublicKey::from_slice(&[02; 33]).unwrap(), 42).with_bolt11_features(channelmanager::provided_invoice_features(&config)).unwrap()
+ let payment_params = PaymentParameters::from_node_id(PublicKey::from_slice(&[02; 33]).unwrap(), 42)
+ .with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config)).unwrap()
.with_route_hints(vec![RouteHint(vec![RouteHintHop {
src_node_id: nodes[2],
short_channel_id: 42,
// we think we can only send up to 1 additional sat over the last-hop but refuse to as its
// under 5% of our payment amount.
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 1,
timestamp: 2,
flags: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 2,
timestamp: 2,
flags: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[1], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 4,
timestamp: 2,
flags: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[7], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 13,
timestamp: 2,
flags: 0|2, // Channel disabled
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let config = UserConfig::default();
- let payment_params = PaymentParameters::from_node_id(nodes[2], 42).with_bolt11_features(channelmanager::provided_invoice_features(&config)).unwrap()
+ let payment_params = PaymentParameters::from_node_id(nodes[2], 42)
+ .with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config))
+ .unwrap()
.with_max_channel_saturation_power_of_half(0);
// We need a route consisting of 3 paths:
// Path via node0 is channels {1, 3}. Limit them to 100 and 50 sats (total limit 50);
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 1,
timestamp: 2,
flags: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 3,
timestamp: 2,
flags: 0,
// Path via node7 is channels {12, 13}. Limit them to 60 and 60 sats (total limit 60);
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 12,
timestamp: 2,
flags: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[7], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 13,
timestamp: 2,
flags: 0,
// Path via node1 is channels {2, 4}. Limit them to 20 and 20 sats (total capacity 20 sats).
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 2,
timestamp: 2,
flags: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[1], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 4,
timestamp: 2,
flags: 0,
add_channel(&gossip_sync, &secp_ctx, &our_privkey, &privkeys[1], ChannelFeatures::from_le_bytes(id_to_feature_flags(6)), 6);
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 6,
timestamp: 1,
flags: 0,
add_channel(&gossip_sync, &secp_ctx, &privkeys[1], &privkeys[4], ChannelFeatures::from_le_bytes(id_to_feature_flags(5)), 5);
update_channel(&gossip_sync, &secp_ctx, &privkeys[1], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 5,
timestamp: 1,
flags: 0,
add_channel(&gossip_sync, &secp_ctx, &privkeys[4], &privkeys[3], ChannelFeatures::from_le_bytes(id_to_feature_flags(4)), 4);
update_channel(&gossip_sync, &secp_ctx, &privkeys[4], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 4,
timestamp: 1,
flags: 0,
add_channel(&gossip_sync, &secp_ctx, &privkeys[3], &privkeys[2], ChannelFeatures::from_le_bytes(id_to_feature_flags(3)), 3);
update_channel(&gossip_sync, &secp_ctx, &privkeys[3], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 3,
timestamp: 1,
flags: 0,
add_channel(&gossip_sync, &secp_ctx, &privkeys[2], &privkeys[4], ChannelFeatures::from_le_bytes(id_to_feature_flags(2)), 2);
update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 2,
timestamp: 1,
flags: 0,
add_channel(&gossip_sync, &secp_ctx, &privkeys[4], &privkeys[6], ChannelFeatures::from_le_bytes(id_to_feature_flags(1)), 1);
update_channel(&gossip_sync, &secp_ctx, &privkeys[4], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 1,
timestamp: 1,
flags: 0,
// We modify the graph to set the htlc_maximum of channel 2 to below the value we wish to
// send.
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 2,
timestamp: 2,
flags: 0,
});
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 12,
timestamp: 2,
flags: 0,
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let config = UserConfig::default();
- let payment_params = PaymentParameters::from_node_id(nodes[2], 42).with_bolt11_features(channelmanager::provided_invoice_features(&config)).unwrap();
+ let payment_params = PaymentParameters::from_node_id(nodes[2], 42)
+ .with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config))
+ .unwrap();
// We modify the graph to set the htlc_minimum of channel 2 and 4 as needed - channel 2
// gets an htlc_maximum_msat of 80_000 and channel 4 an htlc_minimum_msat of 90_000. We
// then try to send 90_000.
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 2,
timestamp: 2,
flags: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[1], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 4,
timestamp: 2,
flags: 0,
assert_eq!(route.paths[0].hops[1].short_channel_id, 13);
assert_eq!(route.paths[0].hops[1].fee_msat, 90_000);
assert_eq!(route.paths[0].hops[1].cltv_expiry_delta, 42);
- assert_eq!(route.paths[0].hops[1].node_features.le_flags(), channelmanager::provided_invoice_features(&config).le_flags());
+ assert_eq!(route.paths[0].hops[1].node_features.le_flags(), channelmanager::provided_bolt11_invoice_features(&config).le_flags());
assert_eq!(route.paths[0].hops[1].channel_features.le_flags(), &id_to_feature_flags(13));
}
}
let network_graph = NetworkGraph::new(Network::Testnet, Arc::clone(&logger));
let scorer = ln_test_utils::TestScorer::new();
let config = UserConfig::default();
- let payment_params = PaymentParameters::from_node_id(nodes[0], 42).with_bolt11_features(channelmanager::provided_invoice_features(&config)).unwrap();
+ let payment_params = PaymentParameters::from_node_id(nodes[0], 42)
+ .with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config))
+ .unwrap();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
// Set the fee on channel 13 to 100% to match channel 4 giving us two equivalent paths (us
// -> node 7 -> node2 and us -> node 1 -> node 2) which we should balance over.
update_channel(&gossip_sync, &secp_ctx, &privkeys[1], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 4,
timestamp: 2,
flags: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[7], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 13,
timestamp: 2,
flags: 0,
});
let config = UserConfig::default();
- let payment_params = PaymentParameters::from_node_id(nodes[2], 42).with_bolt11_features(channelmanager::provided_invoice_features(&config)).unwrap();
+ let payment_params = PaymentParameters::from_node_id(nodes[2], 42)
+ .with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config))
+ .unwrap();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
// 100,000 sats is less than the available liquidity on each channel, set above.
let params = ProbabilisticScoringFeeParameters::default();
let mut scorer = ProbabilisticScorer::new(ProbabilisticScoringDecayParameters::default(), &graph, &logger);
- let features = channelmanager::provided_invoice_features(&UserConfig::default());
+ let features = channelmanager::provided_bolt11_invoice_features(&UserConfig::default());
super::bench_utils::generate_test_routes(&graph, &mut scorer, ¶ms, features, random_init_seed(), 0, 2);
}
let params = ProbabilisticScoringFeeParameters::default();
let mut scorer = ProbabilisticScorer::new(ProbabilisticScoringDecayParameters::default(), &graph, &logger);
- let features = channelmanager::provided_invoice_features(&UserConfig::default());
+ let features = channelmanager::provided_bolt11_invoice_features(&UserConfig::default());
super::bench_utils::generate_test_routes(&graph, &mut scorer, ¶ms, features, random_init_seed(), 1_000_000, 2);
}
let dest_node_id = ln_test_utils::pubkey(42);
let payment_params = PaymentParameters::from_node_id(dest_node_id, 42)
.with_route_hints(vec![route_hint_1.clone()]).unwrap()
- .with_bolt11_features(channelmanager::provided_invoice_features(&config)).unwrap();
+ .with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config))
+ .unwrap();
// Make sure we'll error if our route hints don't have enough liquidity according to their
// htlc_maximum_msat.
route_hint_2.0[0].short_channel_id = 43;
let payment_params = PaymentParameters::from_node_id(dest_node_id, 42)
.with_route_hints(vec![route_hint_1, route_hint_2]).unwrap()
- .with_bolt11_features(channelmanager::provided_invoice_features(&config)).unwrap();
+ .with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config))
+ .unwrap();
let mut route_params = RouteParameters::from_payment_params_and_value(
payment_params, max_htlc_msat + 1);
route_params.max_total_routing_fee_msat = Some(max_htlc_msat * 2);
let dest_node_id = ln_test_utils::pubkey(44);
let payment_params = PaymentParameters::from_node_id(dest_node_id, 42)
.with_route_hints(vec![route_hint_1, route_hint_2]).unwrap()
- .with_bolt11_features(channelmanager::provided_invoice_features(&config)).unwrap();
+ .with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config))
+ .unwrap();
let route_params = RouteParameters::from_payment_params_and_value(
payment_params, amt_msat);
cltv_expiry_delta: 10,
features: BlindedHopFeatures::empty(),
};
- let bolt12_features: Bolt12InvoiceFeatures = channelmanager::provided_invoice_features(&config).to_context();
+ let bolt12_features = channelmanager::provided_bolt12_invoice_features(&config);
let payment_params = PaymentParameters::blinded(vec![
(blinded_payinfo.clone(), blinded_path.clone()),
(blinded_payinfo.clone(), blinded_path.clone())])
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let config = UserConfig::default();
- let bolt12_features: Bolt12InvoiceFeatures = channelmanager::provided_invoice_features(&config).to_context();
+ let bolt12_features = channelmanager::provided_bolt12_invoice_features(&config);
let blinded_path_1 = BlindedPath {
introduction_node_id: nodes[2],
blinding_point: ln_test_utils::pubkey(42),
(blinded_payinfo_2.clone(), blinded_path_2.clone()),
];
let payment_params = PaymentParameters::blinded(blinded_hints.clone())
- .with_bolt12_features(bolt12_features.clone()).unwrap();
+ .with_bolt12_features(bolt12_features).unwrap();
let mut route_params = RouteParameters::from_payment_params_and_value(payment_params, 100_000);
route_params.max_total_routing_fee_msat = Some(100_000);
add_channel(&gossip_sync, &secp_ctx, &privkeys[0], &privkeys[1],
ChannelFeatures::from_le_bytes(id_to_feature_flags(1)), 1);
update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 1,
timestamp: 1,
flags: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[1], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 1,
timestamp: 1,
flags: 1,
blinded_hints[1].0.htlc_maximum_msat = 2_8089_0861_1584_0000;
blinded_hints[1].0.cltv_expiry_delta = 0;
- let bolt12_features: Bolt12InvoiceFeatures = channelmanager::provided_invoice_features(&config).to_context();
+ let bolt12_features = channelmanager::provided_bolt12_invoice_features(&config);
let payment_params = PaymentParameters::blinded(blinded_hints.clone())
- .with_bolt12_features(bolt12_features.clone()).unwrap();
+ .with_bolt12_features(bolt12_features).unwrap();
let netgraph = network_graph.read_only();
let route_params = RouteParameters::from_payment_params_and_value(
];
blinded_hints[1].1.introduction_node_id = nodes[6];
- let bolt12_features: Bolt12InvoiceFeatures = channelmanager::provided_invoice_features(&config).to_context();
+ let bolt12_features = channelmanager::provided_bolt12_invoice_features(&config);
let payment_params = PaymentParameters::blinded(blinded_hints.clone())
.with_bolt12_features(bolt12_features.clone()).unwrap();
let route_params = RouteParameters::from_payment_params_and_value(
payment_params, amt_msat);
if let Err(LightningError { err, .. }) = get_route(
- &our_id, &route_params, &netgraph, None, Arc::clone(&logger), &scorer, &(), &random_seed_bytes
+ &our_id, &route_params, &netgraph, None, Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes
) {
assert_eq!(err, "Failed to find a path to the given destination");
} else { panic!() }
blinded_hints[2].1.introduction_node_id = nodes[6];
- let bolt12_features: Bolt12InvoiceFeatures = channelmanager::provided_invoice_features(&config).to_context();
+ let bolt12_features = channelmanager::provided_bolt12_invoice_features(&config);
let payment_params = PaymentParameters::blinded(blinded_hints.clone())
.with_bolt12_features(bolt12_features.clone()).unwrap();
let route_params = RouteParameters::from_payment_params_and_value(
payment_params, amt_msat);
if let Err(LightningError { err, .. }) = get_route(
- &our_id, &route_params, &netgraph, None, Arc::clone(&logger), &scorer, &(), &random_seed_bytes
+ &our_id, &route_params, &netgraph, None, Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes
) {
assert_eq!(err, "Failed to find a path to the given destination");
} else { panic!() }
cltv_expiry_delta: 0,
features: BlindedHopFeatures::empty(),
};
- let bolt12_features: Bolt12InvoiceFeatures = channelmanager::provided_invoice_features(&config).to_context();
+ let bolt12_features = channelmanager::provided_bolt12_invoice_features(&config);
PaymentParameters::blinded(vec![(blinded_payinfo, blinded_path)])
.with_bolt12_features(bolt12_features.clone()).unwrap()
} else {
PaymentParameters::from_node_id(nodes[1], 42)
.with_route_hints(vec![route_hint]).unwrap()
- .with_bolt11_features(channelmanager::provided_invoice_features(&config)).unwrap()
+ .with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config)).unwrap()
};
let netgraph = network_graph.read_only();
payment_params, amt_msat);
if let Err(LightningError { err, .. }) = get_route(
&our_id, &route_params, &netgraph, Some(&first_hops.iter().collect::<Vec<_>>()),
- Arc::clone(&logger), &scorer, &(), &random_seed_bytes
+ Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes
) {
assert_eq!(err, "Failed to find a path to the given destination");
} else { panic!() }
features: BlindedHopFeatures::empty(),
}, blinded_path.clone()));
}
- let bolt12_features: Bolt12InvoiceFeatures = channelmanager::provided_invoice_features(&config).to_context();
+ let bolt12_features = channelmanager::provided_bolt12_invoice_features(&config);
PaymentParameters::blinded(blinded_hints.clone())
.with_bolt12_features(bolt12_features.clone()).unwrap()
} else {
}
PaymentParameters::from_node_id(nodes[1], 42)
.with_route_hints(route_hints).unwrap()
- .with_bolt11_features(channelmanager::provided_invoice_features(&config)).unwrap()
+ .with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config)).unwrap()
};
let netgraph = network_graph.read_only();
let route = get_route(
&our_id, &route_params, &netgraph, Some(&first_hops.iter().collect::<Vec<_>>()),
- Arc::clone(&logger), &scorer, &(), &random_seed_bytes
+ Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes
).unwrap();
assert_eq!(route.paths.len(), 1);
assert_eq!(route.get_total_amount(), amt_msat);
add_channel(&gossip_sync, &secp_ctx, &privkeys[0], &privkeys[6], ChannelFeatures::from_le_bytes(id_to_feature_flags(6)), 6);
update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 6,
timestamp: 1,
flags: 0,
],
})
];
- let bolt12_features: Bolt12InvoiceFeatures = channelmanager::provided_invoice_features(&config).to_context();
+ let bolt12_features = channelmanager::provided_bolt12_invoice_features(&config);
let payment_params = PaymentParameters::blinded(blinded_hints.clone())
.with_bolt12_features(bolt12_features.clone()).unwrap();
let route_params = RouteParameters::from_payment_params_and_value(
features: BlindedHopFeatures::empty(),
}, blinded_path.clone()));
}
- let bolt12_features: Bolt12InvoiceFeatures = channelmanager::provided_invoice_features(&config).to_context();
+ let bolt12_features = channelmanager::provided_bolt12_invoice_features(&config);
PaymentParameters::blinded(blinded_hints.clone())
.with_bolt12_features(bolt12_features.clone()).unwrap()
};
assert_eq!(route.paths.len(), 1);
assert_eq!(route.get_total_amount(), amt_msat);
}
+
+ #[test]
+ fn first_hop_preferred_over_hint() {
+ // Check that if we have a first hop to a peer we'd always prefer that over a route hint
+ // they gave us, but we'd still consider all subsequent hints if they are more attractive.
+ let secp_ctx = Secp256k1::new();
+ let logger = Arc::new(ln_test_utils::TestLogger::new());
+ let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, Arc::clone(&logger)));
+ let gossip_sync = P2PGossipSync::new(Arc::clone(&network_graph), None, Arc::clone(&logger));
+ let scorer = ln_test_utils::TestScorer::new();
+ let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
+ let random_seed_bytes = keys_manager.get_secure_random_bytes();
+ let config = UserConfig::default();
+
+ let amt_msat = 1_000_000;
+ let (our_privkey, our_node_id, privkeys, nodes) = get_nodes(&secp_ctx);
+
+ add_channel(&gossip_sync, &secp_ctx, &our_privkey, &privkeys[0],
+ ChannelFeatures::from_le_bytes(id_to_feature_flags(1)), 1);
+ update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
+ short_channel_id: 1,
+ timestamp: 1,
+ flags: 0,
+ cltv_expiry_delta: 42,
+ htlc_minimum_msat: 1_000,
+ htlc_maximum_msat: 10_000_000,
+ fee_base_msat: 800,
+ fee_proportional_millionths: 0,
+ excess_data: Vec::new()
+ });
+ update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate {
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
+ short_channel_id: 1,
+ timestamp: 1,
+ flags: 1,
+ cltv_expiry_delta: 42,
+ htlc_minimum_msat: 1_000,
+ htlc_maximum_msat: 10_000_000,
+ fee_base_msat: 800,
+ fee_proportional_millionths: 0,
+ excess_data: Vec::new()
+ });
+
+ add_channel(&gossip_sync, &secp_ctx, &privkeys[0], &privkeys[1],
+ ChannelFeatures::from_le_bytes(id_to_feature_flags(1)), 2);
+ update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate {
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
+ short_channel_id: 2,
+ timestamp: 2,
+ flags: 0,
+ cltv_expiry_delta: 42,
+ htlc_minimum_msat: 1_000,
+ htlc_maximum_msat: 10_000_000,
+ fee_base_msat: 800,
+ fee_proportional_millionths: 0,
+ excess_data: Vec::new()
+ });
+ update_channel(&gossip_sync, &secp_ctx, &privkeys[1], UnsignedChannelUpdate {
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
+ short_channel_id: 2,
+ timestamp: 2,
+ flags: 1,
+ cltv_expiry_delta: 42,
+ htlc_minimum_msat: 1_000,
+ htlc_maximum_msat: 10_000_000,
+ fee_base_msat: 800,
+ fee_proportional_millionths: 0,
+ excess_data: Vec::new()
+ });
+
+ let dest_node_id = nodes[2];
+
+ let route_hint = RouteHint(vec![RouteHintHop {
+ src_node_id: our_node_id,
+ short_channel_id: 44,
+ fees: RoutingFees {
+ base_msat: 234,
+ proportional_millionths: 0,
+ },
+ cltv_expiry_delta: 10,
+ htlc_minimum_msat: None,
+ htlc_maximum_msat: Some(5_000_000),
+ },
+ RouteHintHop {
+ src_node_id: nodes[0],
+ short_channel_id: 45,
+ fees: RoutingFees {
+ base_msat: 123,
+ proportional_millionths: 0,
+ },
+ cltv_expiry_delta: 10,
+ htlc_minimum_msat: None,
+ htlc_maximum_msat: None,
+ }]);
+
+ let payment_params = PaymentParameters::from_node_id(dest_node_id, 42)
+ .with_route_hints(vec![route_hint]).unwrap()
+ .with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config)).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params, amt_msat);
+
+ // First create an insufficient first hop for channel with SCID 1 and check we'd use the
+ // route hint.
+ let first_hop = get_channel_details(Some(1), nodes[0],
+ channelmanager::provided_init_features(&config), 999_999);
+ let first_hops = vec![first_hop];
+
+ let route = get_route(&our_node_id, &route_params.clone(), &network_graph.read_only(),
+ Some(&first_hops.iter().collect::<Vec<_>>()), Arc::clone(&logger), &scorer,
+ &Default::default(), &random_seed_bytes).unwrap();
+ assert_eq!(route.paths.len(), 1);
+ assert_eq!(route.get_total_amount(), amt_msat);
+ assert_eq!(route.paths[0].hops.len(), 2);
+ assert_eq!(route.paths[0].hops[0].short_channel_id, 44);
+ assert_eq!(route.paths[0].hops[1].short_channel_id, 45);
+ assert_eq!(route.get_total_fees(), 123);
+
+ // Now check we would trust our first hop info, i.e., fail if we detect the route hint is
+ // for a first hop channel.
+ let mut first_hop = get_channel_details(Some(1), nodes[0], channelmanager::provided_init_features(&config), 999_999);
+ first_hop.outbound_scid_alias = Some(44);
+ let first_hops = vec![first_hop];
+
+ let route_res = get_route(&our_node_id, &route_params.clone(), &network_graph.read_only(),
+ Some(&first_hops.iter().collect::<Vec<_>>()), Arc::clone(&logger), &scorer,
+ &Default::default(), &random_seed_bytes);
+ assert!(route_res.is_err());
+
+ // Finally check we'd use the first hop if has sufficient outbound capacity. But we'd stil
+ // use the cheaper second hop of the route hint.
+ let mut first_hop = get_channel_details(Some(1), nodes[0],
+ channelmanager::provided_init_features(&config), 10_000_000);
+ first_hop.outbound_scid_alias = Some(44);
+ let first_hops = vec![first_hop];
+
+ let route = get_route(&our_node_id, &route_params.clone(), &network_graph.read_only(),
+ Some(&first_hops.iter().collect::<Vec<_>>()), Arc::clone(&logger), &scorer,
+ &Default::default(), &random_seed_bytes).unwrap();
+ assert_eq!(route.paths.len(), 1);
+ assert_eq!(route.get_total_amount(), amt_msat);
+ assert_eq!(route.paths[0].hops.len(), 2);
+ assert_eq!(route.paths[0].hops[0].short_channel_id, 1);
+ assert_eq!(route.paths[0].hops[1].short_channel_id, 45);
+ assert_eq!(route.get_total_fees(), 123);
+ }
+
+ #[test]
+ fn allow_us_being_first_hint() {
+ // Check that we consider a route hint even if we are the src of the first hop.
+ let secp_ctx = Secp256k1::new();
+ let logger = Arc::new(ln_test_utils::TestLogger::new());
+ let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, Arc::clone(&logger)));
+ let scorer = ln_test_utils::TestScorer::new();
+ let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
+ let random_seed_bytes = keys_manager.get_secure_random_bytes();
+ let config = UserConfig::default();
+
+ let (_, our_node_id, _, nodes) = get_nodes(&secp_ctx);
+
+ let amt_msat = 1_000_000;
+ let dest_node_id = nodes[1];
+
+ let first_hop = get_channel_details(Some(1), nodes[0], channelmanager::provided_init_features(&config), 10_000_000);
+ let first_hops = vec![first_hop];
+
+ let route_hint = RouteHint(vec![RouteHintHop {
+ src_node_id: our_node_id,
+ short_channel_id: 44,
+ fees: RoutingFees {
+ base_msat: 123,
+ proportional_millionths: 0,
+ },
+ cltv_expiry_delta: 10,
+ htlc_minimum_msat: None,
+ htlc_maximum_msat: None,
+ }]);
+
+ let payment_params = PaymentParameters::from_node_id(dest_node_id, 42)
+ .with_route_hints(vec![route_hint]).unwrap()
+ .with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config)).unwrap();
+
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params, amt_msat);
+
+
+ let route = get_route(&our_node_id, &route_params, &network_graph.read_only(),
+ Some(&first_hops.iter().collect::<Vec<_>>()), Arc::clone(&logger), &scorer,
+ &Default::default(), &random_seed_bytes).unwrap();
+
+ assert_eq!(route.paths.len(), 1);
+ assert_eq!(route.get_total_amount(), amt_msat);
+ assert_eq!(route.get_total_fees(), 0);
+ assert_eq!(route.paths[0].hops.len(), 1);
+
+ assert_eq!(route.paths[0].hops[0].short_channel_id, 44);
+ }
}
#[cfg(all(any(test, ldk_bench), not(feature = "no-std")))]
// Generate fail/success paths for a wider range of potential amounts with
// MPP enabled to give us a chance to apply penalties for more potential
// routes.
- let mpp_features = channelmanager::provided_invoice_features(&UserConfig::default());
+ let mpp_features = channelmanager::provided_bolt11_invoice_features(&UserConfig::default());
let params = PaymentParameters::from_node_id(dst, 42)
.with_bolt11_features(mpp_features).unwrap();
let route_params = RouteParameters::from_payment_params_and_value(
let network_graph = bench_utils::read_network_graph(&logger).unwrap();
let scorer = FixedPenaltyScorer::with_penalty(0);
generate_routes(bench, &network_graph, scorer, &Default::default(),
- channelmanager::provided_invoice_features(&UserConfig::default()), 0,
+ channelmanager::provided_bolt11_invoice_features(&UserConfig::default()), 0,
"generate_mpp_routes_with_zero_penalty_scorer");
}
let params = ProbabilisticScoringFeeParameters::default();
let scorer = ProbabilisticScorer::new(ProbabilisticScoringDecayParameters::default(), &network_graph, &logger);
generate_routes(bench, &network_graph, scorer, ¶ms,
- channelmanager::provided_invoice_features(&UserConfig::default()), 0,
+ channelmanager::provided_bolt11_invoice_features(&UserConfig::default()), 0,
"generate_mpp_routes_with_probabilistic_scorer");
}
let params = ProbabilisticScoringFeeParameters::default();
let scorer = ProbabilisticScorer::new(ProbabilisticScoringDecayParameters::default(), &network_graph, &logger);
generate_routes(bench, &network_graph, scorer, ¶ms,
- channelmanager::provided_invoice_features(&UserConfig::default()), 100_000_000,
+ channelmanager::provided_bolt11_invoice_features(&UserConfig::default()), 100_000_000,
"generate_large_mpp_routes_with_probabilistic_scorer");
}
let scorer = ProbabilisticScorer::new(
ProbabilisticScoringDecayParameters::default(), &network_graph, &logger);
generate_routes(bench, &network_graph, scorer, ¶ms,
- channelmanager::provided_invoice_features(&UserConfig::default()), 0,
+ channelmanager::provided_bolt11_invoice_features(&UserConfig::default()), 0,
"generate_routes_with_nonlinear_probabilistic_scorer");
}
let scorer = ProbabilisticScorer::new(
ProbabilisticScoringDecayParameters::default(), &network_graph, &logger);
generate_routes(bench, &network_graph, scorer, ¶ms,
- channelmanager::provided_invoice_features(&UserConfig::default()), 0,
+ channelmanager::provided_bolt11_invoice_features(&UserConfig::default()), 0,
"generate_mpp_routes_with_nonlinear_probabilistic_scorer");
}
let scorer = ProbabilisticScorer::new(
ProbabilisticScoringDecayParameters::default(), &network_graph, &logger);
generate_routes(bench, &network_graph, scorer, ¶ms,
- channelmanager::provided_invoice_features(&UserConfig::default()), 100_000_000,
+ channelmanager::provided_bolt11_invoice_features(&UserConfig::default()), 100_000_000,
"generate_large_mpp_routes_with_nonlinear_probabilistic_scorer");
}
use crate::util::ser::{ReadableArgs, Writeable};
use crate::util::test_utils::{self, TestLogger};
- use bitcoin::blockdata::constants::genesis_block;
+ use bitcoin::blockdata::constants::ChainHash;
use bitcoin::hashes::Hash;
use bitcoin::hashes::sha256d::Hash as Sha256dHash;
use bitcoin::network::constants::Network;
network_graph: &mut NetworkGraph<&TestLogger>, short_channel_id: u64, node_1_key: SecretKey,
node_2_key: SecretKey
) {
- let genesis_hash = genesis_block(Network::Testnet).header.block_hash();
+ let genesis_hash = ChainHash::using_genesis_block(Network::Testnet);
let node_1_secret = &SecretKey::from_slice(&[39; 32]).unwrap();
let node_2_secret = &SecretKey::from_slice(&[40; 32]).unwrap();
let secp_ctx = Secp256k1::new();
network_graph: &mut NetworkGraph<&TestLogger>, short_channel_id: u64, node_key: SecretKey,
flags: u8, htlc_maximum_msat: u64, timestamp: u32,
) {
- let genesis_hash = genesis_block(Network::Testnet).header.block_hash();
+ let genesis_hash = ChainHash::using_genesis_block(Network::Testnet);
let secp_ctx = Secp256k1::new();
let unsigned_update = UnsignedChannelUpdate {
chain_hash: genesis_hash,
use crate::util::test_utils;
use crate::util::ser::Writeable;
+use bitcoin::blockdata::constants::ChainHash;
use bitcoin::hashes::sha256d::Hash as Sha256dHash;
use bitcoin::hashes::Hash;
use bitcoin::network::constants::Network;
-use bitcoin::blockdata::constants::genesis_block;
use hex;
let unsigned_announcement = UnsignedChannelAnnouncement {
features,
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id,
node_id_1,
node_id_2,
add_channel(&gossip_sync, &secp_ctx, &cur_privkey, &next_privkey,
ChannelFeatures::from_le_bytes(id_to_feature_flags(1)), cur_short_channel_id);
update_channel(&gossip_sync, &secp_ctx, &cur_privkey, UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: cur_short_channel_id,
timestamp: idx as u32,
flags: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &next_privkey, UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: cur_short_channel_id,
timestamp: (idx as u32)+1,
flags: 1,
add_channel(&gossip_sync, &secp_ctx, &our_privkey, &privkeys[0], ChannelFeatures::from_le_bytes(id_to_feature_flags(1)), 1);
update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 1,
timestamp: 1,
flags: 1,
add_channel(&gossip_sync, &secp_ctx, &our_privkey, &privkeys[1], ChannelFeatures::from_le_bytes(id_to_feature_flags(2)), 2);
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 2,
timestamp: 1,
flags: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[1], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 2,
timestamp: 1,
flags: 1,
add_channel(&gossip_sync, &secp_ctx, &our_privkey, &privkeys[7], ChannelFeatures::from_le_bytes(id_to_feature_flags(12)), 12);
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 12,
timestamp: 1,
flags: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[7], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 12,
timestamp: 1,
flags: 1,
add_channel(&gossip_sync, &secp_ctx, &privkeys[0], &privkeys[2], ChannelFeatures::from_le_bytes(id_to_feature_flags(3)), 3);
update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 3,
timestamp: 1,
flags: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 3,
timestamp: 1,
flags: 1,
add_channel(&gossip_sync, &secp_ctx, &privkeys[1], &privkeys[2], ChannelFeatures::from_le_bytes(id_to_feature_flags(4)), 4);
update_channel(&gossip_sync, &secp_ctx, &privkeys[1], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 4,
timestamp: 1,
flags: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 4,
timestamp: 1,
flags: 1,
add_channel(&gossip_sync, &secp_ctx, &privkeys[7], &privkeys[2], ChannelFeatures::from_le_bytes(id_to_feature_flags(13)), 13);
update_channel(&gossip_sync, &secp_ctx, &privkeys[7], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 13,
timestamp: 1,
flags: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 13,
timestamp: 1,
flags: 1,
add_channel(&gossip_sync, &secp_ctx, &privkeys[2], &privkeys[4], ChannelFeatures::from_le_bytes(id_to_feature_flags(6)), 6);
update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 6,
timestamp: 1,
flags: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[4], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 6,
timestamp: 1,
flags: 1,
add_channel(&gossip_sync, &secp_ctx, &privkeys[4], &privkeys[3], ChannelFeatures::from_le_bytes(id_to_feature_flags(11)), 11);
update_channel(&gossip_sync, &secp_ctx, &privkeys[4], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 11,
timestamp: 1,
flags: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[3], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 11,
timestamp: 1,
flags: 1,
add_channel(&gossip_sync, &secp_ctx, &privkeys[2], &privkeys[5], ChannelFeatures::from_le_bytes(id_to_feature_flags(7)), 7);
update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 7,
timestamp: 1,
flags: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[5], UnsignedChannelUpdate {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 7,
timestamp: 1,
flags: 1,
//! channel matches a UTXO on-chain, requiring at least some marginal on-chain transacting in
//! order to announce a channel. This module handles that checking.
-use bitcoin::{BlockHash, TxOut};
+use bitcoin::TxOut;
+use bitcoin::blockdata::constants::ChainHash;
use bitcoin::hashes::hex::ToHex;
use crate::events::MessageSendEvent;
/// The `UtxoLookup` trait defines behavior for accessing on-chain UTXOs.
pub trait UtxoLookup {
/// Returns the transaction output of a funding transaction encoded by [`short_channel_id`].
- /// Returns an error if `genesis_hash` is for a different chain or if such a transaction output
- /// is unknown.
+ /// Returns an error if `chain_hash` is for a different chain or if such a transaction output is
+ /// unknown.
///
/// [`short_channel_id`]: https://github.com/lightning/bolts/blob/master/07-routing-gossip.md#definition-of-short_channel_id
- fn get_utxo(&self, genesis_hash: &BlockHash, short_channel_id: u64) -> UtxoResult;
+ fn get_utxo(&self, chain_hash: &ChainHash, short_channel_id: u64) -> UtxoResult;
}
enum ChannelAnnouncement {
/// once we have a concrete resolution of a request.
pub(crate) struct UtxoResolver(Result<TxOut, UtxoLookupError>);
impl UtxoLookup for UtxoResolver {
- fn get_utxo(&self, _genesis_hash: &BlockHash, _short_channel_id: u64) -> UtxoResult {
+ fn get_utxo(&self, _chain_hash: &ChainHash, _short_channel_id: u64) -> UtxoResult {
UtxoResult::Sync(self.0.clone())
}
}
use bitcoin::secp256k1::ecdh::SharedSecret;
use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature};
use bitcoin::secp256k1::schnorr;
-use bitcoin::{PackedLockTime, secp256k1, Sequence, Witness};
+use bitcoin::{PackedLockTime, secp256k1, Sequence, Witness, Txid};
use crate::util::transaction_utils;
use crate::util::crypto::{hkdf_extract_expand_twice, sign, sign_with_aux_rand};
use crate::util::ser::{Writeable, Writer, Readable, ReadableArgs};
use crate::chain::transaction::OutPoint;
-use crate::events::bump_transaction::HTLCDescriptor;
use crate::ln::channel::ANCHOR_OUTPUT_VALUE_SATOSHI;
use crate::ln::{chan_utils, PaymentPreimage};
use crate::ln::chan_utils::{HTLCOutputInCommitment, make_funding_redeemscript, ChannelPublicKeys, HolderCommitmentTransaction, ChannelTransactionParameters, CommitmentTransaction, ClosingTransaction};
}
}
+/// The parameters required to derive a channel signer via [`SignerProvider`].
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct ChannelDerivationParameters {
+ /// The value in satoshis of the channel we're attempting to spend the anchor output of.
+ pub value_satoshis: u64,
+ /// The unique identifier to re-derive the signer for the associated channel.
+ pub keys_id: [u8; 32],
+ /// The necessary channel parameters that need to be provided to the re-derived signer through
+ /// [`ChannelSigner::provide_channel_parameters`].
+ pub transaction_parameters: ChannelTransactionParameters,
+}
+
+impl_writeable_tlv_based!(ChannelDerivationParameters, {
+ (0, value_satoshis, required),
+ (2, keys_id, required),
+ (4, transaction_parameters, required),
+});
+
+/// A descriptor used to sign for a commitment transaction's HTLC output.
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct HTLCDescriptor {
+ /// The parameters required to derive the signer for the HTLC input.
+ pub channel_derivation_parameters: ChannelDerivationParameters,
+ /// The txid of the commitment transaction in which the HTLC output lives.
+ pub commitment_txid: Txid,
+ /// The number of the commitment transaction in which the HTLC output lives.
+ pub per_commitment_number: u64,
+ /// The key tweak corresponding to the number of the commitment transaction in which the HTLC
+ /// output lives. This tweak is applied to all the basepoints for both parties in the channel to
+ /// arrive at unique keys per commitment.
+ ///
+ /// See <https://github.com/lightning/bolts/blob/master/03-transactions.md#keys> for more info.
+ pub per_commitment_point: PublicKey,
+ /// The feerate to use on the HTLC claiming transaction. This is always `0` for HTLCs
+ /// originating from a channel supporting anchor outputs, otherwise it is the channel's
+ /// negotiated feerate at the time the commitment transaction was built.
+ pub feerate_per_kw: u32,
+ /// The details of the HTLC as it appears in the commitment transaction.
+ pub htlc: HTLCOutputInCommitment,
+ /// The preimage, if `Some`, to claim the HTLC output with. If `None`, the timeout path must be
+ /// taken.
+ pub preimage: Option<PaymentPreimage>,
+ /// The counterparty's signature required to spend the HTLC output.
+ pub counterparty_sig: Signature
+}
+
+impl_writeable_tlv_based!(HTLCDescriptor, {
+ (0, channel_derivation_parameters, required),
+ (1, feerate_per_kw, (default_value, 0)),
+ (2, commitment_txid, required),
+ (4, per_commitment_number, required),
+ (6, per_commitment_point, required),
+ (8, htlc, required),
+ (10, preimage, option),
+ (12, counterparty_sig, required),
+});
+
+impl HTLCDescriptor {
+ /// Returns the outpoint of the HTLC output in the commitment transaction. This is the outpoint
+ /// being spent by the HTLC input in the HTLC transaction.
+ pub fn outpoint(&self) -> bitcoin::OutPoint {
+ bitcoin::OutPoint {
+ txid: self.commitment_txid,
+ vout: self.htlc.transaction_output_index.unwrap(),
+ }
+ }
+
+ /// Returns the UTXO to be spent by the HTLC input, which can be obtained via
+ /// [`Self::unsigned_tx_input`].
+ pub fn previous_utxo<C: secp256k1::Signing + secp256k1::Verification>(&self, secp: &Secp256k1<C>) -> TxOut {
+ TxOut {
+ script_pubkey: self.witness_script(secp).to_v0_p2wsh(),
+ value: self.htlc.amount_msat / 1000,
+ }
+ }
+
+ /// Returns the unsigned transaction input spending the HTLC output in the commitment
+ /// transaction.
+ pub fn unsigned_tx_input(&self) -> TxIn {
+ chan_utils::build_htlc_input(
+ &self.commitment_txid, &self.htlc, &self.channel_derivation_parameters.transaction_parameters.channel_type_features
+ )
+ }
+
+ /// Returns the delayed output created as a result of spending the HTLC output in the commitment
+ /// transaction.
+ pub fn tx_output<C: secp256k1::Signing + secp256k1::Verification>(&self, secp: &Secp256k1<C>) -> TxOut {
+ let channel_params = self.channel_derivation_parameters.transaction_parameters.as_holder_broadcastable();
+ let broadcaster_keys = channel_params.broadcaster_pubkeys();
+ let counterparty_keys = channel_params.countersignatory_pubkeys();
+ let broadcaster_delayed_key = chan_utils::derive_public_key(
+ secp, &self.per_commitment_point, &broadcaster_keys.delayed_payment_basepoint
+ );
+ let counterparty_revocation_key = chan_utils::derive_public_revocation_key(
+ secp, &self.per_commitment_point, &counterparty_keys.revocation_basepoint
+ );
+ chan_utils::build_htlc_output(
+ self.feerate_per_kw, channel_params.contest_delay(), &self.htlc,
+ channel_params.channel_type_features(), &broadcaster_delayed_key, &counterparty_revocation_key
+ )
+ }
+
+ /// Returns the witness script of the HTLC output in the commitment transaction.
+ pub fn witness_script<C: secp256k1::Signing + secp256k1::Verification>(&self, secp: &Secp256k1<C>) -> Script {
+ let channel_params = self.channel_derivation_parameters.transaction_parameters.as_holder_broadcastable();
+ let broadcaster_keys = channel_params.broadcaster_pubkeys();
+ let counterparty_keys = channel_params.countersignatory_pubkeys();
+ let broadcaster_htlc_key = chan_utils::derive_public_key(
+ secp, &self.per_commitment_point, &broadcaster_keys.htlc_basepoint
+ );
+ let counterparty_htlc_key = chan_utils::derive_public_key(
+ secp, &self.per_commitment_point, &counterparty_keys.htlc_basepoint
+ );
+ let counterparty_revocation_key = chan_utils::derive_public_revocation_key(
+ secp, &self.per_commitment_point, &counterparty_keys.revocation_basepoint
+ );
+ chan_utils::get_htlc_redeemscript_with_explicit_keys(
+ &self.htlc, channel_params.channel_type_features(), &broadcaster_htlc_key, &counterparty_htlc_key,
+ &counterparty_revocation_key,
+ )
+ }
+
+ /// Returns the fully signed witness required to spend the HTLC output in the commitment
+ /// transaction.
+ pub fn tx_input_witness(&self, signature: &Signature, witness_script: &Script) -> Witness {
+ chan_utils::build_htlc_input_witness(
+ signature, &self.counterparty_sig, &self.preimage, witness_script,
+ &self.channel_derivation_parameters.transaction_parameters.channel_type_features
+ )
+ }
+
+ /// Derives the channel signer required to sign the HTLC input.
+ pub fn derive_channel_signer<S: WriteableEcdsaChannelSigner, SP: Deref>(&self, signer_provider: &SP) -> S
+ where
+ SP::Target: SignerProvider<Signer = S>
+ {
+ let mut signer = signer_provider.derive_channel_signer(
+ self.channel_derivation_parameters.value_satoshis,
+ self.channel_derivation_parameters.keys_id,
+ );
+ signer.provide_channel_parameters(&self.channel_derivation_parameters.transaction_parameters);
+ signer
+ }
+}
+
/// A trait to handle Lightning channel key material without concretizing the channel type or
/// the signature mechanism.
pub trait ChannelSigner {
/// This is required in order for the signer to make sure that the state has moved
/// forward and it is safe to sign the next counterparty commitment.
fn validate_counterparty_revocation(&self, idx: u64, secret: &SecretKey) -> Result<(), ()>;
- /// Creates a signature for a holder's commitment transaction and its claiming HTLC transactions.
+ /// Creates a signature for a holder's commitment transaction.
///
/// This will be called
/// - with a non-revoked `commitment_tx`.
/// - with the latest `commitment_tx` when we initiate a force-close.
- /// - with the previous `commitment_tx`, just to get claiming HTLC
- /// signatures, if we are reacting to a [`ChannelMonitor`]
- /// [replica](https://github.com/lightningdevkit/rust-lightning/blob/main/GLOSSARY.md#monitor-replicas)
- /// that decided to broadcast before it had been updated to the latest `commitment_tx`.
///
/// This may be called multiple times for the same transaction.
///
/// An external signer implementation should check that the commitment has not been revoked.
- ///
- /// [`ChannelMonitor`]: crate::chain::channelmonitor::ChannelMonitor
+ //
// TODO: Document the things someone using this interface should enforce before signing.
- fn sign_holder_commitment_and_htlcs(&self, commitment_tx: &HolderCommitmentTransaction,
- secp_ctx: &Secp256k1<secp256k1::All>) -> Result<(Signature, Vec<Signature>), ()>;
- /// Same as [`sign_holder_commitment_and_htlcs`], but exists only for tests to get access to
- /// holder commitment transactions which will be broadcasted later, after the channel has moved
- /// on to a newer state. Thus, needs its own method as [`sign_holder_commitment_and_htlcs`] may
- /// enforce that we only ever get called once.
+ fn sign_holder_commitment(&self, commitment_tx: &HolderCommitmentTransaction,
+ secp_ctx: &Secp256k1<secp256k1::All>) -> Result<Signature, ()>;
+ /// Same as [`sign_holder_commitment`], but exists only for tests to get access to holder
+ /// commitment transactions which will be broadcasted later, after the channel has moved on to a
+ /// newer state. Thus, needs its own method as [`sign_holder_commitment`] may enforce that we
+ /// only ever get called once.
#[cfg(any(test,feature = "unsafe_revoked_tx_signing"))]
- fn unsafe_sign_holder_commitment_and_htlcs(&self, commitment_tx: &HolderCommitmentTransaction,
- secp_ctx: &Secp256k1<secp256k1::All>) -> Result<(Signature, Vec<Signature>), ()>;
+ fn unsafe_sign_holder_commitment(&self, commitment_tx: &HolderCommitmentTransaction,
+ secp_ctx: &Secp256k1<secp256k1::All>) -> Result<Signature, ()>;
/// Create a signature for the given input in a transaction spending an HTLC transaction output
/// or a commitment transaction `to_local` output when our counterparty broadcasts an old state.
///
secp_ctx: &Secp256k1<secp256k1::All>) -> Result<Signature, ()>;
/// Computes the signature for a commitment transaction's HTLC output used as an input within
/// `htlc_tx`, which spends the commitment transaction at index `input`. The signature returned
- /// must be be computed using [`EcdsaSighashType::All`]. Note that this should only be used to
- /// sign HTLC transactions from channels supporting anchor outputs after all additional
- /// inputs/outputs have been added to the transaction.
+ /// must be be computed using [`EcdsaSighashType::All`].
+ ///
+ /// Note that this may be called for HTLCs in the penultimate commitment transaction if a
+ /// [`ChannelMonitor`] [replica](https://github.com/lightningdevkit/rust-lightning/blob/main/GLOSSARY.md#monitor-replicas)
+ /// broadcasts it before receiving the update for the latest commitment transaction.
///
/// [`EcdsaSighashType::All`]: bitcoin::blockdata::transaction::EcdsaSighashType::All
+ /// [`ChannelMonitor`]: crate::chain::channelmonitor::ChannelMonitor
fn sign_holder_htlc_transaction(&self, htlc_tx: &Transaction, input: usize,
htlc_descriptor: &HTLCDescriptor, secp_ctx: &Secp256k1<secp256k1::All>
) -> Result<Signature, ()>;
Ok(())
}
- fn sign_holder_commitment_and_htlcs(&self, commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1<secp256k1::All>) -> Result<(Signature, Vec<Signature>), ()> {
+ fn sign_holder_commitment(&self, commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1<secp256k1::All>) -> Result<Signature, ()> {
let funding_pubkey = PublicKey::from_secret_key(secp_ctx, &self.funding_key);
let counterparty_keys = self.counterparty_pubkeys().expect(MISSING_PARAMS_ERR);
let funding_redeemscript = make_funding_redeemscript(&funding_pubkey, &counterparty_keys.funding_pubkey);
let trusted_tx = commitment_tx.trust();
- let sig = trusted_tx.built_transaction().sign_holder_commitment(&self.funding_key, &funding_redeemscript, self.channel_value_satoshis, &self, secp_ctx);
- let channel_parameters = self.get_channel_parameters().expect(MISSING_PARAMS_ERR);
- let htlc_sigs = trusted_tx.get_htlc_sigs(&self.htlc_base_key, &channel_parameters.as_holder_broadcastable(), &self, secp_ctx)?;
- Ok((sig, htlc_sigs))
+ Ok(trusted_tx.built_transaction().sign_holder_commitment(&self.funding_key, &funding_redeemscript, self.channel_value_satoshis, &self, secp_ctx))
}
#[cfg(any(test,feature = "unsafe_revoked_tx_signing"))]
- fn unsafe_sign_holder_commitment_and_htlcs(&self, commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1<secp256k1::All>) -> Result<(Signature, Vec<Signature>), ()> {
+ fn unsafe_sign_holder_commitment(&self, commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1<secp256k1::All>) -> Result<Signature, ()> {
let funding_pubkey = PublicKey::from_secret_key(secp_ctx, &self.funding_key);
let counterparty_keys = self.counterparty_pubkeys().expect(MISSING_PARAMS_ERR);
let funding_redeemscript = make_funding_redeemscript(&funding_pubkey, &counterparty_keys.funding_pubkey);
let trusted_tx = commitment_tx.trust();
- let sig = trusted_tx.built_transaction().sign_holder_commitment(&self.funding_key, &funding_redeemscript, self.channel_value_satoshis, &self, secp_ctx);
- let channel_parameters = self.get_channel_parameters().expect(MISSING_PARAMS_ERR);
- let htlc_sigs = trusted_tx.get_htlc_sigs(&self.htlc_base_key, &channel_parameters.as_holder_broadcastable(), &self, secp_ctx)?;
- Ok((sig, htlc_sigs))
+ Ok(trusted_tx.built_transaction().sign_holder_commitment(&self.funding_key, &funding_redeemscript, self.channel_value_satoshis, &self, secp_ctx))
}
fn sign_justice_revoked_output(&self, justice_tx: &Transaction, input: usize, amount: u64, per_commitment_key: &SecretKey, secp_ctx: &Secp256k1<secp256k1::All>) -> Result<Signature, ()> {
/// funder/initiator.
///
/// When we are the funder, because we have to pay the channel closing fee, we bound the
- /// acceptable fee by our [`Background`] and [`Normal`] fees, with the upper bound increased by
+ /// acceptable fee by our [`ChannelCloseMinimum`] and [`NonAnchorChannelFee`] fees, with the upper bound increased by
/// this value. Because the on-chain fee we'd pay to force-close the channel is kept near our
- /// [`Normal`] feerate during normal operation, this value represents the additional fee we're
+ /// [`NonAnchorChannelFee`] feerate during normal operation, this value represents the additional fee we're
/// willing to pay in order to avoid waiting for our counterparty's to_self_delay to reclaim our
/// funds.
///
/// When we are not the funder, we require the closing transaction fee pay at least our
- /// [`Background`] fee estimate, but allow our counterparty to pay as much fee as they like.
+ /// [`ChannelCloseMinimum`] fee estimate, but allow our counterparty to pay as much fee as they like.
/// Thus, this value is ignored when we are not the funder.
///
/// Default value: 1000 satoshis.
///
- /// [`Normal`]: crate::chain::chaininterface::ConfirmationTarget::Normal
- /// [`Background`]: crate::chain::chaininterface::ConfirmationTarget::Background
+ /// [`NonAnchorChannelFee`]: crate::chain::chaininterface::ConfirmationTarget::NonAnchorChannelFee
+ /// [`ChannelCloseMinimum`]: crate::chain::chaininterface::ConfirmationTarget::ChannelCloseMinimum
pub force_close_avoidance_max_fee_satoshis: u64,
/// If set, allows this channel's counterparty to skim an additional fee off this node's inbound
/// HTLCs. Useful for liquidity providers to offload on-chain channel costs to end users.
/// 3) payments intended to be intercepted will route using a fake scid (this is typically used so
/// the forwarding node can open a JIT channel to the next hop)
pub(crate) mod fake_scid {
- use bitcoin::hash_types::BlockHash;
- use bitcoin::hashes::hex::FromHex;
+ use bitcoin::blockdata::constants::ChainHash;
+ use bitcoin::network::constants::Network;
use crate::sign::EntropySource;
use crate::util::chacha20::ChaCha20;
use crate::util::scid_utils;
/// between segwit activation and the current best known height, and the tx index and output
/// index are also selected from a "reasonable" range. We add this logic because it makes it
/// non-obvious at a glance that the scid is fake, e.g. if it appears in invoice route hints.
- pub(crate) fn get_fake_scid<ES: Deref>(&self, highest_seen_blockheight: u32, genesis_hash: &BlockHash, fake_scid_rand_bytes: &[u8; 32], entropy_source: &ES) -> u64
+ pub(crate) fn get_fake_scid<ES: Deref>(&self, highest_seen_blockheight: u32, chain_hash: &ChainHash, fake_scid_rand_bytes: &[u8; 32], entropy_source: &ES) -> u64
where ES::Target: EntropySource,
{
// Ensure we haven't created a namespace that doesn't fit into the 3 bits we've allocated for
assert!((*self as u8) < MAX_NAMESPACES);
let rand_bytes = entropy_source.get_secure_random_bytes();
- let segwit_activation_height = segwit_activation_height(genesis_hash);
+ let segwit_activation_height = segwit_activation_height(chain_hash);
let mut blocks_since_segwit_activation = highest_seen_blockheight.saturating_sub(segwit_activation_height);
// We want to ensure that this fake channel won't conflict with any transactions we haven't
}
}
- fn segwit_activation_height(genesis: &BlockHash) -> u32 {
- const MAINNET_GENESIS_STR: &'static str = "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f";
- if BlockHash::from_hex(MAINNET_GENESIS_STR).unwrap() == *genesis {
+ fn segwit_activation_height(chain_hash: &ChainHash) -> u32 {
+ if *chain_hash == ChainHash::using_genesis_block(Network::Bitcoin) {
MAINNET_SEGWIT_ACTIVATION_HEIGHT
} else {
TEST_SEGWIT_ACTIVATION_HEIGHT
}
/// Returns whether the given fake scid falls into the phantom namespace.
- pub fn is_valid_phantom(fake_scid_rand_bytes: &[u8; 32], scid: u64, genesis_hash: &BlockHash) -> bool {
+ pub fn is_valid_phantom(fake_scid_rand_bytes: &[u8; 32], scid: u64, chain_hash: &ChainHash) -> bool {
let block_height = scid_utils::block_from_scid(&scid);
let tx_index = scid_utils::tx_index_from_scid(&scid);
let namespace = Namespace::Phantom;
let valid_vout = namespace.get_encrypted_vout(block_height, tx_index, fake_scid_rand_bytes);
- block_height >= segwit_activation_height(genesis_hash)
+ block_height >= segwit_activation_height(chain_hash)
&& valid_vout == scid_utils::vout_from_scid(&scid) as u8
}
/// Returns whether the given fake scid falls into the intercept namespace.
- pub fn is_valid_intercept(fake_scid_rand_bytes: &[u8; 32], scid: u64, genesis_hash: &BlockHash) -> bool {
+ pub fn is_valid_intercept(fake_scid_rand_bytes: &[u8; 32], scid: u64, chain_hash: &ChainHash) -> bool {
let block_height = scid_utils::block_from_scid(&scid);
let tx_index = scid_utils::tx_index_from_scid(&scid);
let namespace = Namespace::Intercept;
let valid_vout = namespace.get_encrypted_vout(block_height, tx_index, fake_scid_rand_bytes);
- block_height >= segwit_activation_height(genesis_hash)
+ block_height >= segwit_activation_height(chain_hash)
&& valid_vout == scid_utils::vout_from_scid(&scid) as u8
}
#[cfg(test)]
mod tests {
- use bitcoin::blockdata::constants::genesis_block;
+ use bitcoin::blockdata::constants::ChainHash;
use bitcoin::network::constants::Network;
use crate::util::scid_utils::fake_scid::{is_valid_intercept, is_valid_phantom, MAINNET_SEGWIT_ACTIVATION_HEIGHT, MAX_TX_INDEX, MAX_NAMESPACES, Namespace, NAMESPACE_ID_BITMASK, segwit_activation_height, TEST_SEGWIT_ACTIVATION_HEIGHT};
use crate::util::scid_utils;
#[test]
fn test_segwit_activation_height() {
- let mainnet_genesis = genesis_block(Network::Bitcoin).header.block_hash();
+ let mainnet_genesis = ChainHash::using_genesis_block(Network::Bitcoin);
assert_eq!(segwit_activation_height(&mainnet_genesis), MAINNET_SEGWIT_ACTIVATION_HEIGHT);
- let testnet_genesis = genesis_block(Network::Testnet).header.block_hash();
+ let testnet_genesis = ChainHash::using_genesis_block(Network::Testnet);
assert_eq!(segwit_activation_height(&testnet_genesis), TEST_SEGWIT_ACTIVATION_HEIGHT);
- let signet_genesis = genesis_block(Network::Signet).header.block_hash();
+ let signet_genesis = ChainHash::using_genesis_block(Network::Signet);
assert_eq!(segwit_activation_height(&signet_genesis), TEST_SEGWIT_ACTIVATION_HEIGHT);
- let regtest_genesis = genesis_block(Network::Regtest).header.block_hash();
+ let regtest_genesis = ChainHash::using_genesis_block(Network::Regtest);
assert_eq!(segwit_activation_height(®test_genesis), TEST_SEGWIT_ACTIVATION_HEIGHT);
}
fn test_is_valid_phantom() {
let namespace = Namespace::Phantom;
let fake_scid_rand_bytes = [0; 32];
- let testnet_genesis = genesis_block(Network::Testnet).header.block_hash();
+ let testnet_genesis = ChainHash::using_genesis_block(Network::Testnet);
let valid_encrypted_vout = namespace.get_encrypted_vout(0, 0, &fake_scid_rand_bytes);
let valid_fake_scid = scid_utils::scid_from_parts(1, 0, valid_encrypted_vout as u64).unwrap();
assert!(is_valid_phantom(&fake_scid_rand_bytes, valid_fake_scid, &testnet_genesis));
fn test_is_valid_intercept() {
let namespace = Namespace::Intercept;
let fake_scid_rand_bytes = [0; 32];
- let testnet_genesis = genesis_block(Network::Testnet).header.block_hash();
+ let testnet_genesis = ChainHash::using_genesis_block(Network::Testnet);
let valid_encrypted_vout = namespace.get_encrypted_vout(0, 0, &fake_scid_rand_bytes);
let valid_fake_scid = scid_utils::scid_from_parts(1, 0, valid_encrypted_vout as u64).unwrap();
assert!(is_valid_intercept(&fake_scid_rand_bytes, valid_fake_scid, &testnet_genesis));
#[test]
fn test_get_fake_scid() {
- let mainnet_genesis = genesis_block(Network::Bitcoin).header.block_hash();
+ let mainnet_genesis = ChainHash::using_genesis_block(Network::Bitcoin);
let seed = [0; 32];
let fake_scid_rand_bytes = [1; 32];
let keys_manager = Arc::new(test_utils::TestKeysInterface::new(&seed, Network::Testnet));
(&self.0).len() as u8
}
}
+
+impl core::fmt::Display for Hostname {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ write!(f, "{}", self.0)?;
+ Ok(())
+ }
+}
impl Deref for Hostname {
type Target = String;
use bitcoin::secp256k1;
use bitcoin::secp256k1::{SecretKey, PublicKey};
use bitcoin::secp256k1::{Secp256k1, ecdsa::Signature};
-use crate::events::bump_transaction::HTLCDescriptor;
+use crate::sign::HTLCDescriptor;
use crate::util::ser::{Writeable, Writer};
use crate::io::Error;
use crate::ln::features::ChannelTypeFeatures;
Ok(())
}
- fn sign_holder_commitment_and_htlcs(&self, commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1<secp256k1::All>) -> Result<(Signature, Vec<Signature>), ()> {
+ fn sign_holder_commitment(&self, commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1<secp256k1::All>) -> Result<Signature, ()> {
let trusted_tx = self.verify_holder_commitment_tx(commitment_tx, secp_ctx);
- let commitment_txid = trusted_tx.txid();
- let holder_csv = self.inner.counterparty_selected_contest_delay().unwrap();
-
let state = self.state.lock().unwrap();
let commitment_number = trusted_tx.commitment_number();
if state.last_holder_revoked_commitment - 1 != commitment_number && state.last_holder_revoked_commitment - 2 != commitment_number {
state.last_holder_revoked_commitment, commitment_number, self.inner.commitment_seed[0])
}
}
-
- for (this_htlc, sig) in trusted_tx.htlcs().iter().zip(&commitment_tx.counterparty_htlc_sigs) {
- assert!(this_htlc.transaction_output_index.is_some());
- let keys = trusted_tx.keys();
- let htlc_tx = chan_utils::build_htlc_transaction(&commitment_txid, trusted_tx.feerate_per_kw(), holder_csv, &this_htlc, self.channel_type_features(), &keys.broadcaster_delayed_payment_key, &keys.revocation_key);
-
- let htlc_redeemscript = chan_utils::get_htlc_redeemscript(&this_htlc, self.channel_type_features(), &keys);
-
- let sighash_type = if self.channel_type_features().supports_anchors_zero_fee_htlc_tx() {
- EcdsaSighashType::SinglePlusAnyoneCanPay
- } else {
- EcdsaSighashType::All
- };
- let sighash = hash_to_message!(
- &sighash::SighashCache::new(&htlc_tx).segwit_signature_hash(
- 0, &htlc_redeemscript, this_htlc.amount_msat / 1000, sighash_type,
- ).unwrap()[..]
- );
- secp_ctx.verify_ecdsa(&sighash, sig, &keys.countersignatory_htlc_key).unwrap();
- }
-
- Ok(self.inner.sign_holder_commitment_and_htlcs(commitment_tx, secp_ctx).unwrap())
+ Ok(self.inner.sign_holder_commitment(commitment_tx, secp_ctx).unwrap())
}
#[cfg(any(test,feature = "unsafe_revoked_tx_signing"))]
- fn unsafe_sign_holder_commitment_and_htlcs(&self, commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1<secp256k1::All>) -> Result<(Signature, Vec<Signature>), ()> {
- Ok(self.inner.unsafe_sign_holder_commitment_and_htlcs(commitment_tx, secp_ctx).unwrap())
+ fn unsafe_sign_holder_commitment(&self, commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1<secp256k1::All>) -> Result<Signature, ()> {
+ Ok(self.inner.unsafe_sign_holder_commitment(commitment_tx, secp_ctx).unwrap())
}
fn sign_justice_revoked_output(&self, justice_tx: &Transaction, input: usize, amount: u64, per_commitment_key: &SecretKey, secp_ctx: &Secp256k1<secp256k1::All>) -> Result<Signature, ()> {
&self, htlc_tx: &Transaction, input: usize, htlc_descriptor: &HTLCDescriptor,
secp_ctx: &Secp256k1<secp256k1::All>
) -> Result<Signature, ()> {
+ let state = self.state.lock().unwrap();
+ if state.last_holder_revoked_commitment - 1 != htlc_descriptor.per_commitment_number &&
+ state.last_holder_revoked_commitment - 2 != htlc_descriptor.per_commitment_number
+ {
+ if !self.disable_revocation_policy_check {
+ panic!("can only sign the next two unrevoked commitment numbers, revoked={} vs requested={} for {}",
+ state.last_holder_revoked_commitment, htlc_descriptor.per_commitment_number, self.inner.commitment_seed[0])
+ }
+ }
assert_eq!(htlc_tx.input[input], htlc_descriptor.unsigned_tx_input());
assert_eq!(htlc_tx.output[input], htlc_descriptor.tx_output(secp_ctx));
+ {
+ let witness_script = htlc_descriptor.witness_script(secp_ctx);
+ let sighash_type = if self.channel_type_features().supports_anchors_zero_fee_htlc_tx() {
+ EcdsaSighashType::SinglePlusAnyoneCanPay
+ } else {
+ EcdsaSighashType::All
+ };
+ let sighash = &sighash::SighashCache::new(&*htlc_tx).segwit_signature_hash(
+ input, &witness_script, htlc_descriptor.htlc.amount_msat / 1000, sighash_type
+ ).unwrap();
+ let countersignatory_htlc_key = chan_utils::derive_public_key(
+ &secp_ctx, &htlc_descriptor.per_commitment_point, &self.inner.counterparty_pubkeys().unwrap().htlc_basepoint
+ );
+ secp_ctx.verify_ecdsa(
+ &hash_to_message!(&sighash), &htlc_descriptor.counterparty_sig, &countersignatory_htlc_key
+ ).unwrap();
+ }
Ok(self.inner.sign_holder_htlc_transaction(htlc_tx, input, htlc_descriptor, secp_ctx).unwrap())
}
pub sat_per_kw: Mutex<u32>,
}
impl chaininterface::FeeEstimator for TestFeeEstimator {
- fn get_est_sat_per_1000_weight(&self, _confirmation_target: ConfirmationTarget) -> u32 {
- *self.sat_per_kw.lock().unwrap()
+ fn get_est_sat_per_1000_weight(&self, confirmation_target: ConfirmationTarget) -> u32 {
+ match confirmation_target {
+ ConfirmationTarget::MaxAllowedNonAnchorChannelRemoteFee => {
+ core::cmp::max(25 * 250, *self.sat_per_kw.lock().unwrap() * 10)
+ }
+ _ => *self.sat_per_kw.lock().unwrap(),
+ }
}
}
if let Some((find_route_query, find_route_res)) = self.next_routes.lock().unwrap().pop_front() {
assert_eq!(find_route_query, *params);
if let Ok(ref route) = find_route_res {
- assert_eq!(route.route_params.as_ref().unwrap().final_value_msat, find_route_query.final_value_msat);
+ assert_eq!(route.route_params, Some(find_route_query));
let scorer = self.scorer.read().unwrap();
let scorer = ScorerAccountingForInFlightHtlcs::new(scorer, &inflight_htlcs);
for path in &route.paths {
expected_recv_msgs: Mutex<Option<Vec<wire::Message<()>>>>,
connected_peers: Mutex<HashSet<PublicKey>>,
pub message_fetch_counter: AtomicUsize,
- genesis_hash: ChainHash,
+ chain_hash: ChainHash,
}
impl TestChannelMessageHandler {
- pub fn new(genesis_hash: ChainHash) -> Self {
+ pub fn new(chain_hash: ChainHash) -> Self {
TestChannelMessageHandler {
pending_events: Mutex::new(Vec::new()),
expected_recv_msgs: Mutex::new(None),
connected_peers: Mutex::new(HashSet::new()),
message_fetch_counter: AtomicUsize::new(0),
- genesis_hash,
+ chain_hash,
}
}
channelmanager::provided_init_features(&UserConfig::default())
}
- fn get_genesis_hashes(&self) -> Option<Vec<ChainHash>> {
- Some(vec![self.genesis_hash])
+ fn get_chain_hashes(&self) -> Option<Vec<ChainHash>> {
+ Some(vec![self.chain_hash])
}
fn handle_open_channel_v2(&self, _their_node_id: &PublicKey, msg: &msgs::OpenChannelV2) {
let node_2_btckey = SecretKey::from_slice(&[39; 32]).unwrap();
let unsigned_ann = msgs::UnsignedChannelAnnouncement {
features: ChannelFeatures::empty(),
- chain_hash: genesis_block(network).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(network),
short_channel_id: short_chan_id,
node_id_1: NodeId::from_pubkey(&PublicKey::from_secret_key(&secp_ctx, &node_1_privkey)),
node_id_2: NodeId::from_pubkey(&PublicKey::from_secret_key(&secp_ctx, &node_2_privkey)),
msgs::ChannelUpdate {
signature: Signature::from(unsafe { FFISignature::new() }),
contents: msgs::UnsignedChannelUpdate {
- chain_hash: genesis_block(network).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(network),
short_channel_id: short_chan_id,
timestamp: 0,
flags: 0,
pending_events.push(events::MessageSendEvent::SendGossipTimestampFilter {
node_id: their_node_id.clone(),
msg: msgs::GossipTimestampFilter {
- chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ chain_hash: ChainHash::using_genesis_block(Network::Testnet),
first_timestamp: gossip_start_time as u32,
timestamp_range: u32::max_value(),
},
fn log(&self, record: &Record) {
*self.lines.lock().unwrap().entry((record.module_path.to_string(), format!("{}", record.args))).or_insert(0) += 1;
if record.level >= self.level {
- #[cfg(all(not(ldk_bench), feature = "std"))]
- println!("{:<5} {} [{} : {}, {}] {}", record.level.to_string(), self.id, record.module_path, record.file, record.line, record.args);
+ #[cfg(all(not(ldk_bench), feature = "std"))] {
+ let pfx = format!("{} {} [{}:{}]", self.id, record.level.to_string(), record.module_path, record.line);
+ println!("{:<55}{}", pfx, record.args);
+ }
}
}
}
}
pub struct TestChainSource {
- pub genesis_hash: BlockHash,
+ pub chain_hash: ChainHash,
pub utxo_ret: Mutex<UtxoResult>,
pub get_utxo_call_count: AtomicUsize,
pub watched_txn: Mutex<HashSet<(Txid, Script)>>,
pub fn new(network: Network) -> Self {
let script_pubkey = Builder::new().push_opcode(opcodes::OP_TRUE).into_script();
Self {
- genesis_hash: genesis_block(network).block_hash(),
+ chain_hash: ChainHash::using_genesis_block(network),
utxo_ret: Mutex::new(UtxoResult::Sync(Ok(TxOut { value: u64::max_value(), script_pubkey }))),
get_utxo_call_count: AtomicUsize::new(0),
watched_txn: Mutex::new(HashSet::new()),
}
impl UtxoLookup for TestChainSource {
- fn get_utxo(&self, genesis_hash: &BlockHash, _short_channel_id: u64) -> UtxoResult {
+ fn get_utxo(&self, chain_hash: &ChainHash, _short_channel_id: u64) -> UtxoResult {
self.get_utxo_call_count.fetch_add(1, Ordering::Relaxed);
- if self.genesis_hash != *genesis_hash {
+ if self.chain_hash != *chain_hash {
return UtxoResult::Sync(Err(UtxoLookupError::UnknownChain));
}
+++ /dev/null
-## Backwards Compatibility
-
-* Creating a blinded path to receive a payment over and then downgrading to a version of LDK prior
- to 0.0.117 may result in failure to receive the payment (#2413).
+++ /dev/null
-## Backwards Compatibility
-
-* If an `Event::InvoiceRequestFailed` was generated for a BOLT 12 payment (#2371), downgrading will result in the payment silently failing if the event had not been processed yet.