include:
- toolchain: stable
platform: ubuntu-latest
- coverage: true
# 1.48.0 is the MSRV for all crates except lightning-transaction-sync and Win/Mac
- toolchain: 1.48.0
platform: ubuntu-latest
- name: Checkout source code
uses: actions/checkout@v3
- name: Install Rust ${{ matrix.toolchain }} toolchain
- uses: actions-rs/toolchain@v1
- with:
- toolchain: ${{ matrix.toolchain }}
- override: true
- profile: minimal
+ run: |
+ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile=minimal --default-toolchain ${{ matrix.toolchain }}
+ rustup override set ${{ matrix.toolchain }}
- name: Install no-std-check dependencies for ARM Embedded
if: "matrix.platform == 'ubuntu-latest'"
run: |
run: |
sudo apt-get -y install shellcheck
shellcheck ci/ci-tests.sh
- - name: Run CI script with coverage generation
- if: matrix.coverage
- shell: bash # Default on Winblows is powershell
- run: LDK_COVERAGE_BUILD=true ./ci/ci-tests.sh
- name: Run CI script
- if: "!matrix.coverage"
shell: bash # Default on Winblows is powershell
run: ./ci/ci-tests.sh
- - name: Install deps for kcov
- if: matrix.coverage
- run: |
- sudo apt-get update
- sudo apt-get -y install binutils-dev libcurl4-openssl-dev zlib1g-dev libdw-dev libiberty-dev
- - name: Install kcov
- if: matrix.coverage
- run: |
- wget https://github.com/SimonKagstrom/kcov/archive/master.tar.gz
- tar xzf master.tar.gz
- cd kcov-master && mkdir build && cd build
- cmake ..
- make
- make install DESTDIR=../../kcov-build
- cd ../.. && rm -rf kcov-master master.tar.gz
- - name: Generate coverage report
- if: matrix.coverage
- run: |
- for file in target/debug/deps/lightning*; do
- [ -x "${file}" ] || continue;
- mkdir -p "target/cov/$(basename $file)";
- ./kcov-build/usr/local/bin/kcov --exclude-pattern=/.cargo,/usr/lib --verify "target/cov/$(basename $file)" "$file";
- done
- - name: Upload coverage
- if: matrix.coverage
- uses: codecov/codecov-action@v3
+
+ coverage:
+ strategy:
+ fail-fast: false
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout source code
+ uses: actions/checkout@v3
with:
+ fetch-depth: 0
+ - name: Install Rust stable toolchain
+ run: |
+ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile=minimal
+ - name: Run tests with coverage generation
+ run: |
+ cargo install cargo-llvm-cov
+ export RUSTFLAGS="-Clink-dead-code -Coverflow-checks=off"
+ cargo llvm-cov --features rest-client,rpc-client,tokio,futures,serde --codecov --hide-instantiations --output-path=target/codecov.json
# Could you use this to fake the coverage report for your PR? Sure.
# Will anyone be impressed by your amazing coverage? No
# Maybe if codecov wasn't broken we wouldn't need to do this...
- token: f421b687-4dc2-4387-ac3d-dc3b2528af57
- fail_ci_if_error: true
+ bash <(curl -s https://codecov.io/bash) -f target/codecov.json -t "f421b687-4dc2-4387-ac3d-dc3b2528af57"
benchmark:
runs-on: ubuntu-latest
- name: Checkout source code
uses: actions/checkout@v3
- name: Install Rust ${{ env.TOOLCHAIN }} toolchain
- uses: actions-rs/toolchain@v1
- with:
- toolchain: ${{ env.TOOLCHAIN }}
- override: true
- profile: minimal
+ run: |
+ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile=minimal --default-toolchain ${{ env.TOOLCHAIN }}
+ rustup override set ${{ env.TOOLCHAIN }}
- name: Cache routing graph snapshot
id: cache-graph
uses: actions/cache@v3
check_commits:
runs-on: ubuntu-latest
env:
- TOOLCHAIN: 1.57.0
+ TOOLCHAIN: stable
steps:
- name: Checkout source code
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Install Rust ${{ env.TOOLCHAIN }} toolchain
- uses: actions-rs/toolchain@v1
- with:
- toolchain: ${{ env.TOOLCHAIN }}
- override: true
- profile: minimal
+ run: |
+ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile=minimal --default-toolchain ${{ env.TOOLCHAIN }}
+ rustup override set ${{ env.TOOLCHAIN }}
- name: Fetch full tree and rebase on upstream
run: |
git remote add upstream https://github.com/lightningdevkit/rust-lightning
with:
fetch-depth: 0
- name: Install Rust ${{ env.TOOLCHAIN }} toolchain
- uses: actions-rs/toolchain@v1
- with:
- toolchain: ${{ env.TOOLCHAIN }}
- override: true
- profile: minimal
+ run: |
+ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile=minimal --default-toolchain ${{ env.TOOLCHAIN }}
+ rustup override set ${{ env.TOOLCHAIN }}
- name: Run cargo check for release build.
run: |
cargo check --release
fuzz:
runs-on: ubuntu-latest
env:
- TOOLCHAIN: stable
+ TOOLCHAIN: 1.58
steps:
- name: Checkout source code
uses: actions/checkout@v3
- - name: Install Rust 1.58 toolchain
- uses: actions-rs/toolchain@v1
- with:
- toolchain: 1.58
- override: true
- profile: minimal
+ - name: Install Rust ${{ env.TOOLCHAIN }} toolchain
+ run: |
+ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile=minimal --default-toolchain ${{ env.TOOLCHAIN }}
+ rustup override set ${{ env.TOOLCHAIN }}
- name: Install dependencies for honggfuzz
run: |
sudo apt-get update
- name: Checkout source code
uses: actions/checkout@v3
- name: Install Rust ${{ env.TOOLCHAIN }} toolchain
- uses: actions-rs/toolchain@v1
- with:
- toolchain: ${{ env.TOOLCHAIN }}
- override: true
- profile: minimal
+ run: |
+ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile=minimal --default-toolchain ${{ env.TOOLCHAIN }}
+ rustup override set ${{ env.TOOLCHAIN }}
- name: Install clippy
run: |
rustup component add clippy
+# 0.0.116 - Jul 21, 2023 - "Anchoring the Roadmap"
+
+## API Updates
+
+ * Support for zero-HTLC-fee anchor output channels has been added and is now
+ considered beta (#2367). Users who set
+ `ChannelHandshakeConfig::negotiate_anchors_zero_fee_htlc_tx` should be
+ prepared to handle the new `Event::BumpTransaction`, e.g. via the
+ `BumpTransactionEventHandler` (#2089). Note that in order to do so you must
+ ensure you always have a reserve of available unspent on-chain funds to use
+ for CPFP. LDK currently makes no attempt to ensure this for you.
+ * Users who set `ChannelHandshakeConfig::negotiate_anchors_zero_fee_htlc_tx`
+ and wish to accept inbound anchor-based channels must do so manually by
+ setting `UserConfig::manually_accept_inbound_channels` (#2368).
+ * Support forwarding and accepting HTLCs with a reduced amount has been added,
+ to support LSPs skimming a fee on the penultimate hop (#2319).
+ * BOLT11 and BOLT12 Invoice and related types have been renamed to include a
+ BOLTNN prefix, ensuring uniqueness in `lightning{,-invoice}` crates (#2416).
+ * `Score`rs now have an associated type which represents a parameter passed
+ when calculating penalties. This allows for the same `Score`r to be used with
+ different penalty calculation parameters (#2237).
+ * `DefaultRouter` is no longer restrained to a `Mutex`-wrapped `Score`,
+ allowing it to be used in `no-std` builds (#2383).
+ * `CustomMessageHandler::provided_{node,init}_features` and various custom
+ feature bit methods on `*Features` were added (#2204).
+ * Keysend/push payments using MPP are now supported when receiving if
+ `UserConfig::accept_mpp_keysend` is set and when sending if specified in the
+ `PaymentParameters`. Note that not all recipients support this (#2156).
+ * A new `ConfirmationTarget::MempoolMinimum` has been added (#2415).
+ * `SpendableOutputDescriptor::to_psbt_input` was added (#2286).
+ * `ChannelManager::update_partial_channel_config` was added (#2330).
+ * `ChannelDetails::channel_shutdown_state` was added (#2347).
+ * The shutdown script can now be provided at shutdown time via
+ `ChannelManager::close_channel_with_feerate_and_script` (#2219).
+ * `BroadcasterInterface` now takes multiple transactions at once. While not
+ available today, in the future single calls should be passed to a full node
+ via a single batch/package transaction acceptance API (#2272).
+ * `Balance::claimable_amount_satoshis` was added (#2333).
+ * `payment_{hash,preimage}` have been added to some `Balance` variants (#2217).
+ * The `lightning::chain::keysinterface` is now `lightning::sign` (#2246).
+ * Routing to a blinded path has been implemented, though sending to such a
+ route is not yet supported in `ChannelManager` (#2120).
+ * `OffersMessageHandler` was added for offers-related onion messages (#2294).
+ * The `CustomMessageHandler` parameter to `PeerManager` has moved to
+ `MessageHandler` from `PeerManager::new` explicitly (#2249).
+ * Various P2P messages for dual funding channel establishment have been added,
+ though handling for them is not yet in `ChannelManager` (#1794)
+ * Script-fetching methods in `sign` interfaces can now return errors, see docs
+ for the implications of failing (#2213).
+ * The `data_loss_protect` option is now required when reading
+ `channel_reestablish` messages, as many others have done (#2253).
+ * `InFlightHtlcs::add_inflight_htlc` has been added (#2042).
+ * The `init` message `networks` field is now written and checked (#2329).
+ * `PeerManager` generics have been simplified with the introduction of the
+ `APeerManager` trait (#2249).
+ * `ParitalOrd` and `Ord` are now implemented for `Invoice` (#2279).
+ * `ParitalEq` and `Debug` are now implemented for `InMemorySigner` (#2328).
+ * `ParitalEq` and `Eq` are now implemented for `PaymentError` (#2316).
+ * `NetworkGraph::update_channel_from_announcement_no_lookup` was added (#2222).
+ * `lightning::routing::gossip::verify_{channel,node}_announcement` was added
+ (#2307).
+
+## Backwards Compatibility
+ * `PaymentParameters` written with blinded path info using LDK 0.0.115 will not
+ be readable in LDK 0.0.116, and vice versa.
+ * Forwarding less than `Event::HTLCIntercepted::expected_outbound_amount_msat`
+ in `ChannelManager::forward_intercepted_htlc` may prevent the
+ `ChannelManager` from being read by LDK prior to 0.0.116 (#2319)
+ * Setting `ChannelConfig::accept_underpaying_htlcs` may prevent the
+ `ChannelManager` from being read by LDK prior to 0.0.116 and un-setting the
+ parameter between restarts may lead to payment failures (#2319).
+ * `ChannelManager::create_inbound_payment{,_for_hash}_legacy` has been removed,
+ removing the ability to create inbound payments which are claimable after
+ downgrade to LDK 0.0.103 and prior. In the future handling such payments will
+ also be removed (#2351).
+ * Some fields required by LDK 0.0.103 and earlier are no longer written, thus
+ deserializing objects written by 0.0.116 with 0.0.103 may now fail (#2351).
+
+## Bug Fixes
+ * `ChannelDetails::next_outbound_htlc_limit_msat` was made substantially more
+ accurate and a corresponding `next_outbound_htlc_minimum_msat` was added.
+ This resolves issues where unpayable routes were generated due to
+ overestimation of the amount which is payable over one of our channels as
+ the first hop (#2312).
+ * A rare case where delays in processing `Event`s generated by
+ `ChannelMonitor`s could lead to loss of those events in case of an untimely
+ crash. This could lead to the loss of an `Event::SpendableOutputs` (#2369).
+ * Fixed a regression in 0.0.115 which caused `PendingHTLCsForwardable` events
+ to be missed when processing phantom node receives. This caused such
+ payments to be delayed until a further, unrelated HTLC came in (#2395).
+ * Peers which are unresponsive to channel messages for several timer ticks are
+ now disconnected to allow for on-reconnection state machine reset. This
+ works around some issues in LND prior to 16.3 which can cause channels to
+ hang and eventually force-close (#2293).
+ * `ChannelManager::new` now requires the current time (either from a recent
+ block header or the system clock), ensuring invoices created immediately
+ after startup aren't already expired (#2372).
+ * Resolved an issue where reading a `ProbabilisticScorer` on some platforms
+ (e.g. iOS) can lead to a panic (#2322).
+ * `ChannelConfig::max_dust_htlc_exposure` is now allowed to scale based on
+ current fees, and the default has been updated to do so. This substantially
+ reduces the chance of force-closure due to dust exposure. Note that existing
+ channels will retain their current value and you may wish to update the
+ value on your existing channels on upgrade (#2354).
+ * `PeerManager::process_events` no longer blocks in any case. This fixes a bug
+ where reentrancy from `PeerManager` into user code which eventually calls
+ `process_events` could lead to a deadlock (#2280).
+ * The persist timing of network graph and scoring in
+ `lightning-background-processor` has been tweaked to provide more reliable
+ persistence after updates to either (#2226).
+ * The number of route hints added to BOLT 11 invoices by the
+ `lightning-invoice::utils` builders has been reduced to three to ensure
+ invoices can be represented in scan-able QR codes (#2044).
+ * Fixed sending large onion messages, which would previously have resulted in
+ an HMAC error on the second hop (#2277).
+ * Fixed a memory leak that may occur when a `ChannelManager` or
+ `ChannelMonitor` is `drop`ed (#2233).
+ * A potential deadlock in calling `NetworkGraph::eq` was resolved (#2284).
+ * Fixed an overflow which prevented disconnecting peers in some minor cases
+ with more than 31 peers (#2245).
+ * Gossip messages with an unknown chain hash are now ignored (#2230).
+ * Rapid Gossip Sync processing now fails on an unknown chain hash (#2324).
+ * `RouteHintHop::htlc_maximum_msat` is now enforced. Note that BOLT11 route
+ hints do not have such a field so this code is generally unused (#2305).
+
+## Security
+0.0.116 fixes a denial-of-service vulnerability which is reachable from
+untrusted input from channel counterparties if a 0-conf channel exists with
+that counterparty.
+ * A premature `announcement_signatures` message from a peer prior to a 0-conf
+ channel's funding transaction receiving any confirmations would panic in any
+ version since 0-conf channels were introduced (#2439).
+
+In total, this release features 142 files changed, 21033 insertions, 11066
+deletions in 327 commits from 21 authors, in alphabetical order:
+ * Alec Chen
+ * Andrei
+ * Antoine Riard
+ * Arik Sosman
+ * Chad Upjohn
+ * Daniel Granhão
+ * Duncan Dean
+ * Elias Rohrer
+ * Fred Walker
+ * Gleb Naumenko
+ * Jeffrey Czyz
+ * Martin Habovstiak
+ * Matt Corallo
+ * Tony Giorgio
+ * Valentine Wallace
+ * Vladimir Fomene
+ * Willem Van Lint
+ * Wilmer Paulino
+ * benthecarman
+ * ff
+ * henghonglee
+
+
# 0.0.115 - Apr 24, 2023 - "Rebroadcast the Bugfixes"
## API Updates
"lightning-custom-message",
"lightning-transaction-sync",
"no-std-check",
+ "msrv-no-dev-deps-check",
"bench",
]
lightning::routing::router::benches::generate_routes_with_probabilistic_scorer,
lightning::routing::router::benches::generate_mpp_routes_with_probabilistic_scorer,
lightning::routing::router::benches::generate_large_mpp_routes_with_probabilistic_scorer,
+ lightning::routing::router::benches::generate_routes_with_nonlinear_probabilistic_scorer,
+ lightning::routing::router::benches::generate_mpp_routes_with_nonlinear_probabilistic_scorer,
+ lightning::routing::router::benches::generate_large_mpp_routes_with_nonlinear_probabilistic_scorer,
lightning::sign::benches::bench_get_secure_random_bytes,
lightning::ln::channelmanager::bench::bench_sends,
- lightning_persister::bench::bench_sends,
+ lightning_persister::fs_store::bench::bench_sends,
lightning_rapid_gossip_sync::bench::bench_reading_full_graph_from_file,
lightning::routing::gossip::benches::read_network_graph,
lightning::routing::gossip::benches::write_network_graph);
cargo doc --document-private-items
cd fuzz && RUSTFLAGS="--cfg=fuzzing" cargo check --features=stdin_fuzz
cd ../lightning && cargo check --no-default-features --features=no-std
+cd .. && RUSTC_BOOTSTRAP=1 RUSTFLAGS="--cfg=c_bindings" cargo check -Z avoid-dev-deps
RUSTC_MINOR_VERSION=$(rustc --version | awk '{ split($2,a,"."); print a[2] }')
HOST_PLATFORM="$(rustc --version --verbose | grep "host:" | awk '{ print $2 }')"
-# Tokio MSRV on versions 1.17 through 1.26 is rustc 1.49. Above 1.26 MSRV is 1.56.
-[ "$RUSTC_MINOR_VERSION" -lt 49 ] && cargo update -p tokio --precise "1.14.1" --verbose
-[[ "$RUSTC_MINOR_VERSION" -gt 48 && "$RUSTC_MINOR_VERSION" -lt 56 ]] && cargo update -p tokio --precise "1.25.1" --verbose
+# Some crates require pinning to meet our MSRV even for our downstream users,
+# which we do here.
+# Further crates which appear only as dev-dependencies are pinned further down.
+function PIN_RELEASE_DEPS {
+ # Tokio MSRV on versions 1.17 through 1.26 is rustc 1.49. Above 1.26 MSRV is 1.56.
+ [ "$RUSTC_MINOR_VERSION" -lt 49 ] && cargo update -p tokio --precise "1.14.1" --verbose
+ [[ "$RUSTC_MINOR_VERSION" -gt 48 && "$RUSTC_MINOR_VERSION" -lt 56 ]] && cargo update -p tokio --precise "1.25.1" --verbose
-# Sadly the log crate is always a dependency of tokio until 1.20, and has no reasonable MSRV guarantees
-[ "$RUSTC_MINOR_VERSION" -lt 49 ] && cargo update -p log --precise "0.4.18" --verbose
+ # Sadly the log crate is always a dependency of tokio until 1.20, and has no reasonable MSRV guarantees
+ [ "$RUSTC_MINOR_VERSION" -lt 49 ] && cargo update -p log --precise "0.4.18" --verbose
+
+ # The serde_json crate switched to Rust edition 2021 starting with v1.0.101, i.e., has MSRV of 1.56
+ [ "$RUSTC_MINOR_VERSION" -lt 56 ] && cargo update -p serde_json --precise "1.0.100" --verbose
+
+ return 0 # Don't fail the script if our rustc is higher than the last check
+}
+
+PIN_RELEASE_DEPS # pin the release dependencies in our main workspace
# The addr2line v0.20 crate (a dependency of `backtrace` starting with 0.3.68) relies on 1.55+
[ "$RUSTC_MINOR_VERSION" -lt 55 ] && cargo update -p backtrace --precise "0.3.67" --verbose
-[ "$LDK_COVERAGE_BUILD" != "" ] && export RUSTFLAGS="-C link-dead-code"
+# The quote crate switched to Rust edition 2021 starting with v1.0.31, i.e., has MSRV of 1.56
+[ "$RUSTC_MINOR_VERSION" -lt 56 ] && cargo update -p quote --precise "1.0.30" --verbose
+
+# The syn crate depends on too-new proc-macro2 starting with v2.0.33, i.e., has MSRV of 1.56
+if [ "$RUSTC_MINOR_VERSION" -lt 56 ]; then
+ SYN_2_DEP=$(grep -o '"syn 2.*' Cargo.lock | tr -d '",' | tr ' ' ':')
+ cargo update -p "$SYN_2_DEP" --precise "2.0.32" --verbose
+fi
+
+# The proc-macro2 crate switched to Rust edition 2021 starting with v1.0.66, i.e., has MSRV of 1.56
+[ "$RUSTC_MINOR_VERSION" -lt 56 ] && cargo update -p proc-macro2 --precise "1.0.65" --verbose
+
+# The memchr crate switched to an MSRV of 1.60 starting with v2.6.0
+[ "$RUSTC_MINOR_VERSION" -lt 60 ] && cargo update -p memchr --precise "2.5.0" --verbose
export RUST_BACKTRACE=1
echo -e "\n\nBuilding and testing all workspace crates..."
-cargo build --verbose --color always
cargo test --verbose --color always
+cargo check --verbose --color always
-echo -e "\n\nBuilding with all Log-Limiting features"
-pushd lightning
-grep '^max_level_' Cargo.toml | awk '{ print $1 }'| while read -r FEATURE; do
- cargo build --verbose --color always --features "$FEATURE"
-done
+echo -e "\n\nBuilding and testing Block Sync Clients with features"
+pushd lightning-block-sync
+cargo test --verbose --color always --features rest-client
+cargo check --verbose --color always --features rest-client
+cargo test --verbose --color always --features rpc-client
+cargo check --verbose --color always --features rpc-client
+cargo test --verbose --color always --features rpc-client,rest-client
+cargo check --verbose --color always --features rpc-client,rest-client
+cargo test --verbose --color always --features rpc-client,rest-client,tokio
+cargo check --verbose --color always --features rpc-client,rest-client,tokio
+popd
+
+if [[ $RUSTC_MINOR_VERSION -gt 67 && "$HOST_PLATFORM" != *windows* ]]; then
+ echo -e "\n\nBuilding and testing Transaction Sync Clients with features"
+ pushd lightning-transaction-sync
+ cargo test --verbose --color always --features esplora-blocking
+ cargo check --verbose --color always --features esplora-blocking
+ cargo test --verbose --color always --features esplora-async
+ cargo check --verbose --color always --features esplora-async
+ cargo test --verbose --color always --features esplora-async-https
+ cargo check --verbose --color always --features esplora-async-https
+ popd
+fi
+
+echo -e "\n\nTest futures builds"
+pushd lightning-background-processor
+cargo test --verbose --color always --features futures
popd
+if [ "$RUSTC_MINOR_VERSION" -gt 55 ]; then
+ echo -e "\n\nTest Custom Message Macros"
+ pushd lightning-custom-message
+ cargo test --verbose --color always
+ popd
+fi
+
if [ "$RUSTC_MINOR_VERSION" -gt 51 ]; then # Current `object` MSRV, subject to change
echo -e "\n\nTest backtrace-debug builds"
pushd lightning
popd
fi
+echo -e "\n\nBuilding with all Log-Limiting features"
+pushd lightning
+grep '^max_level_' Cargo.toml | awk '{ print $1 }'| while read -r FEATURE; do
+ cargo check --verbose --color always --features "$FEATURE"
+done
+popd
+
echo -e "\n\nTesting no-std flags in various combinations"
for DIR in lightning lightning-invoice lightning-rapid-gossip-sync; do
pushd $DIR
echo -e "\n\nTesting no-std build on a downstream no-std crate"
# check no-std compatibility across dependencies
pushd no-std-check
-cargo check --verbose --color always --features lightning-transaction-sync
-popd
-
-if [ -f "$(which arm-none-eabi-gcc)" ]; then
- pushd no-std-check
- cargo build --target=thumbv7m-none-eabi
- popd
+if [[ $RUSTC_MINOR_VERSION -gt 67 ]]; then
+ # lightning-transaction-sync's MSRV is 1.67
+ cargo check --verbose --color always --features lightning-transaction-sync
+else
+ # The memchr crate switched to an MSRV of 1.60 starting with v2.6.0
+ # This is currently only a release dependency via core2, which we intend to work with
+ # rust-bitcoin to remove soon.
+ [ "$RUSTC_MINOR_VERSION" -lt 60 ] && cargo update -p memchr --precise "2.5.0" --verbose
+ cargo check --verbose --color always
fi
-
-echo -e "\n\nBuilding and testing Block Sync Clients with features"
-pushd lightning-block-sync
-cargo build --verbose --color always --features rest-client
-cargo test --verbose --color always --features rest-client
-cargo build --verbose --color always --features rpc-client
-cargo test --verbose --color always --features rpc-client
-cargo build --verbose --color always --features rpc-client,rest-client
-cargo test --verbose --color always --features rpc-client,rest-client
-cargo build --verbose --color always --features rpc-client,rest-client,tokio
-cargo test --verbose --color always --features rpc-client,rest-client,tokio
popd
-if [[ $RUSTC_MINOR_VERSION -gt 67 && "$HOST_PLATFORM" != *windows* ]]; then
- echo -e "\n\nBuilding and testing Transaction Sync Clients with features"
- pushd lightning-transaction-sync
- cargo build --verbose --color always --features esplora-blocking
- cargo test --verbose --color always --features esplora-blocking
- cargo build --verbose --color always --features esplora-async
- cargo test --verbose --color always --features esplora-async
- cargo build --verbose --color always --features esplora-async-https
- cargo test --verbose --color always --features esplora-async-https
- popd
-fi
-
-echo -e "\n\nTest futures builds"
-pushd lightning-background-processor
-cargo test --verbose --color always --features futures
+# Test that we can build downstream code with only the "release pins".
+pushd msrv-no-dev-deps-check
+PIN_RELEASE_DEPS
+cargo check
popd
-if [ "$RUSTC_MINOR_VERSION" -gt 55 ]; then
- echo -e "\n\nTest Custom Message Macros"
- pushd lightning-custom-message
- cargo test --verbose --color always
+if [ -f "$(which arm-none-eabi-gcc)" ]; then
+ pushd no-std-check
+ cargo build --target=thumbv7m-none-eabi
popd
fi
--- /dev/null
+// This file is Copyright its original authors, visible in version control
+// history.
+//
+// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
+// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// You may not use this file except in accordance with one or both of these
+// licenses.
+
+use lightning::util::base32;
+
+use crate::utils::test_logger;
+
+#[inline]
+pub fn do_test(data: &[u8]) {
+ if let Ok(s) = std::str::from_utf8(data) {
+ let first_decoding = base32::Alphabet::RFC4648 { padding: true }.decode(s);
+ if let Ok(first_decoding) = first_decoding {
+ let encoding_response = base32::Alphabet::RFC4648 { padding: true }.encode(&first_decoding);
+ assert_eq!(encoding_response, s.to_ascii_uppercase());
+ let second_decoding = base32::Alphabet::RFC4648 { padding: true }.decode(&encoding_response).unwrap();
+ assert_eq!(first_decoding, second_decoding);
+ }
+ }
+
+ if let Ok(s) = std::str::from_utf8(data) {
+ let first_decoding = base32::Alphabet::RFC4648 { padding: false }.decode(s);
+ if let Ok(first_decoding) = first_decoding {
+ let encoding_response = base32::Alphabet::RFC4648 { padding: false }.encode(&first_decoding);
+ assert_eq!(encoding_response, s.to_ascii_uppercase());
+ let second_decoding = base32::Alphabet::RFC4648 { padding: false }.decode(&encoding_response).unwrap();
+ assert_eq!(first_decoding, second_decoding);
+ }
+ }
+
+ let encode_response = base32::Alphabet::RFC4648 { padding: false }.encode(&data);
+ let decode_response = base32::Alphabet::RFC4648 { padding: false }.decode(&encode_response).unwrap();
+ assert_eq!(data, decode_response);
+
+ let encode_response = base32::Alphabet::RFC4648 { padding: true }.encode(&data);
+ let decode_response = base32::Alphabet::RFC4648 { padding: true }.decode(&encode_response).unwrap();
+ assert_eq!(data, decode_response);
+}
+
+pub fn base32_test<Out: test_logger::Output>(data: &[u8], _out: Out) {
+ do_test(data);
+}
+
+#[no_mangle]
+pub extern "C" fn base32_run(data: *const u8, datalen: usize) {
+ do_test(unsafe { std::slice::from_raw_parts(data, datalen) });
+}
use crate::utils::test_logger;
use core::convert::TryFrom;
-use lightning::offers::parse::{Bech32Encode, ParseError};
+use lightning::offers::parse::{Bech32Encode, Bolt12ParseError};
#[inline]
pub fn do_test<Out: test_logger::Output>(data: &[u8], _out: Out) {
}
impl TryFrom<Vec<u8>> for Bytes {
- type Error = ParseError;
- fn try_from(data: Vec<u8>) -> Result<Self, ParseError> {
+ type Error = Bolt12ParseError;
+ fn try_from(data: Vec<u8>) -> Result<Self, Bolt12ParseError> {
Ok(Bytes(data))
}
}
--- /dev/null
+// This file is Copyright its original authors, visible in version control
+// history.
+//
+// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
+// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// You may not use this file except in accordance with one or both of these
+// licenses.
+
+// This file is auto-generated by gen_target.sh based on target_template.txt
+// To modify it, modify target_template.txt and run gen_target.sh instead.
+
+#![cfg_attr(feature = "libfuzzer_fuzz", no_main)]
+
+#[cfg(not(fuzzing))]
+compile_error!("Fuzz targets need cfg=fuzzing");
+
+extern crate lightning_fuzz;
+use lightning_fuzz::base32::*;
+
+#[cfg(feature = "afl")]
+#[macro_use] extern crate afl;
+#[cfg(feature = "afl")]
+fn main() {
+ fuzz!(|data| {
+ base32_run(data.as_ptr(), data.len());
+ });
+}
+
+#[cfg(feature = "honggfuzz")]
+#[macro_use] extern crate honggfuzz;
+#[cfg(feature = "honggfuzz")]
+fn main() {
+ loop {
+ fuzz!(|data| {
+ base32_run(data.as_ptr(), data.len());
+ });
+ }
+}
+
+#[cfg(feature = "libfuzzer_fuzz")]
+#[macro_use] extern crate libfuzzer_sys;
+#[cfg(feature = "libfuzzer_fuzz")]
+fuzz_target!(|data: &[u8]| {
+ base32_run(data.as_ptr(), data.len());
+});
+
+#[cfg(feature = "stdin_fuzz")]
+fn main() {
+ use std::io::Read;
+
+ let mut data = Vec::with_capacity(8192);
+ std::io::stdin().read_to_end(&mut data).unwrap();
+ base32_run(data.as_ptr(), data.len());
+}
+
+#[test]
+fn run_test_cases() {
+ use std::fs;
+ use std::io::Read;
+ use lightning_fuzz::utils::test_logger::StringBuffer;
+
+ use std::sync::{atomic, Arc};
+ {
+ let data: Vec<u8> = vec![0];
+ base32_run(data.as_ptr(), data.len());
+ }
+ let mut threads = Vec::new();
+ let threads_running = Arc::new(atomic::AtomicUsize::new(0));
+ if let Ok(tests) = fs::read_dir("test_cases/base32") {
+ for test in tests {
+ let mut data: Vec<u8> = Vec::new();
+ let path = test.unwrap().path();
+ fs::File::open(&path).unwrap().read_to_end(&mut data).unwrap();
+ threads_running.fetch_add(1, atomic::Ordering::AcqRel);
+
+ let thread_count_ref = Arc::clone(&threads_running);
+ let main_thread_ref = std::thread::current();
+ threads.push((path.file_name().unwrap().to_str().unwrap().to_string(),
+ std::thread::spawn(move || {
+ let string_logger = StringBuffer::new();
+
+ let panic_logger = string_logger.clone();
+ let res = if ::std::panic::catch_unwind(move || {
+ base32_test(&data, panic_logger);
+ }).is_err() {
+ Some(string_logger.into_string())
+ } else { None };
+ thread_count_ref.fetch_sub(1, atomic::Ordering::AcqRel);
+ main_thread_ref.unpark();
+ res
+ })
+ ));
+ while threads_running.load(atomic::Ordering::Acquire) > 32 {
+ std::thread::park();
+ }
+ }
+ }
+ let mut failed_outputs = Vec::new();
+ for (test, thread) in threads.drain(..) {
+ if let Some(output) = thread.join().unwrap() {
+ println!("\nOutput of {}:\n{}\n", test, output);
+ failed_outputs.push(test);
+ }
+ }
+ if !failed_outputs.is_empty() {
+ println!("Test cases which failed: ");
+ for case in failed_outputs {
+ println!("{}", case);
+ }
+ panic!();
+ }
+}
--- /dev/null
+// This file is Copyright its original authors, visible in version control
+// history.
+//
+// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
+// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// You may not use this file except in accordance with one or both of these
+// licenses.
+
+// This file is auto-generated by gen_target.sh based on target_template.txt
+// To modify it, modify target_template.txt and run gen_target.sh instead.
+
+#![cfg_attr(feature = "libfuzzer_fuzz", no_main)]
+
+#[cfg(not(fuzzing))]
+compile_error!("Fuzz targets need cfg=fuzzing");
+
+extern crate lightning_fuzz;
+use lightning_fuzz::fromstr_to_netaddress::*;
+
+#[cfg(feature = "afl")]
+#[macro_use] extern crate afl;
+#[cfg(feature = "afl")]
+fn main() {
+ fuzz!(|data| {
+ fromstr_to_netaddress_run(data.as_ptr(), data.len());
+ });
+}
+
+#[cfg(feature = "honggfuzz")]
+#[macro_use] extern crate honggfuzz;
+#[cfg(feature = "honggfuzz")]
+fn main() {
+ loop {
+ fuzz!(|data| {
+ fromstr_to_netaddress_run(data.as_ptr(), data.len());
+ });
+ }
+}
+
+#[cfg(feature = "libfuzzer_fuzz")]
+#[macro_use] extern crate libfuzzer_sys;
+#[cfg(feature = "libfuzzer_fuzz")]
+fuzz_target!(|data: &[u8]| {
+ fromstr_to_netaddress_run(data.as_ptr(), data.len());
+});
+
+#[cfg(feature = "stdin_fuzz")]
+fn main() {
+ use std::io::Read;
+
+ let mut data = Vec::with_capacity(8192);
+ std::io::stdin().read_to_end(&mut data).unwrap();
+ fromstr_to_netaddress_run(data.as_ptr(), data.len());
+}
+
+#[test]
+fn run_test_cases() {
+ use std::fs;
+ use std::io::Read;
+ use lightning_fuzz::utils::test_logger::StringBuffer;
+
+ use std::sync::{atomic, Arc};
+ {
+ let data: Vec<u8> = vec![0];
+ fromstr_to_netaddress_run(data.as_ptr(), data.len());
+ }
+ let mut threads = Vec::new();
+ let threads_running = Arc::new(atomic::AtomicUsize::new(0));
+ if let Ok(tests) = fs::read_dir("test_cases/fromstr_to_netaddress") {
+ for test in tests {
+ let mut data: Vec<u8> = Vec::new();
+ let path = test.unwrap().path();
+ fs::File::open(&path).unwrap().read_to_end(&mut data).unwrap();
+ threads_running.fetch_add(1, atomic::Ordering::AcqRel);
+
+ let thread_count_ref = Arc::clone(&threads_running);
+ let main_thread_ref = std::thread::current();
+ threads.push((path.file_name().unwrap().to_str().unwrap().to_string(),
+ std::thread::spawn(move || {
+ let string_logger = StringBuffer::new();
+
+ let panic_logger = string_logger.clone();
+ let res = if ::std::panic::catch_unwind(move || {
+ fromstr_to_netaddress_test(&data, panic_logger);
+ }).is_err() {
+ Some(string_logger.into_string())
+ } else { None };
+ thread_count_ref.fetch_sub(1, atomic::Ordering::AcqRel);
+ main_thread_ref.unpark();
+ res
+ })
+ ));
+ while threads_running.load(atomic::Ordering::Acquire) > 32 {
+ std::thread::park();
+ }
+ }
+ }
+ let mut failed_outputs = Vec::new();
+ for (test, thread) in threads.drain(..) {
+ if let Some(output) = thread.join().unwrap() {
+ println!("\nOutput of {}:\n{}\n", test, output);
+ failed_outputs.push(test);
+ }
+ }
+ if !failed_outputs.is_empty() {
+ println!("Test cases which failed: ");
+ for case in failed_outputs {
+ println!("{}", case);
+ }
+ panic!();
+ }
+}
GEN_TEST router
GEN_TEST zbase32
GEN_TEST indexedmap
+GEN_TEST onion_hop_data
+GEN_TEST base32
+GEN_TEST fromstr_to_netaddress
GEN_TEST msg_accept_channel msg_targets::
GEN_TEST msg_announcement_signatures msg_targets::
GEN_TEST msg_error_message msg_targets::
GEN_TEST msg_channel_update msg_targets::
-GEN_TEST msg_onion_hop_data msg_targets::
GEN_TEST msg_ping msg_targets::
GEN_TEST msg_pong msg_targets::
+++ /dev/null
-// This file is Copyright its original authors, visible in version control
-// history.
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-
-// This file is auto-generated by gen_target.sh based on target_template.txt
-// To modify it, modify target_template.txt and run gen_target.sh instead.
-
-#![cfg_attr(feature = "libfuzzer_fuzz", no_main)]
-
-#[cfg(not(fuzzing))]
-compile_error!("Fuzz targets need cfg=fuzzing");
-
-extern crate lightning_fuzz;
-use lightning_fuzz::msg_targets::msg_onion_hop_data::*;
-
-#[cfg(feature = "afl")]
-#[macro_use] extern crate afl;
-#[cfg(feature = "afl")]
-fn main() {
- fuzz!(|data| {
- msg_onion_hop_data_run(data.as_ptr(), data.len());
- });
-}
-
-#[cfg(feature = "honggfuzz")]
-#[macro_use] extern crate honggfuzz;
-#[cfg(feature = "honggfuzz")]
-fn main() {
- loop {
- fuzz!(|data| {
- msg_onion_hop_data_run(data.as_ptr(), data.len());
- });
- }
-}
-
-#[cfg(feature = "libfuzzer_fuzz")]
-#[macro_use] extern crate libfuzzer_sys;
-#[cfg(feature = "libfuzzer_fuzz")]
-fuzz_target!(|data: &[u8]| {
- msg_onion_hop_data_run(data.as_ptr(), data.len());
-});
-
-#[cfg(feature = "stdin_fuzz")]
-fn main() {
- use std::io::Read;
-
- let mut data = Vec::with_capacity(8192);
- std::io::stdin().read_to_end(&mut data).unwrap();
- msg_onion_hop_data_run(data.as_ptr(), data.len());
-}
-
-#[test]
-fn run_test_cases() {
- use std::fs;
- use std::io::Read;
- use lightning_fuzz::utils::test_logger::StringBuffer;
-
- use std::sync::{atomic, Arc};
- {
- let data: Vec<u8> = vec![0];
- msg_onion_hop_data_run(data.as_ptr(), data.len());
- }
- let mut threads = Vec::new();
- let threads_running = Arc::new(atomic::AtomicUsize::new(0));
- if let Ok(tests) = fs::read_dir("test_cases/msg_onion_hop_data") {
- for test in tests {
- let mut data: Vec<u8> = Vec::new();
- let path = test.unwrap().path();
- fs::File::open(&path).unwrap().read_to_end(&mut data).unwrap();
- threads_running.fetch_add(1, atomic::Ordering::AcqRel);
-
- let thread_count_ref = Arc::clone(&threads_running);
- let main_thread_ref = std::thread::current();
- threads.push((path.file_name().unwrap().to_str().unwrap().to_string(),
- std::thread::spawn(move || {
- let string_logger = StringBuffer::new();
-
- let panic_logger = string_logger.clone();
- let res = if ::std::panic::catch_unwind(move || {
- msg_onion_hop_data_test(&data, panic_logger);
- }).is_err() {
- Some(string_logger.into_string())
- } else { None };
- thread_count_ref.fetch_sub(1, atomic::Ordering::AcqRel);
- main_thread_ref.unpark();
- res
- })
- ));
- while threads_running.load(atomic::Ordering::Acquire) > 32 {
- std::thread::park();
- }
- }
- }
- let mut failed_outputs = Vec::new();
- for (test, thread) in threads.drain(..) {
- if let Some(output) = thread.join().unwrap() {
- println!("\nOutput of {}:\n{}\n", test, output);
- failed_outputs.push(test);
- }
- }
- if !failed_outputs.is_empty() {
- println!("Test cases which failed: ");
- for case in failed_outputs {
- println!("{}", case);
- }
- panic!();
- }
-}
--- /dev/null
+// This file is Copyright its original authors, visible in version control
+// history.
+//
+// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
+// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// You may not use this file except in accordance with one or both of these
+// licenses.
+
+// This file is auto-generated by gen_target.sh based on target_template.txt
+// To modify it, modify target_template.txt and run gen_target.sh instead.
+
+#![cfg_attr(feature = "libfuzzer_fuzz", no_main)]
+
+#[cfg(not(fuzzing))]
+compile_error!("Fuzz targets need cfg=fuzzing");
+
+extern crate lightning_fuzz;
+use lightning_fuzz::onion_hop_data::*;
+
+#[cfg(feature = "afl")]
+#[macro_use] extern crate afl;
+#[cfg(feature = "afl")]
+fn main() {
+ fuzz!(|data| {
+ onion_hop_data_run(data.as_ptr(), data.len());
+ });
+}
+
+#[cfg(feature = "honggfuzz")]
+#[macro_use] extern crate honggfuzz;
+#[cfg(feature = "honggfuzz")]
+fn main() {
+ loop {
+ fuzz!(|data| {
+ onion_hop_data_run(data.as_ptr(), data.len());
+ });
+ }
+}
+
+#[cfg(feature = "libfuzzer_fuzz")]
+#[macro_use] extern crate libfuzzer_sys;
+#[cfg(feature = "libfuzzer_fuzz")]
+fuzz_target!(|data: &[u8]| {
+ onion_hop_data_run(data.as_ptr(), data.len());
+});
+
+#[cfg(feature = "stdin_fuzz")]
+fn main() {
+ use std::io::Read;
+
+ let mut data = Vec::with_capacity(8192);
+ std::io::stdin().read_to_end(&mut data).unwrap();
+ onion_hop_data_run(data.as_ptr(), data.len());
+}
+
+#[test]
+fn run_test_cases() {
+ use std::fs;
+ use std::io::Read;
+ use lightning_fuzz::utils::test_logger::StringBuffer;
+
+ use std::sync::{atomic, Arc};
+ {
+ let data: Vec<u8> = vec![0];
+ onion_hop_data_run(data.as_ptr(), data.len());
+ }
+ let mut threads = Vec::new();
+ let threads_running = Arc::new(atomic::AtomicUsize::new(0));
+ if let Ok(tests) = fs::read_dir("test_cases/onion_hop_data") {
+ for test in tests {
+ let mut data: Vec<u8> = Vec::new();
+ let path = test.unwrap().path();
+ fs::File::open(&path).unwrap().read_to_end(&mut data).unwrap();
+ threads_running.fetch_add(1, atomic::Ordering::AcqRel);
+
+ let thread_count_ref = Arc::clone(&threads_running);
+ let main_thread_ref = std::thread::current();
+ threads.push((path.file_name().unwrap().to_str().unwrap().to_string(),
+ std::thread::spawn(move || {
+ let string_logger = StringBuffer::new();
+
+ let panic_logger = string_logger.clone();
+ let res = if ::std::panic::catch_unwind(move || {
+ onion_hop_data_test(&data, panic_logger);
+ }).is_err() {
+ Some(string_logger.into_string())
+ } else { None };
+ thread_count_ref.fetch_sub(1, atomic::Ordering::AcqRel);
+ main_thread_ref.unpark();
+ res
+ })
+ ));
+ while threads_running.load(atomic::Ordering::Acquire) > 32 {
+ std::thread::park();
+ }
+ }
+ }
+ let mut failed_outputs = Vec::new();
+ for (test, thread) in threads.drain(..) {
+ if let Some(output) = thread.join().unwrap() {
+ println!("\nOutput of {}:\n{}\n", test, output);
+ failed_outputs.push(test);
+ }
+ }
+ if !failed_outputs.is_empty() {
+ println!("Test cases which failed: ");
+ for case in failed_outputs {
+ println!("{}", case);
+ }
+ panic!();
+ }
+}
use lightning::ln::msgs::{self, CommitmentUpdate, ChannelMessageHandler, DecodeError, UpdateAddHTLC, Init};
use lightning::ln::script::ShutdownScript;
use lightning::ln::functional_test_utils::*;
-use lightning::util::enforcing_trait_impls::{EnforcingSigner, EnforcementState};
+use lightning::offers::invoice::UnsignedBolt12Invoice;
+use lightning::offers::invoice_request::UnsignedInvoiceRequest;
+use lightning::util::test_channel_signer::{TestChannelSigner, EnforcementState};
use lightning::util::errors::APIError;
use lightning::util::logger::Logger;
use lightning::util::config::UserConfig;
use bitcoin::secp256k1::{Message, PublicKey, SecretKey, Scalar, Secp256k1};
use bitcoin::secp256k1::ecdh::SharedSecret;
use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature};
+use bitcoin::secp256k1::schnorr;
use std::mem;
use std::cmp::{self, Ordering};
// Background feerate which is <= the minimum Normal feerate.
match conf_target {
ConfirmationTarget::HighPriority => MAX_FEE,
- ConfirmationTarget::Background => 253,
+ ConfirmationTarget::Background|ConfirmationTarget::MempoolMinimum => 253,
ConfirmationTarget::Normal => cmp::min(self.ret_val.load(atomic::Ordering::Acquire), MAX_FEE),
}
}
impl Router for FuzzRouter {
fn find_route(
&self, _payer: &PublicKey, _params: &RouteParameters, _first_hops: Option<&[&ChannelDetails]>,
- _inflight_htlcs: &InFlightHtlcs
+ _inflight_htlcs: InFlightHtlcs
) -> Result<Route, msgs::LightningError> {
Err(msgs::LightningError {
err: String::from("Not implemented"),
pub logger: Arc<dyn Logger>,
pub keys: Arc<KeyProvider>,
pub persister: Arc<TestPersister>,
- pub chain_monitor: Arc<chainmonitor::ChainMonitor<EnforcingSigner, Arc<dyn chain::Filter>, Arc<TestBroadcaster>, Arc<FuzzEstimator>, Arc<dyn Logger>, Arc<TestPersister>>>,
+ pub chain_monitor: Arc<chainmonitor::ChainMonitor<TestChannelSigner, Arc<dyn chain::Filter>, Arc<TestBroadcaster>, Arc<FuzzEstimator>, Arc<dyn Logger>, Arc<TestPersister>>>,
// If we reload a node with an old copy of ChannelMonitors, the ChannelManager deserialization
// logic will automatically force-close our channels for us (as we don't have an up-to-date
// monitor implying we are not able to punish misbehaving counterparties). Because this test
// "fails" if we ever force-close a channel, we avoid doing so, always saving the latest
// fully-serialized monitor state here, as well as the corresponding update_id.
pub latest_monitors: Mutex<HashMap<OutPoint, (u64, Vec<u8>)>>,
- pub should_update_manager: atomic::AtomicBool,
}
impl TestChainMonitor {
pub fn new(broadcaster: Arc<TestBroadcaster>, logger: Arc<dyn Logger>, feeest: Arc<FuzzEstimator>, persister: Arc<TestPersister>, keys: Arc<KeyProvider>) -> Self {
keys,
persister,
latest_monitors: Mutex::new(HashMap::new()),
- should_update_manager: atomic::AtomicBool::new(false),
}
}
}
-impl chain::Watch<EnforcingSigner> for TestChainMonitor {
- fn watch_channel(&self, funding_txo: OutPoint, monitor: channelmonitor::ChannelMonitor<EnforcingSigner>) -> chain::ChannelMonitorUpdateStatus {
+impl chain::Watch<TestChannelSigner> for TestChainMonitor {
+ fn watch_channel(&self, funding_txo: OutPoint, monitor: channelmonitor::ChannelMonitor<TestChannelSigner>) -> Result<chain::ChannelMonitorUpdateStatus, ()> {
let mut ser = VecWriter(Vec::new());
monitor.write(&mut ser).unwrap();
if let Some(_) = self.latest_monitors.lock().unwrap().insert(funding_txo, (monitor.get_latest_update_id(), ser.0)) {
panic!("Already had monitor pre-watch_channel");
}
- self.should_update_manager.store(true, atomic::Ordering::Relaxed);
self.chain_monitor.watch_channel(funding_txo, monitor)
}
hash_map::Entry::Occupied(entry) => entry,
hash_map::Entry::Vacant(_) => panic!("Didn't have monitor on update call"),
};
- let deserialized_monitor = <(BlockHash, channelmonitor::ChannelMonitor<EnforcingSigner>)>::
+ let deserialized_monitor = <(BlockHash, channelmonitor::ChannelMonitor<TestChannelSigner>)>::
read(&mut Cursor::new(&map_entry.get().1), (&*self.keys, &*self.keys)).unwrap().1;
deserialized_monitor.update_monitor(update, &&TestBroadcaster{}, &FuzzEstimator { ret_val: atomic::AtomicU32::new(253) }, &self.logger).unwrap();
let mut ser = VecWriter(Vec::new());
deserialized_monitor.write(&mut ser).unwrap();
map_entry.insert((update.update_id, ser.0));
- self.should_update_manager.store(true, atomic::Ordering::Relaxed);
self.chain_monitor.update_channel(funding_txo, update)
}
unreachable!()
}
+ fn sign_bolt12_invoice_request(
+ &self, _invoice_request: &UnsignedInvoiceRequest
+ ) -> Result<schnorr::Signature, ()> {
+ unreachable!()
+ }
+
+ fn sign_bolt12_invoice(
+ &self, _invoice: &UnsignedBolt12Invoice,
+ ) -> Result<schnorr::Signature, ()> {
+ unreachable!()
+ }
+
fn sign_gossip_message(&self, msg: lightning::ln::msgs::UnsignedGossipMessage) -> Result<Signature, ()> {
let msg_hash = Message::from_slice(&Sha256dHash::hash(&msg.encode()[..])[..]).map_err(|_| ())?;
let secp_ctx = Secp256k1::signing_only();
}
impl SignerProvider for KeyProvider {
- type Signer = EnforcingSigner;
+ type Signer = TestChannelSigner;
fn generate_channel_keys_id(&self, _inbound: bool, _channel_value_satoshis: u64, _user_channel_id: u128) -> [u8; 32] {
let id = self.rand_bytes_id.fetch_add(1, atomic::Ordering::Relaxed) as u8;
channel_keys_id,
);
let revoked_commitment = self.make_enforcement_state_cell(keys.commitment_seed);
- EnforcingSigner::new_with_revoked(keys, revoked_commitment, false)
+ TestChannelSigner::new_with_revoked(keys, revoked_commitment, false)
}
fn read_chan_signer(&self, buffer: &[u8]) -> Result<Self::Signer, DecodeError> {
let inner: InMemorySigner = ReadableArgs::read(&mut reader, self)?;
let state = self.make_enforcement_state_cell(inner.commitment_seed);
- Ok(EnforcingSigner {
+ Ok(TestChannelSigner {
inner,
state,
disable_revocation_policy_check: false,
channel_features: dest.channel_features(),
fee_msat: amt,
cltv_expiry_delta: 200,
+ maybe_announced_channel: true,
}], blinded_tail: None }],
- payment_params: None,
+ route_params: None,
}, payment_hash, RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_id)) {
check_payment_err(err, amt > max_value_sendable || amt < min_value_sendable);
false
channel_features: middle.channel_features(),
fee_msat: first_hop_fee,
cltv_expiry_delta: 100,
- },RouteHop {
+ maybe_announced_channel: true,
+ }, RouteHop {
pubkey: dest.get_our_node_id(),
node_features: dest.node_features(),
short_channel_id: dest_chan_id,
channel_features: dest.channel_features(),
fee_msat: amt,
cltv_expiry_delta: 200,
+ maybe_announced_channel: true,
}], blinded_tail: None }],
- payment_params: None,
+ route_params: None,
}, payment_hash, RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_id)) {
let sent_amt = amt + first_hop_fee;
check_payment_err(err, sent_amt < min_value_sendable || sent_amt > max_value_sendable);
let mut monitors = HashMap::new();
let mut old_monitors = $old_monitors.latest_monitors.lock().unwrap();
for (outpoint, (update_id, monitor_ser)) in old_monitors.drain() {
- monitors.insert(outpoint, <(BlockHash, ChannelMonitor<EnforcingSigner>)>::read(&mut Cursor::new(&monitor_ser), (&*$keys_manager, &*$keys_manager)).expect("Failed to read monitor").1);
+ monitors.insert(outpoint, <(BlockHash, ChannelMonitor<TestChannelSigner>)>::read(&mut Cursor::new(&monitor_ser), (&*$keys_manager, &*$keys_manager)).expect("Failed to read monitor").1);
chain_monitor.latest_monitors.lock().unwrap().insert(outpoint, (update_id, monitor_ser));
}
let mut monitor_refs = HashMap::new();
let res = (<(BlockHash, ChanMan)>::read(&mut Cursor::new(&$ser.0), read_args).expect("Failed to read manager").1, chain_monitor.clone());
for (funding_txo, mon) in monitors.drain() {
assert_eq!(chain_monitor.chain_monitor.watch_channel(funding_txo, mon),
- ChannelMonitorUpdateStatus::Completed);
+ Ok(ChannelMonitorUpdateStatus::Completed));
}
res
} }
if !chan_a_disconnected {
nodes[1].peer_disconnected(&nodes[0].get_our_node_id());
chan_a_disconnected = true;
- drain_msg_events_on_disconnect!(0);
- }
- if monitor_a.should_update_manager.load(atomic::Ordering::Relaxed) {
- node_a_ser.0.clear();
- nodes[0].write(&mut node_a_ser).unwrap();
+ push_excess_b_events!(nodes[1].get_and_clear_pending_msg_events().drain(..), Some(0));
+ ab_events.clear();
+ ba_events.clear();
}
let (new_node_a, new_monitor_a) = reload_node!(node_a_ser, 0, monitor_a, keys_manager_a, fee_est_a);
nodes[0] = new_node_a;
if !chan_b_disconnected {
nodes[1].peer_disconnected(&nodes[2].get_our_node_id());
chan_b_disconnected = true;
- drain_msg_events_on_disconnect!(2);
- }
- if monitor_c.should_update_manager.load(atomic::Ordering::Relaxed) {
- node_c_ser.0.clear();
- nodes[2].write(&mut node_c_ser).unwrap();
+ push_excess_b_events!(nodes[1].get_and_clear_pending_msg_events().drain(..), Some(2));
+ bc_events.clear();
+ cb_events.clear();
}
let (new_node_c, new_monitor_c) = reload_node!(node_c_ser, 2, monitor_c, keys_manager_c, fee_est_c);
nodes[2] = new_node_c;
_ => test_return!(),
}
- node_a_ser.0.clear();
- nodes[0].write(&mut node_a_ser).unwrap();
- monitor_a.should_update_manager.store(false, atomic::Ordering::Relaxed);
- node_b_ser.0.clear();
- nodes[1].write(&mut node_b_ser).unwrap();
- monitor_b.should_update_manager.store(false, atomic::Ordering::Relaxed);
- node_c_ser.0.clear();
- nodes[2].write(&mut node_c_ser).unwrap();
- monitor_c.should_update_manager.store(false, atomic::Ordering::Relaxed);
+ if nodes[0].get_and_clear_needs_persistence() == true {
+ node_a_ser.0.clear();
+ nodes[0].write(&mut node_a_ser).unwrap();
+ }
+ if nodes[1].get_and_clear_needs_persistence() == true {
+ node_b_ser.0.clear();
+ nodes[1].write(&mut node_b_ser).unwrap();
+ }
+ if nodes[2].get_and_clear_needs_persistence() == true {
+ node_c_ser.0.clear();
+ nodes[2].write(&mut node_c_ser).unwrap();
+ }
}
}
use bitcoin::hash_types::BlockHash;
use lightning::chain::channelmonitor;
-use lightning::util::enforcing_trait_impls::EnforcingSigner;
+use lightning::util::test_channel_signer::TestChannelSigner;
use lightning::util::ser::{ReadableArgs, Writer, Writeable};
use lightning::util::test_utils::OnlyReadsKeysInterface;
#[inline]
pub fn do_test<Out: test_logger::Output>(data: &[u8], _out: Out) {
- if let Ok((latest_block_hash, monitor)) = <(BlockHash, channelmonitor::ChannelMonitor<EnforcingSigner>)>::read(&mut Cursor::new(data), (&OnlyReadsKeysInterface {}, &OnlyReadsKeysInterface {})) {
+ if let Ok((latest_block_hash, monitor)) = <(BlockHash, channelmonitor::ChannelMonitor<TestChannelSigner>)>::read(&mut Cursor::new(data), (&OnlyReadsKeysInterface {}, &OnlyReadsKeysInterface {})) {
let mut w = VecWriter(Vec::new());
monitor.write(&mut w).unwrap();
- let deserialized_copy = <(BlockHash, channelmonitor::ChannelMonitor<EnforcingSigner>)>::read(&mut Cursor::new(&w.0), (&OnlyReadsKeysInterface {}, &OnlyReadsKeysInterface {})).unwrap();
+ let deserialized_copy = <(BlockHash, channelmonitor::ChannelMonitor<TestChannelSigner>)>::read(&mut Cursor::new(&w.0), (&OnlyReadsKeysInterface {}, &OnlyReadsKeysInterface {})).unwrap();
assert!(latest_block_hash == deserialized_copy.0);
assert!(monitor == deserialized_copy.1);
}
--- /dev/null
+// This file is Copyright its original authors, visible in version control
+// history.
+//
+// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
+// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// You may not use this file except in accordance with one or both of these
+// licenses.
+
+use lightning::ln::msgs::SocketAddress;
+use core::str::FromStr;
+
+use crate::utils::test_logger;
+
+#[inline]
+pub fn do_test(data: &[u8]) {
+ if let Ok(s) = std::str::from_utf8(data) {
+ let _ = SocketAddress::from_str(s);
+ }
+
+}
+
+pub fn fromstr_to_netaddress_test<Out: test_logger::Output>(data: &[u8], _out: Out) {
+ do_test(data);
+}
+
+#[no_mangle]
+pub extern "C" fn fromstr_to_netaddress_run(data: *const u8, datalen: usize) {
+ do_test(unsafe { std::slice::from_raw_parts(data, datalen) });
+}
+
use lightning::chain::transaction::OutPoint;
use lightning::sign::{InMemorySigner, Recipient, KeyMaterial, EntropySource, NodeSigner, SignerProvider};
use lightning::events::Event;
-use lightning::ln::{PaymentHash, PaymentPreimage, PaymentSecret};
+use lightning::ln::{ChannelId, PaymentHash, PaymentPreimage, PaymentSecret};
use lightning::ln::channelmanager::{ChainParameters, ChannelDetails, ChannelManager, PaymentId, RecipientOnionFields, Retry};
use lightning::ln::peer_handler::{MessageHandler,PeerManager,SocketDescriptor,IgnoringMessageHandler};
use lightning::ln::msgs::{self, DecodeError};
use lightning::ln::script::ShutdownScript;
use lightning::ln::functional_test_utils::*;
+use lightning::offers::invoice::UnsignedBolt12Invoice;
+use lightning::offers::invoice_request::UnsignedInvoiceRequest;
use lightning::routing::gossip::{P2PGossipSync, NetworkGraph};
use lightning::routing::utxo::UtxoLookup;
use lightning::routing::router::{InFlightHtlcs, PaymentParameters, Route, RouteParameters, Router};
-use lightning::util::config::UserConfig;
+use lightning::util::config::{UserConfig, MaxDustHTLCExposure};
use lightning::util::errors::APIError;
-use lightning::util::enforcing_trait_impls::{EnforcingSigner, EnforcementState};
+use lightning::util::test_channel_signer::{TestChannelSigner, EnforcementState};
use lightning::util::logger::Logger;
use lightning::util::ser::{ReadableArgs, Writeable};
use bitcoin::secp256k1::{Message, PublicKey, SecretKey, Scalar, Secp256k1};
use bitcoin::secp256k1::ecdh::SharedSecret;
use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature};
+use bitcoin::secp256k1::schnorr;
use std::cell::RefCell;
use hashbrown::{HashMap, hash_map};
impl Router for FuzzRouter {
fn find_route(
&self, _payer: &PublicKey, _params: &RouteParameters, _first_hops: Option<&[&ChannelDetails]>,
- _inflight_htlcs: &InFlightHtlcs
+ _inflight_htlcs: InFlightHtlcs
) -> Result<Route, msgs::LightningError> {
Err(msgs::LightningError {
err: String::from("Not implemented"),
}
type ChannelMan<'a> = ChannelManager<
- Arc<chainmonitor::ChainMonitor<EnforcingSigner, Arc<dyn chain::Filter>, Arc<TestBroadcaster>, Arc<FuzzEstimator>, Arc<dyn Logger>, Arc<TestPersister>>>,
+ Arc<chainmonitor::ChainMonitor<TestChannelSigner, Arc<dyn chain::Filter>, Arc<TestBroadcaster>, Arc<FuzzEstimator>, Arc<dyn Logger>, Arc<TestPersister>>>,
Arc<TestBroadcaster>, Arc<KeyProvider>, Arc<KeyProvider>, Arc<KeyProvider>, Arc<FuzzEstimator>, &'a FuzzRouter, Arc<dyn Logger>>;
type PeerMan<'a> = PeerManager<Peer<'a>, Arc<ChannelMan<'a>>, Arc<P2PGossipSync<Arc<NetworkGraph<Arc<dyn Logger>>>, Arc<dyn UtxoLookup>, Arc<dyn Logger>>>, IgnoringMessageHandler, Arc<dyn Logger>, IgnoringMessageHandler, Arc<KeyProvider>>;
struct MoneyLossDetector<'a> {
manager: Arc<ChannelMan<'a>>,
- monitor: Arc<chainmonitor::ChainMonitor<EnforcingSigner, Arc<dyn chain::Filter>, Arc<TestBroadcaster>, Arc<FuzzEstimator>, Arc<dyn Logger>, Arc<TestPersister>>>,
+ monitor: Arc<chainmonitor::ChainMonitor<TestChannelSigner, Arc<dyn chain::Filter>, Arc<TestBroadcaster>, Arc<FuzzEstimator>, Arc<dyn Logger>, Arc<TestPersister>>>,
handler: PeerMan<'a>,
peers: &'a RefCell<[bool; 256]>,
impl<'a> MoneyLossDetector<'a> {
pub fn new(peers: &'a RefCell<[bool; 256]>,
manager: Arc<ChannelMan<'a>>,
- monitor: Arc<chainmonitor::ChainMonitor<EnforcingSigner, Arc<dyn chain::Filter>, Arc<TestBroadcaster>, Arc<FuzzEstimator>, Arc<dyn Logger>, Arc<TestPersister>>>,
+ monitor: Arc<chainmonitor::ChainMonitor<TestChannelSigner, Arc<dyn chain::Filter>, Arc<TestBroadcaster>, Arc<FuzzEstimator>, Arc<dyn Logger>, Arc<TestPersister>>>,
handler: PeerMan<'a>) -> Self {
MoneyLossDetector {
manager,
unreachable!()
}
+ fn sign_bolt12_invoice_request(
+ &self, _invoice_request: &UnsignedInvoiceRequest
+ ) -> Result<schnorr::Signature, ()> {
+ unreachable!()
+ }
+
+ fn sign_bolt12_invoice(
+ &self, _invoice: &UnsignedBolt12Invoice,
+ ) -> Result<schnorr::Signature, ()> {
+ unreachable!()
+ }
+
fn sign_gossip_message(&self, msg: lightning::ln::msgs::UnsignedGossipMessage) -> Result<Signature, ()> {
let msg_hash = Message::from_slice(&Sha256dHash::hash(&msg.encode()[..])[..]).map_err(|_| ())?;
let secp_ctx = Secp256k1::signing_only();
}
impl SignerProvider for KeyProvider {
- type Signer = EnforcingSigner;
+ type Signer = TestChannelSigner;
fn generate_channel_keys_id(&self, inbound: bool, _channel_value_satoshis: u64, _user_channel_id: u128) -> [u8; 32] {
let ctr = self.counter.fetch_add(1, Ordering::Relaxed) as u8;
let secp_ctx = Secp256k1::signing_only();
let ctr = channel_keys_id[0];
let (inbound, state) = self.signer_state.borrow().get(&ctr).unwrap().clone();
- EnforcingSigner::new_with_revoked(if inbound {
+ TestChannelSigner::new_with_revoked(if inbound {
InMemorySigner::new(
&secp_ctx,
SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, ctr]).unwrap(),
}, state, false)
}
- fn read_chan_signer(&self, mut data: &[u8]) -> Result<EnforcingSigner, DecodeError> {
+ fn read_chan_signer(&self, mut data: &[u8]) -> Result<TestChannelSigner, DecodeError> {
let inner: InMemorySigner = ReadableArgs::read(&mut data, self)?;
let state = Arc::new(Mutex::new(EnforcementState::new()));
- Ok(EnforcingSigner::new_with_revoked(
+ Ok(TestChannelSigner::new_with_revoked(
inner,
state,
false
});
let mut config = UserConfig::default();
config.channel_config.forwarding_fee_proportional_millionths = slice_to_be32(get_slice!(4));
+ config.channel_config.max_dust_htlc_exposure = MaxDustHTLCExposure::FeeRateMultiplier(5_000_000 / 253);
config.channel_handshake_config.announced_channel = get_slice!(1)[0] != 0;
let network = Network::Bitcoin;
let best_block_timestamp = genesis_block(network).header.time;
let mut should_forward = false;
let mut payments_received: Vec<PaymentHash> = Vec::new();
let mut payments_sent = 0;
- let mut pending_funding_generation: Vec<([u8; 32], PublicKey, u64, Script)> = Vec::new();
+ let mut pending_funding_generation: Vec<(ChannelId, PublicKey, u64, Script)> = Vec::new();
let mut pending_funding_signatures = HashMap::new();
loop {
4 => {
let final_value_msat = slice_to_be24(get_slice!(3)) as u64;
let payment_params = PaymentParameters::from_node_id(get_pubkey!(), 42);
- let params = RouteParameters {
- payment_params,
- final_value_msat,
- };
+ let params = RouteParameters::from_payment_params_and_value(
+ payment_params, final_value_msat);
let mut payment_hash = PaymentHash([0; 32]);
payment_hash.0[0..8].copy_from_slice(&be64_to_array(payments_sent));
let mut sha = Sha256::engine();
15 => {
let final_value_msat = slice_to_be24(get_slice!(3)) as u64;
let payment_params = PaymentParameters::from_node_id(get_pubkey!(), 42);
- let params = RouteParameters {
- payment_params,
- final_value_msat,
- };
+ let params = RouteParameters::from_payment_params_and_value(
+ payment_params, final_value_msat);
let mut payment_hash = PaymentHash([0; 32]);
payment_hash.0[0..8].copy_from_slice(&be64_to_array(payments_sent));
let mut sha = Sha256::engine();
//
// 0a - create the funding transaction (client should send funding_created now)
//
+ // 00fd00fd - Two feerate requests (calculating max dust exposure) (all returning min feerate) (gonna be ingested by FuzzEstimator)
+ //
// 030112 - inbound read from peer id 1 of len 18
// 0062 01000000000000000000000000000000 - message header indicating message length 98
// 030172 - inbound read from peer id 1 of len 114
// 0300c1 - inbound read from peer id 0 of len 193
// ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff ab00000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000 - end of update_add_htlc from 0 to 1 via client and mac
//
+ // 00fd - One feerate request (calculating max dust exposure) (all returning min feerate) (gonna be ingested by FuzzEstimator)
+ //
// 030012 - inbound read from peer id 0 of len 18
// 0064 03000000000000000000000000000000 - message header indicating message length 100
// 030074 - inbound read from peer id 0 of len 116
// 07 - process the now-pending HTLC forward
// - client now sends id 1 update_add_htlc and commitment_signed (CHECK 7: UpdateHTLCs event for node 03020000 with 1 HTLCs for channel 3f000000)
//
+ // 00fd00fd - Two feerate requests (calculating max dust exposure) (all returning min feerate) (gonna be ingested by FuzzEstimator)
+ //
// - we respond with commitment_signed then revoke_and_ack (a weird, but valid, order)
// 030112 - inbound read from peer id 1 of len 18
// 0064 01000000000000000000000000000000 - message header indicating message length 100
// 0300c1 - inbound read from peer id 0 of len 193
// ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff ab00000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000 - end of update_add_htlc from 0 to 1 via client and mac
//
+ // 00fd - One feerate request (calculating max dust exposure) (all returning min feerate) (gonna be ingested by FuzzEstimator)
+ //
// - now respond to the update_fulfill_htlc+commitment_signed messages the client sent to peer 0
// 030012 - inbound read from peer id 0 of len 18
// 0063 03000000000000000000000000000000 - message header indicating message length 99
// - client now sends id 1 update_add_htlc and commitment_signed (CHECK 7 duplicate)
// - we respond with revoke_and_ack, then commitment_signed, then update_fail_htlc
//
+ // 00fd00fd - Two feerate requests (calculating max dust exposure) (all returning min feerate) (gonna be ingested by FuzzEstimator)
+ //
// 030112 - inbound read from peer id 1 of len 18
// 0064 01000000000000000000000000000000 - message header indicating message length 100
// 030174 - inbound read from peer id 1 of len 116
// 0300c1 - inbound read from peer id 0 of len 193
// ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 5300000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000 - end of update_add_htlc from 0 to 1 via client and mac
//
+ // 00fd - One feerate request (calculating max dust exposure) (all returning min feerate) (gonna be ingested by FuzzEstimator)
+ //
// 030012 - inbound read from peer id 0 of len 18
// 00a4 03000000000000000000000000000000 - message header indicating message length 164
// 0300b4 - inbound read from peer id 0 of len 180
// 07 - process the now-pending HTLC forward
// - client now sends id 1 update_add_htlc and commitment_signed (CHECK 7 duplicate)
//
+ // 00fd00fd - Two feerate requests (calculating max dust exposure) (all returning min feerate) (gonna be ingested by FuzzEstimator)
+ //
// 0c007d - connect a block with one transaction of len 125
// 02000000013a000000000000000000000000000000000000000000000000000000000000000000000000000000800258020000000000002200204b0000000000000000000000000000000000000000000000000000000000000014c0000000000000160014280000000000000000000000000000000000000005000020 - the commitment transaction for channel 3f00000000000000000000000000000000000000000000000000000000000000
//
// - 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("01000000000000000000000000000000000000000000000000000000000000000000000001000300000000000000000000000000000000000000000000000000000000000000020300320003000000000000000000000000000000000000000000000000000000000000000203000000000000000000000000000000030012001003000000000000000000000000000000030020001000021aaa0008aaaaaaaaaaaa9aaa030000000000000000000000000000000300120147030000000000000000000000000000000300fe00207500000000000000000000000000000000000000000000000000000000000000ff4f00f805273c1b203bb5ebf8436bfde57b3be8c2f5e95d9491dbb181909679000000000000c35000000000000000000000000000000162ffffffffffffffff00000000000002220000000000000000000000fd000601e3030000000000000000000000000000000000000000000000000000000000000001030000000000000000000000000000000000000000000000000000000000000002030000000000000000000000000000000000000000000000000000000000000003030000000000000000000000000000000000000000000000000000000000000004030059030000000000000000000000000000000000000000000000000000000000000005020900000000000000000000000000000000000000000000000000000000000000010000010210000300000000000000000000000000000000fd00fd0300120084030000000000000000000000000000000300940022ff4f00f805273c1b203bb5ebf8436bfde57b3be8c2f5e95d9491dbb1819096793d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000210100000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000c005e020000000100000000000000000000000000000000000000000000000000000000000000000000000000ffffffff0150c3000000000000220020ae00000000000000000000000000000000000000000000000000000000000000000000000c00000c00000c00000c00000c00000c00000c00000c00000c00000c00000c00000c000003001200430300000000000000000000000000000003005300243d0000000000000000000000000000000000000000000000000000000000000002080000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000010301320003000000000000000000000000000000000000000000000000000000000000000703000000000000000000000000000000030142000302000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003000000000000000000000000000000030112001001000000000000000000000000000000030120001000021aaa0008aaaaaaaaaaaa9aaa01000000000000000000000000000000050103020000000000000000000000000000000000000000000000000000000000000000c3500003e800fd0301120112010000000000000000000000000000000301ff00210000000000000000000000000000000000000000000000000000000000000e05000000000000016200000000004c4b4000000000000003e800000000000003e80000000203f000050300000000000000000000000000000000000000000000000000000000000001000300000000000000000000000000000000000000000000000000000000000002000300000000000000000000000000000000000000000000000000000000000003000300000000000000000000000000000000000000000000000000000000000004000300000000000000000000000000000000000000000000000000000000000005000266000000000000000000000000000003012300000000000000000000000000000000000000010000000000000000000000000000000a03011200620100000000000000000000000000000003017200233a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007c0001000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000b03011200430100000000000000000000000000000003015300243a000000000000000000000000000000000000000000000000000000000000000267000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003001205ac030000000000000000000000000000000300ff00803d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e80ff00000000000000000000000000000000000000000000000000000000000000000003f00003000000000000000000000000000000000000000000000000000000000000055511020203e80401a0060800000e00000100000a00000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300c1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffab000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001200640300000000000000000000000000000003007400843d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030010000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000900000000000000000000000000000000000000000000000000000000000000020b00000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000703011200640100000000000000000000000000000003017400843a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006a000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003011200630100000000000000000000000000000003017300853a00000000000000000000000000000000000000000000000000000000000000660000000000000000000000000000000000000000000000000000000000000002640000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000030112004a0100000000000000000000000000000003015a00823a000000000000000000000000000000000000000000000000000000000000000000000000000000ff008888888888888888888888888888888888888888888888888888888888880100000000000000000000000000000003011200640100000000000000000000000000000003017400843a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003011200630100000000000000000000000000000003017300853a0000000000000000000000000000000000000000000000000000000000000067000000000000000000000000000000000000000000000000000000000000000265000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003001205ac030000000000000000000000000000000300ff00803d0000000000000000000000000000000000000000000000000000000000000000000000000000010000000000003e80ff00000000000000000000000000000000000000000000000000000000000000000003f00003000000000000000000000000000000000000000000000000000000000000055511020203e80401a0060800000e00000100000a00000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300c1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffab000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000020a000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001200640300000000000000000000000000000003007400843d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c3010000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000b00000000000000000000000000000000000000000000000000000000000000020d00000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000703011200640100000000000000000000000000000003017400843a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000039000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003011200630100000000000000000000000000000003017300853a00000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000002700000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000030112002c0100000000000000000000000000000003013c00833a00000000000000000000000000000000000000000000000000000000000000000000000000000100000100000000000000000000000000000003011200640100000000000000000000000000000003017400843a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000039000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003011200630100000000000000000000000000000003017300853a000000000000000000000000000000000000000000000000000000000000006500000000000000000000000000000000000000000000000000000000000000027100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000703001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000020c000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001200640300000000000000000000000000000003007400843d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000032010000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001205ac030000000000000000000000000000000300ff00803d00000000000000000000000000000000000000000000000000000000000000000000000000000200000000000b0838ff00000000000000000000000000000000000000000000000000000000000000000003f0000300000000000000000000000000000000000000000000000000000000000005551202030927c00401a0060800000e00000100000a00000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300c1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff53000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001200a4030000000000000000000000000000000300b400843d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007501000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006705000000000000000000000000000000000000000000000000000000000000060300000000000000000000000000000003001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000d00000000000000000000000000000000000000000000000000000000000000020f0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000070c007d02000000013a000000000000000000000000000000000000000000000000000000000000000000000000000000800258020000000000002200204b0000000000000000000000000000000000000000000000000000000000000014c00000000000001600142800000000000000000000000000000000000000050000200c005e0200000001730000000000000000000000000000000000000000000000000000000000000000000000000000000001a701000000000000220020b200000000000000000000000000000000000000000000000000000000000000000000000c00000c00000c00000c00000c000007").unwrap(), &(Arc::clone(&logger) as Arc<dyn Logger>));
+ super::do_test(&::hex::decode("01000000000000000000000000000000000000000000000000000000000000000000000001000300000000000000000000000000000000000000000000000000000000000000020300320003000000000000000000000000000000000000000000000000000000000000000203000000000000000000000000000000030012001003000000000000000000000000000000030020001000021aaa0008aaaaaaaaaaaa9aaa030000000000000000000000000000000300120147030000000000000000000000000000000300fe00207500000000000000000000000000000000000000000000000000000000000000ff4f00f805273c1b203bb5ebf8436bfde57b3be8c2f5e95d9491dbb181909679000000000000c35000000000000000000000000000000162ffffffffffffffff00000000000002220000000000000000000000fd000601e3030000000000000000000000000000000000000000000000000000000000000001030000000000000000000000000000000000000000000000000000000000000002030000000000000000000000000000000000000000000000000000000000000003030000000000000000000000000000000000000000000000000000000000000004030059030000000000000000000000000000000000000000000000000000000000000005020900000000000000000000000000000000000000000000000000000000000000010000010210000300000000000000000000000000000000fd00fd0300120084030000000000000000000000000000000300940022ff4f00f805273c1b203bb5ebf8436bfde57b3be8c2f5e95d9491dbb1819096793d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000210100000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000c005e020000000100000000000000000000000000000000000000000000000000000000000000000000000000ffffffff0150c3000000000000220020ae00000000000000000000000000000000000000000000000000000000000000000000000c00000c00000c00000c00000c00000c00000c00000c00000c00000c00000c00000c000003001200430300000000000000000000000000000003005300243d0000000000000000000000000000000000000000000000000000000000000002080000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000010301320003000000000000000000000000000000000000000000000000000000000000000703000000000000000000000000000000030142000302000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003000000000000000000000000000000030112001001000000000000000000000000000000030120001000021aaa0008aaaaaaaaaaaa9aaa01000000000000000000000000000000050103020000000000000000000000000000000000000000000000000000000000000000c3500003e800fd0301120112010000000000000000000000000000000301ff00210000000000000000000000000000000000000000000000000000000000000e05000000000000016200000000004c4b4000000000000003e800000000000003e80000000203f000050300000000000000000000000000000000000000000000000000000000000001000300000000000000000000000000000000000000000000000000000000000002000300000000000000000000000000000000000000000000000000000000000003000300000000000000000000000000000000000000000000000000000000000004000300000000000000000000000000000000000000000000000000000000000005000266000000000000000000000000000003012300000000000000000000000000000000000000010000000000000000000000000000000a00fd00fd03011200620100000000000000000000000000000003017200233a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007c0001000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000b03011200430100000000000000000000000000000003015300243a000000000000000000000000000000000000000000000000000000000000000267000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003001205ac030000000000000000000000000000000300ff00803d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e80ff00000000000000000000000000000000000000000000000000000000000000000003f00003000000000000000000000000000000000000000000000000000000000000055511020203e80401a0060800000e00000100000a00000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300c1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffab000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000fd03001200640300000000000000000000000000000003007400843d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030010000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000900000000000000000000000000000000000000000000000000000000000000020b00000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000700fd00fd03011200640100000000000000000000000000000003017400843a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006a000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003011200630100000000000000000000000000000003017300853a00000000000000000000000000000000000000000000000000000000000000660000000000000000000000000000000000000000000000000000000000000002640000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000030112004a0100000000000000000000000000000003015a00823a000000000000000000000000000000000000000000000000000000000000000000000000000000ff008888888888888888888888888888888888888888888888888888888888880100000000000000000000000000000003011200640100000000000000000000000000000003017400843a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003011200630100000000000000000000000000000003017300853a0000000000000000000000000000000000000000000000000000000000000067000000000000000000000000000000000000000000000000000000000000000265000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003001205ac030000000000000000000000000000000300ff00803d0000000000000000000000000000000000000000000000000000000000000000000000000000010000000000003e80ff00000000000000000000000000000000000000000000000000000000000000000003f00003000000000000000000000000000000000000000000000000000000000000055511020203e80401a0060800000e00000100000a00000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300c1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffab000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000fd03001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000020a000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001200640300000000000000000000000000000003007400843d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c3010000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000b00000000000000000000000000000000000000000000000000000000000000020d00000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000700fd00fd03011200640100000000000000000000000000000003017400843a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000039000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003011200630100000000000000000000000000000003017300853a00000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000002700000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000030112002c0100000000000000000000000000000003013c00833a00000000000000000000000000000000000000000000000000000000000000000000000000000100000100000000000000000000000000000003011200640100000000000000000000000000000003017400843a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000039000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003011200630100000000000000000000000000000003017300853a000000000000000000000000000000000000000000000000000000000000006500000000000000000000000000000000000000000000000000000000000000027100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000703001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000020c000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001200640300000000000000000000000000000003007400843d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000032010000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001205ac030000000000000000000000000000000300ff00803d00000000000000000000000000000000000000000000000000000000000000000000000000000200000000000b0838ff00000000000000000000000000000000000000000000000000000000000000000003f0000300000000000000000000000000000000000000000000000000000000000005551202030927c00401a0060800000e00000100000a00000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300c1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff53000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000fd03001200a4030000000000000000000000000000000300b400843d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007501000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006705000000000000000000000000000000000000000000000000000000000000060300000000000000000000000000000003001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000d00000000000000000000000000000000000000000000000000000000000000020f00000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000700fd00fd0c007d02000000013a000000000000000000000000000000000000000000000000000000000000000000000000000000800258020000000000002200204b0000000000000000000000000000000000000000000000000000000000000014c00000000000001600142800000000000000000000000000000000000000050000200c005e0200000001730000000000000000000000000000000000000000000000000000000000000000000000000000000001a701000000000000220020b200000000000000000000000000000000000000000000000000000000000000000000000c00000c00000c00000c00000c000007").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
// licenses.
use crate::utils::test_logger;
-use lightning::offers::invoice::Invoice;
+use lightning::offers::invoice::Bolt12Invoice;
use lightning::util::ser::Writeable;
use std::convert::TryFrom;
#[inline]
pub fn do_test<Out: test_logger::Output>(data: &[u8], _out: Out) {
- if let Ok(invoice) = Invoice::try_from(data.to_vec()) {
+ if let Ok(invoice) = Bolt12Invoice::try_from(data.to_vec()) {
let mut bytes = Vec::with_capacity(data.len());
invoice.write(&mut bytes).unwrap();
assert_eq!(data, bytes);
use lightning::sign::EntropySource;
use lightning::ln::PaymentHash;
use lightning::ln::features::BlindedHopFeatures;
-use lightning::offers::invoice::{BlindedPayInfo, UnsignedInvoice};
+use lightning::offers::invoice::{BlindedPayInfo, UnsignedBolt12Invoice};
use lightning::offers::invoice_request::InvoiceRequest;
-use lightning::offers::parse::SemanticError;
+use lightning::offers::parse::Bolt12SemanticError;
use lightning::util::ser::Writeable;
#[inline]
if signing_pubkey == odd_pubkey || signing_pubkey == even_pubkey {
unsigned_invoice
.sign::<_, Infallible>(
- |digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys))
+ |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
)
.unwrap()
.write(&mut buffer)
} else {
unsigned_invoice
.sign::<_, Infallible>(
- |digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys))
+ |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
)
.unwrap_err();
}
SecretKey::from_slice(&[byte; 32]).unwrap()
}
-fn build_response<'a, T: secp256k1::Signing + secp256k1::Verification>(
- invoice_request: &'a InvoiceRequest, secp_ctx: &Secp256k1<T>
-) -> Result<UnsignedInvoice<'a>, SemanticError> {
+fn build_response<T: secp256k1::Signing + secp256k1::Verification>(
+ invoice_request: &InvoiceRequest, secp_ctx: &Secp256k1<T>
+) -> Result<UnsignedBolt12Invoice, Bolt12SemanticError> {
let entropy_source = Randomness {};
let paths = vec![
BlindedPath::new_for_message(&[pubkey(43), pubkey(44), pubkey(42)], &entropy_source, secp_ctx).unwrap(),
pub mod refund_deser;
pub mod router;
pub mod zbase32;
+pub mod onion_hop_data;
+pub mod base32;
+pub mod fromstr_to_netaddress;
pub mod msg_targets;
GEN_TEST lightning::ln::msgs::FundingSigned test_msg_simple ""
GEN_TEST lightning::ln::msgs::GossipTimestampFilter test_msg_simple ""
GEN_TEST lightning::ln::msgs::Init test_msg_simple ""
-GEN_TEST lightning::ln::msgs::OnionHopData test_msg_simple ""
GEN_TEST lightning::ln::msgs::OpenChannel test_msg_simple ""
GEN_TEST lightning::ln::msgs::Ping test_msg_simple ""
GEN_TEST lightning::ln::msgs::Pong test_msg_simple ""
pub mod msg_funding_signed;
pub mod msg_gossip_timestamp_filter;
pub mod msg_init;
-pub mod msg_onion_hop_data;
pub mod msg_open_channel;
pub mod msg_ping;
pub mod msg_pong;
+++ /dev/null
-// This file is Copyright its original authors, visible in version control
-// history.
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-
-// This file is auto-generated by gen_target.sh based on msg_target_template.txt
-// To modify it, modify msg_target_template.txt and run gen_target.sh instead.
-
-use crate::msg_targets::utils::VecWriter;
-use crate::utils::test_logger;
-
-#[inline]
-pub fn msg_onion_hop_data_test<Out: test_logger::Output>(data: &[u8], _out: Out) {
- test_msg_simple!(lightning::ln::msgs::OnionHopData, data);
-}
-
-#[no_mangle]
-pub extern "C" fn msg_onion_hop_data_run(data: *const u8, datalen: usize) {
- let data = unsafe { std::slice::from_raw_parts(data, datalen) };
- test_msg_simple!(lightning::ln::msgs::OnionHopData, data);
-}
use core::convert::{Infallible, TryFrom};
use lightning::offers::invoice_request::UnsignedInvoiceRequest;
use lightning::offers::offer::{Amount, Offer, Quantity};
-use lightning::offers::parse::SemanticError;
+use lightning::offers::parse::Bolt12SemanticError;
use lightning::util::ser::Writeable;
#[inline]
if let Ok(invoice_request) = build_response(&offer, pubkey) {
invoice_request
.sign::<_, Infallible>(
- |digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys))
+ |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
)
.unwrap()
.write(&mut buffer)
}
}
-fn build_response<'a>(
- offer: &'a Offer, pubkey: PublicKey
-) -> Result<UnsignedInvoiceRequest<'a>, SemanticError> {
+fn build_response(
+ offer: &Offer, pubkey: PublicKey
+) -> Result<UnsignedInvoiceRequest, Bolt12SemanticError> {
let mut builder = offer.request_invoice(vec![42; 64], pubkey)?;
builder = match offer.amount() {
None => builder.amount_msats(1000).unwrap(),
Some(Amount::Bitcoin { amount_msats }) => builder.amount_msats(amount_msats + 1)?,
- Some(Amount::Currency { .. }) => return Err(SemanticError::UnsupportedCurrency),
+ Some(Amount::Currency { .. }) => return Err(Bolt12SemanticError::UnsupportedCurrency),
};
builder = match offer.supported_quantity() {
--- /dev/null
+// This file is Copyright its original authors, visible in version control
+// history.
+//
+// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
+// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// You may not use this file except in accordance with one or both of these
+// licenses.
+
+// This file is auto-generated by gen_target.sh based on msg_target_template.txt
+// To modify it, modify msg_target_template.txt and run gen_target.sh instead.
+
+use crate::utils::test_logger;
+use lightning::util::test_utils;
+
+#[inline]
+pub fn onion_hop_data_test<Out: test_logger::Output>(data: &[u8], _out: Out) {
+ use lightning::util::ser::ReadableArgs;
+ let mut r = ::std::io::Cursor::new(data);
+ let node_signer = test_utils::TestNodeSigner::new(test_utils::privkey(42));
+ let _ = <lightning::ln::msgs::InboundOnionPayload as ReadableArgs<&&test_utils::TestNodeSigner>>::read(&mut r, &&node_signer);
+}
+
+#[no_mangle]
+pub extern "C" fn onion_hop_data_run(data: *const u8, datalen: usize) {
+ use lightning::util::ser::ReadableArgs;
+ let data = unsafe { std::slice::from_raw_parts(data, datalen) };
+ let mut r = ::std::io::Cursor::new(data);
+ let node_signer = test_utils::TestNodeSigner::new(test_utils::privkey(42));
+ let _ = <lightning::ln::msgs::InboundOnionPayload as ReadableArgs<&&test_utils::TestNodeSigner>>::read(&mut r, &&node_signer);
+}
use bitcoin::secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey};
use bitcoin::secp256k1::ecdh::SharedSecret;
use bitcoin::secp256k1::ecdsa::RecoverableSignature;
+use bitcoin::secp256k1::schnorr;
use lightning::sign::{Recipient, KeyMaterial, EntropySource, NodeSigner, SignerProvider};
use lightning::ln::msgs::{self, DecodeError, OnionMessageHandler};
use lightning::ln::script::ShutdownScript;
-use lightning::util::enforcing_trait_impls::EnforcingSigner;
+use lightning::offers::invoice::UnsignedBolt12Invoice;
+use lightning::offers::invoice_request::UnsignedInvoiceRequest;
+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};
unreachable!()
}
+ fn sign_bolt12_invoice_request(
+ &self, _invoice_request: &UnsignedInvoiceRequest
+ ) -> Result<schnorr::Signature, ()> {
+ unreachable!()
+ }
+
+ fn sign_bolt12_invoice(
+ &self, _invoice: &UnsignedBolt12Invoice,
+ ) -> Result<schnorr::Signature, ()> {
+ unreachable!()
+ }
+
fn sign_gossip_message(&self, _msg: lightning::ln::msgs::UnsignedGossipMessage) -> Result<bitcoin::secp256k1::ecdsa::Signature, ()> {
unreachable!()
}
}
impl SignerProvider for KeyProvider {
- type Signer = EnforcingSigner;
+ type Signer = TestChannelSigner;
fn generate_channel_keys_id(&self, _inbound: bool, _channel_value_satoshis: u64, _user_channel_id: u128) -> [u8; 32] { unreachable!() }
unreachable!()
}
- fn read_chan_signer(&self, _data: &[u8]) -> Result<EnforcingSigner, DecodeError> { unreachable!() }
+ fn read_chan_signer(&self, _data: &[u8]) -> Result<TestChannelSigner, DecodeError> { unreachable!() }
fn get_destination_script(&self) -> Result<Script, ()> { unreachable!() }
use lightning::sign::EntropySource;
use lightning::ln::PaymentHash;
use lightning::ln::features::BlindedHopFeatures;
-use lightning::offers::invoice::{BlindedPayInfo, UnsignedInvoice};
-use lightning::offers::parse::SemanticError;
+use lightning::offers::invoice::{BlindedPayInfo, UnsignedBolt12Invoice};
+use lightning::offers::parse::Bolt12SemanticError;
use lightning::offers::refund::Refund;
use lightning::util::ser::Writeable;
if let Ok(invoice) = build_response(&refund, pubkey, &secp_ctx) {
invoice
.sign::<_, Infallible>(
- |digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys))
+ |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
)
.unwrap()
.write(&mut buffer)
SecretKey::from_slice(&[byte; 32]).unwrap()
}
-fn build_response<'a, T: secp256k1::Signing + secp256k1::Verification>(
- refund: &'a Refund, signing_pubkey: PublicKey, secp_ctx: &Secp256k1<T>
-) -> Result<UnsignedInvoice<'a>, SemanticError> {
+fn build_response<T: secp256k1::Signing + secp256k1::Verification>(
+ refund: &Refund, signing_pubkey: PublicKey, secp_ctx: &Secp256k1<T>
+) -> Result<UnsignedBolt12Invoice, Bolt12SemanticError> {
let entropy_source = Randomness {};
let paths = vec![
BlindedPath::new_for_message(&[pubkey(43), pubkey(44), pubkey(42)], &entropy_source, secp_ctx).unwrap(),
use bitcoin::blockdata::transaction::TxOut;
use bitcoin::hash_types::BlockHash;
+use lightning::blinded_path::{BlindedHop, BlindedPath};
use lightning::chain::transaction::OutPoint;
+use lightning::ln::ChannelId;
use lightning::ln::channelmanager::{self, ChannelDetails, ChannelCounterparty};
+use lightning::ln::features::{BlindedHopFeatures, Bolt12InvoiceFeatures};
use lightning::ln::msgs;
+use lightning::offers::invoice::BlindedPayInfo;
use lightning::routing::gossip::{NetworkGraph, RoutingFees};
use lightning::routing::utxo::{UtxoFuture, UtxoLookup, UtxoLookupError, UtxoResult};
use lightning::routing::router::{find_route, PaymentParameters, RouteHint, RouteHintHop, RouteParameters};
let mut node_pks = HashSet::new();
let mut scid = 42;
+ macro_rules! first_hops {
+ ($first_hops_vec: expr) => {
+ match get_slice!(1)[0] {
+ 0 => None,
+ count => {
+ for _ in 0..count {
+ scid += 1;
+ let rnid = node_pks.iter().skip(u16::from_be_bytes(get_slice!(2).try_into().unwrap()) as usize % node_pks.len()).next().unwrap();
+ let capacity = u64::from_be_bytes(get_slice!(8).try_into().unwrap());
+ $first_hops_vec.push(ChannelDetails {
+ channel_id: ChannelId::new_zero(),
+ counterparty: ChannelCounterparty {
+ node_id: *rnid,
+ features: channelmanager::provided_init_features(&UserConfig::default()),
+ unspendable_punishment_reserve: 0,
+ forwarding_info: None,
+ outbound_htlc_minimum_msat: None,
+ outbound_htlc_maximum_msat: None,
+ },
+ funding_txo: Some(OutPoint { txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(), index: 0 }),
+ channel_type: None,
+ short_channel_id: Some(scid),
+ inbound_scid_alias: None,
+ outbound_scid_alias: None,
+ channel_value_satoshis: capacity,
+ user_channel_id: 0, inbound_capacity_msat: 0,
+ unspendable_punishment_reserve: None,
+ confirmations_required: None,
+ confirmations: None,
+ force_close_spend_delay: None,
+ is_outbound: true, is_channel_ready: true,
+ is_usable: true, is_public: true,
+ outbound_capacity_msat: capacity.saturating_mul(1000),
+ next_outbound_htlc_limit_msat: capacity.saturating_mul(1000),
+ next_outbound_htlc_minimum_msat: 0,
+ inbound_htlc_minimum_msat: None,
+ inbound_htlc_maximum_msat: None,
+ config: None,
+ feerate_sat_per_1000_weight: None,
+ channel_shutdown_state: Some(channelmanager::ChannelShutdownState::NotShuttingDown),
+ });
+ }
+ Some(&$first_hops_vec[..])
+ },
+ }
+ }
+ }
+
+ macro_rules! last_hops {
+ ($last_hops: expr) => {
+ let count = get_slice!(1)[0];
+ for _ in 0..count {
+ scid += 1;
+ let rnid = node_pks.iter().skip(slice_to_be16(get_slice!(2))as usize % node_pks.len()).next().unwrap();
+ $last_hops.push(RouteHint(vec![RouteHintHop {
+ src_node_id: *rnid,
+ short_channel_id: scid,
+ fees: RoutingFees {
+ base_msat: slice_to_be32(get_slice!(4)),
+ proportional_millionths: slice_to_be32(get_slice!(4)),
+ },
+ cltv_expiry_delta: slice_to_be16(get_slice!(2)),
+ htlc_minimum_msat: Some(slice_to_be64(get_slice!(8))),
+ htlc_maximum_msat: None,
+ }]));
+ }
+ }
+ }
+
+ macro_rules! find_routes {
+ ($first_hops: expr, $node_pks: expr, $route_params: expr) => {
+ let scorer = ProbabilisticScorer::new(ProbabilisticScoringDecayParameters::default(), &net_graph, &logger);
+ let random_seed_bytes: [u8; 32] = [get_slice!(1)[0]; 32];
+ for target in $node_pks {
+ let final_value_msat = slice_to_be64(get_slice!(8));
+ let final_cltv_expiry_delta = slice_to_be32(get_slice!(4));
+ let route_params = $route_params(final_value_msat, final_cltv_expiry_delta, target);
+ let _ = find_route(&our_pubkey, &route_params, &net_graph,
+ $first_hops.map(|c| c.iter().collect::<Vec<_>>()).as_ref().map(|a| a.as_slice()),
+ &logger, &scorer, &ProbabilisticScoringFeeParameters::default(), &random_seed_bytes);
+ }
+ }
+ }
+
loop {
match get_slice!(1)[0] {
0 => {
net_graph.channel_failed_permanent(short_channel_id);
},
_ if node_pks.is_empty() => {},
- _ => {
+ x if x < 250 => {
let mut first_hops_vec = Vec::new();
- let first_hops = match get_slice!(1)[0] {
- 0 => None,
- count => {
- for _ in 0..count {
- scid += 1;
- let rnid = node_pks.iter().skip(u16::from_be_bytes(get_slice!(2).try_into().unwrap()) as usize % node_pks.len()).next().unwrap();
- let capacity = u64::from_be_bytes(get_slice!(8).try_into().unwrap());
- first_hops_vec.push(ChannelDetails {
- channel_id: [0; 32],
- counterparty: ChannelCounterparty {
- node_id: *rnid,
- features: channelmanager::provided_init_features(&UserConfig::default()),
- unspendable_punishment_reserve: 0,
- forwarding_info: None,
- outbound_htlc_minimum_msat: None,
- outbound_htlc_maximum_msat: None,
- },
- funding_txo: Some(OutPoint { txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(), index: 0 }),
- channel_type: None,
- short_channel_id: Some(scid),
- inbound_scid_alias: None,
- outbound_scid_alias: None,
- channel_value_satoshis: capacity,
- user_channel_id: 0, inbound_capacity_msat: 0,
- unspendable_punishment_reserve: None,
- confirmations_required: None,
- confirmations: None,
- force_close_spend_delay: None,
- is_outbound: true, is_channel_ready: true,
- is_usable: true, is_public: true,
- balance_msat: 0,
- outbound_capacity_msat: capacity.saturating_mul(1000),
- next_outbound_htlc_limit_msat: capacity.saturating_mul(1000),
- next_outbound_htlc_minimum_msat: 0,
- inbound_htlc_minimum_msat: None,
- inbound_htlc_maximum_msat: None,
- config: None,
- feerate_sat_per_1000_weight: None,
- channel_shutdown_state: Some(channelmanager::ChannelShutdownState::NotShuttingDown),
- });
- }
- Some(&first_hops_vec[..])
- },
- };
+ // Use macros here and in the blinded match arm to ensure values are fetched from the fuzz
+ // input in the same order, for better coverage.
+ let first_hops = first_hops!(first_hops_vec);
let mut last_hops = Vec::new();
- {
- let count = get_slice!(1)[0];
- for _ in 0..count {
- scid += 1;
- let rnid = node_pks.iter().skip(slice_to_be16(get_slice!(2))as usize % node_pks.len()).next().unwrap();
- last_hops.push(RouteHint(vec![RouteHintHop {
- src_node_id: *rnid,
- short_channel_id: scid,
- fees: RoutingFees {
- base_msat: slice_to_be32(get_slice!(4)),
- proportional_millionths: slice_to_be32(get_slice!(4)),
- },
- cltv_expiry_delta: slice_to_be16(get_slice!(2)),
- htlc_minimum_msat: Some(slice_to_be64(get_slice!(8))),
- htlc_maximum_msat: None,
- }]));
- }
- }
- let scorer = ProbabilisticScorer::new(ProbabilisticScoringDecayParameters::default(), &net_graph, &logger);
- let random_seed_bytes: [u8; 32] = [get_slice!(1)[0]; 32];
- for target in node_pks.iter() {
- let final_value_msat = slice_to_be64(get_slice!(8));
- let final_cltv_expiry_delta = slice_to_be32(get_slice!(4));
- let route_params = RouteParameters {
- payment_params: PaymentParameters::from_node_id(*target, final_cltv_expiry_delta)
+ last_hops!(last_hops);
+ find_routes!(first_hops, node_pks.iter(), |final_amt, final_delta, target: &PublicKey| {
+ RouteParameters::from_payment_params_and_value(
+ PaymentParameters::from_node_id(*target, final_delta)
.with_route_hints(last_hops.clone()).unwrap(),
- final_value_msat,
- };
- let _ = find_route(&our_pubkey, &route_params, &net_graph,
- first_hops.map(|c| c.iter().collect::<Vec<_>>()).as_ref().map(|a| a.as_slice()),
- &logger, &scorer, &ProbabilisticScoringFeeParameters::default(), &random_seed_bytes);
- }
+ final_amt)
+ });
},
+ x => {
+ let mut first_hops_vec = Vec::new();
+ let first_hops = first_hops!(first_hops_vec);
+ let mut last_hops_unblinded = Vec::new();
+ last_hops!(last_hops_unblinded);
+ let dummy_pk = PublicKey::from_slice(&[2; 33]).unwrap();
+ let last_hops: Vec<(BlindedPayInfo, BlindedPath)> = last_hops_unblinded.into_iter().map(|hint| {
+ let hop = &hint.0[0];
+ let payinfo = BlindedPayInfo {
+ fee_base_msat: hop.fees.base_msat,
+ fee_proportional_millionths: hop.fees.proportional_millionths,
+ htlc_minimum_msat: hop.htlc_minimum_msat.unwrap(),
+ htlc_maximum_msat: hop.htlc_minimum_msat.unwrap().saturating_mul(100),
+ cltv_expiry_delta: hop.cltv_expiry_delta,
+ features: BlindedHopFeatures::empty(),
+ };
+ let num_blinded_hops = x % 250;
+ let mut blinded_hops = Vec::new();
+ for _ in 0..num_blinded_hops {
+ blinded_hops.push(BlindedHop {
+ blinded_node_id: dummy_pk,
+ encrypted_payload: Vec::new()
+ });
+ }
+ (payinfo, BlindedPath {
+ introduction_node_id: hop.src_node_id,
+ blinding_point: dummy_pk,
+ blinded_hops,
+ })
+ }).collect();
+ let mut features = Bolt12InvoiceFeatures::empty();
+ features.set_basic_mpp_optional();
+ find_routes!(first_hops, vec![dummy_pk].iter(), |final_amt, _, _| {
+ RouteParameters::from_payment_params_and_value(PaymentParameters::blinded(last_hops.clone())
+ .with_bolt12_features(features.clone()).unwrap(),
+ final_amt)
+ });
+ }
}
}
}
use lightning::chain::{chainmonitor, channelmonitor};
use lightning::chain::chainmonitor::MonitorUpdateId;
use lightning::chain::transaction::OutPoint;
-use lightning::util::enforcing_trait_impls::EnforcingSigner;
+use lightning::util::test_channel_signer::TestChannelSigner;
use std::sync::Mutex;
pub struct TestPersister {
pub update_ret: Mutex<chain::ChannelMonitorUpdateStatus>,
}
-impl chainmonitor::Persist<EnforcingSigner> for TestPersister {
- fn persist_new_channel(&self, _funding_txo: OutPoint, _data: &channelmonitor::ChannelMonitor<EnforcingSigner>, _update_id: MonitorUpdateId) -> chain::ChannelMonitorUpdateStatus {
+impl chainmonitor::Persist<TestChannelSigner> for TestPersister {
+ fn persist_new_channel(&self, _funding_txo: OutPoint, _data: &channelmonitor::ChannelMonitor<TestChannelSigner>, _update_id: MonitorUpdateId) -> chain::ChannelMonitorUpdateStatus {
self.update_ret.lock().unwrap().clone()
}
- fn update_persisted_channel(&self, _funding_txo: OutPoint, _update: Option<&channelmonitor::ChannelMonitorUpdate>, _data: &channelmonitor::ChannelMonitor<EnforcingSigner>, _update_id: MonitorUpdateId) -> chain::ChannelMonitorUpdateStatus {
+ fn update_persisted_channel(&self, _funding_txo: OutPoint, _update: Option<&channelmonitor::ChannelMonitorUpdate>, _data: &channelmonitor::ChannelMonitor<TestChannelSigner>, _update_id: MonitorUpdateId) -> chain::ChannelMonitorUpdateStatus {
self.update_ret.lock().unwrap().clone()
}
}
// You may not use this file except in accordance with one or both of these
// licenses.
-use lightning::util::zbase32;
+use lightning::util::base32;
use crate::utils::test_logger;
#[inline]
pub fn do_test(data: &[u8]) {
- let res = zbase32::encode(data);
- assert_eq!(&zbase32::decode(&res).unwrap()[..], data);
+ let res = base32::Alphabet::ZBase32.encode(data);
+ assert_eq!(&base32::Alphabet::ZBase32.decode(&res).unwrap()[..], data);
if let Ok(s) = std::str::from_utf8(data) {
- if let Ok(decoded) = zbase32::decode(s) {
- assert_eq!(&zbase32::encode(&decoded), &s.to_ascii_lowercase());
+ let res = base32::Alphabet::ZBase32.decode(s);
+ if let Ok(decoded) = res {
+ assert_eq!(&base32::Alphabet::ZBase32.encode(&decoded), &s.to_ascii_lowercase());
}
}
}
void router_run(const unsigned char* data, size_t data_len);
void zbase32_run(const unsigned char* data, size_t data_len);
void indexedmap_run(const unsigned char* data, size_t data_len);
+void onion_hop_data_run(const unsigned char* data, size_t data_len);
+void base32_run(const unsigned char* data, size_t data_len);
+void fromstr_to_netaddress_run(const unsigned char* data, size_t data_len);
void msg_accept_channel_run(const unsigned char* data, size_t data_len);
void msg_announcement_signatures_run(const unsigned char* data, size_t data_len);
void msg_channel_reestablish_run(const unsigned char* data, size_t data_len);
void msg_update_add_htlc_run(const unsigned char* data, size_t data_len);
void msg_error_message_run(const unsigned char* data, size_t data_len);
void msg_channel_update_run(const unsigned char* data, size_t data_len);
-void msg_onion_hop_data_run(const unsigned char* data, size_t data_len);
void msg_ping_run(const unsigned char* data, size_t data_len);
void msg_pong_run(const unsigned char* data, size_t data_len);
void msg_channel_details_run(const unsigned char* data, size_t data_len);
[package]
name = "lightning-background-processor"
-version = "0.0.116-alpha1"
+version = "0.0.117-alpha2"
authors = ["Valentine Wallace <vwallace@protonmail.com>"]
license = "MIT OR Apache-2.0"
-repository = "http://github.com/lightningdevkit/rust-lightning"
+repository = "https://github.com/lightningdevkit/rust-lightning"
description = """
Utilities to perform required background tasks for Rust Lightning.
"""
[features]
futures = [ ]
-std = ["lightning/std", "lightning-rapid-gossip-sync/std"]
+std = ["bitcoin/std", "lightning/std", "lightning-rapid-gossip-sync/std"]
+no-std = ["bitcoin/no-std", "lightning/no-std", "lightning-rapid-gossip-sync/no-std"]
default = ["std"]
[dependencies]
bitcoin = { version = "0.29.0", default-features = false }
-lightning = { version = "0.0.116-alpha1", path = "../lightning", default-features = false }
-lightning-rapid-gossip-sync = { version = "0.0.116-alpha1", path = "../lightning-rapid-gossip-sync", default-features = false }
+lightning = { version = "0.0.117-alpha2", path = "../lightning", default-features = false }
+lightning-rapid-gossip-sync = { version = "0.0.117-alpha2", 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.116-alpha1", path = "../lightning", features = ["_test_utils"] }
-lightning-invoice = { version = "0.24.0-alpha1", path = "../lightning-invoice" }
-lightning-persister = { version = "0.0.116-alpha1", path = "../lightning-persister" }
+lightning = { version = "0.0.117-alpha2", path = "../lightning", features = ["_test_utils"] }
+lightning-invoice = { version = "0.25.0-alpha2", path = "../lightning-invoice" }
+lightning-persister = { version = "0.0.117-alpha2", path = "../lightning-persister" }
use lightning::routing::gossip::{NetworkGraph, P2PGossipSync};
use lightning::routing::utxo::UtxoLookup;
use lightning::routing::router::Router;
-use lightning::routing::scoring::{Score, WriteableScore};
+use lightning::routing::scoring::{ScoreUpdate, WriteableScore};
use lightning::util::logger::Logger;
use lightning::util::persist::Persister;
#[cfg(feature = "std")]
fn update_scorer<'a, S: 'static + Deref<Target = SC> + Send + Sync, SC: 'a + WriteableScore<'a>>(
scorer: &'a S, event: &Event
) -> bool {
- let mut score = scorer.lock();
match event {
Event::PaymentPathFailed { ref path, short_channel_id: Some(scid), .. } => {
+ let mut score = scorer.write_lock();
score.payment_path_failed(path, *scid);
},
Event::PaymentPathFailed { ref path, payment_failed_permanently: true, .. } => {
// Reached if the destination explicitly failed it back. We treat this as a successful probe
// because the payment made it all the way to the destination with sufficient liquidity.
+ let mut score = scorer.write_lock();
score.probe_successful(path);
},
Event::PaymentPathSuccessful { path, .. } => {
+ let mut score = scorer.write_lock();
score.payment_path_successful(path);
},
Event::ProbeSuccessful { path, .. } => {
+ let mut score = scorer.write_lock();
score.probe_successful(path);
},
Event::ProbeFailed { path, short_channel_id: Some(scid), .. } => {
+ let mut score = scorer.write_lock();
score.probe_failed(path, *scid);
},
_ => return false,
// see `await_start`'s use below.
let mut await_start = None;
if $check_slow_await { await_start = Some($get_timer(1)); }
- let updates_available = $await;
+ $await;
let await_slow = if $check_slow_await { $timer_elapsed(&mut await_start.unwrap(), 1) } else { false };
// Exit the loop if the background processor was requested to stop.
break;
}
- if updates_available {
+ if $channel_manager.get_and_clear_needs_persistence() {
log_trace!($logger, "Persisting ChannelManager...");
$persister.persist_manager(&*$channel_manager)?;
log_trace!($logger, "Done persisting ChannelManager.");
/// For example, in order to process background events in a [Tokio](https://tokio.rs/) task, you
/// could setup `process_events_async` like this:
/// ```
-/// # struct MyPersister {}
-/// # impl lightning::util::persist::KVStorePersister for MyPersister {
-/// # fn persist<W: lightning::util::ser::Writeable>(&self, key: &str, object: &W) -> lightning::io::Result<()> { Ok(()) }
+/// # use lightning::io;
+/// # use std::sync::{Arc, Mutex};
+/// # use std::sync::atomic::{AtomicBool, Ordering};
+/// # use lightning_background_processor::{process_events_async, GossipSync};
+/// # struct MyStore {}
+/// # impl lightning::util::persist::KVStore for MyStore {
+/// # fn read(&self, namespace: &str, sub_namespace: &str, key: &str) -> io::Result<Vec<u8>> { Ok(Vec::new()) }
+/// # fn write(&self, namespace: &str, sub_namespace: &str, key: &str, buf: &[u8]) -> io::Result<()> { Ok(()) }
+/// # fn remove(&self, namespace: &str, sub_namespace: &str, key: &str, lazy: bool) -> io::Result<()> { Ok(()) }
+/// # fn list(&self, namespace: &str, sub_namespace: &str) -> io::Result<Vec<String>> { Ok(Vec::new()) }
/// # }
/// # struct MyEventHandler {}
/// # impl MyEventHandler {
/// # fn send_data(&mut self, _data: &[u8], _resume_read: bool) -> usize { 0 }
/// # fn disconnect_socket(&mut self) {}
/// # }
-/// # use std::sync::{Arc, Mutex};
-/// # use std::sync::atomic::{AtomicBool, Ordering};
-/// # use lightning_background_processor::{process_events_async, GossipSync};
/// # type MyBroadcaster = dyn lightning::chain::chaininterface::BroadcasterInterface + Send + Sync;
/// # type MyFeeEstimator = dyn lightning::chain::chaininterface::FeeEstimator + Send + Sync;
/// # type MyNodeSigner = dyn lightning::sign::NodeSigner + Send + Sync;
/// # type MyUtxoLookup = dyn lightning::routing::utxo::UtxoLookup + Send + Sync;
/// # type MyFilter = dyn lightning::chain::Filter + Send + Sync;
/// # type MyLogger = dyn lightning::util::logger::Logger + Send + Sync;
-/// # type MyChainMonitor = lightning::chain::chainmonitor::ChainMonitor<lightning::sign::InMemorySigner, Arc<MyFilter>, Arc<MyBroadcaster>, Arc<MyFeeEstimator>, Arc<MyLogger>, Arc<MyPersister>>;
+/// # type MyChainMonitor = lightning::chain::chainmonitor::ChainMonitor<lightning::sign::InMemorySigner, Arc<MyFilter>, Arc<MyBroadcaster>, Arc<MyFeeEstimator>, Arc<MyLogger>, Arc<MyStore>>;
/// # type MyPeerManager = lightning::ln::peer_handler::SimpleArcPeerManager<MySocketDescriptor, MyChainMonitor, MyBroadcaster, MyFeeEstimator, MyUtxoLookup, MyLogger>;
/// # type MyNetworkGraph = lightning::routing::gossip::NetworkGraph<Arc<MyLogger>>;
/// # type MyGossipSync = lightning::routing::gossip::P2PGossipSync<Arc<MyNetworkGraph>, Arc<MyUtxoLookup>, Arc<MyLogger>>;
/// # type MyChannelManager = lightning::ln::channelmanager::SimpleArcChannelManager<MyChainMonitor, MyBroadcaster, MyFeeEstimator, MyLogger>;
/// # type MyScorer = Mutex<lightning::routing::scoring::ProbabilisticScorer<Arc<MyNetworkGraph>, Arc<MyLogger>>>;
///
-/// # async fn setup_background_processing(my_persister: Arc<MyPersister>, my_event_handler: Arc<MyEventHandler>, my_chain_monitor: Arc<MyChainMonitor>, my_channel_manager: Arc<MyChannelManager>, my_gossip_sync: Arc<MyGossipSync>, my_logger: Arc<MyLogger>, my_scorer: Arc<MyScorer>, my_peer_manager: Arc<MyPeerManager>) {
+/// # async fn setup_background_processing(my_persister: Arc<MyStore>, my_event_handler: Arc<MyEventHandler>, my_chain_monitor: Arc<MyChainMonitor>, my_channel_manager: Arc<MyChannelManager>, my_gossip_sync: Arc<MyGossipSync>, my_logger: Arc<MyLogger>, my_scorer: Arc<MyScorer>, my_peer_manager: Arc<MyPeerManager>) {
/// let background_persister = Arc::clone(&my_persister);
/// let background_event_handler = Arc::clone(&my_event_handler);
/// let background_chain_mon = Arc::clone(&my_chain_monitor);
channel_manager, channel_manager.process_pending_events_async(async_event_handler).await,
gossip_sync, peer_manager, logger, scorer, should_break, {
let fut = Selector {
- a: channel_manager.get_persistable_update_future(),
+ a: channel_manager.get_event_or_persistence_needed_future(),
b: chain_monitor.get_update_future(),
c: sleeper(if mobile_interruptable_platform { Duration::from_millis(100) } else { Duration::from_secs(FASTEST_TIMER) }),
};
match fut.await {
- SelectorOutput::A => true,
- SelectorOutput::B => false,
+ SelectorOutput::A|SelectorOutput::B => {},
SelectorOutput::C(exit) => {
should_break = exit;
- false
}
}
}, |t| sleeper(Duration::from_secs(t)),
define_run_body!(persister, chain_monitor, chain_monitor.process_pending_events(&event_handler),
channel_manager, channel_manager.process_pending_events(&event_handler),
gossip_sync, peer_manager, logger, scorer, stop_thread.load(Ordering::Acquire),
- Sleeper::from_two_futures(
- channel_manager.get_persistable_update_future(),
+ { Sleeper::from_two_futures(
+ channel_manager.get_event_or_persistence_needed_future(),
chain_monitor.get_update_future()
- ).wait_timeout(Duration::from_millis(100)),
+ ).wait_timeout(Duration::from_millis(100)); },
|_| Instant::now(), |time: &Instant, dur| time.elapsed().as_secs() > dur, false)
});
Self { stop_thread: stop_thread_clone, thread_handle: Some(handle) }
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, Score};
+ use lightning::routing::scoring::{ChannelUsage, ScoreUpdate, ScoreLookUp};
use lightning::util::config::UserConfig;
use lightning::util::ser::Writeable;
use lightning::util::test_utils;
- use lightning::util::persist::KVStorePersister;
- use lightning_persister::FilesystemPersister;
+ use lightning::util::persist::{KVStore, CHANNEL_MANAGER_PERSISTENCE_NAMESPACE, CHANNEL_MANAGER_PERSISTENCE_SUB_NAMESPACE, CHANNEL_MANAGER_PERSISTENCE_KEY, NETWORK_GRAPH_PERSISTENCE_NAMESPACE, NETWORK_GRAPH_PERSISTENCE_SUB_NAMESPACE, NETWORK_GRAPH_PERSISTENCE_KEY, SCORER_PERSISTENCE_NAMESPACE, SCORER_PERSISTENCE_SUB_NAMESPACE, SCORER_PERSISTENCE_KEY};
+ use lightning_persister::fs_store::FilesystemStore;
use std::collections::VecDeque;
use std::{fs, env};
use std::path::PathBuf;
>,
Arc<test_utils::TestLogger>>;
- type ChainMonitor = chainmonitor::ChainMonitor<InMemorySigner, Arc<test_utils::TestChainSource>, Arc<test_utils::TestBroadcaster>, Arc<test_utils::TestFeeEstimator>, Arc<test_utils::TestLogger>, Arc<FilesystemPersister>>;
+ type ChainMonitor = chainmonitor::ChainMonitor<InMemorySigner, Arc<test_utils::TestChainSource>, Arc<test_utils::TestBroadcaster>, Arc<test_utils::TestFeeEstimator>, Arc<test_utils::TestLogger>, Arc<FilesystemStore>>;
type PGS = Arc<P2PGossipSync<Arc<NetworkGraph<Arc<test_utils::TestLogger>>>, Arc<test_utils::TestChainSource>, Arc<test_utils::TestLogger>>>;
type RGS = Arc<RapidGossipSync<Arc<NetworkGraph<Arc<test_utils::TestLogger>>>, Arc<test_utils::TestLogger>>>;
rapid_gossip_sync: RGS,
peer_manager: Arc<PeerManager<TestDescriptor, Arc<test_utils::TestChannelMessageHandler>, Arc<test_utils::TestRoutingMessageHandler>, IgnoringMessageHandler, Arc<test_utils::TestLogger>, IgnoringMessageHandler, Arc<KeysManager>>>,
chain_monitor: Arc<ChainMonitor>,
- persister: Arc<FilesystemPersister>,
+ kv_store: Arc<FilesystemStore>,
tx_broadcaster: Arc<test_utils::TestBroadcaster>,
network_graph: Arc<NetworkGraph<Arc<test_utils::TestLogger>>>,
logger: Arc<test_utils::TestLogger>,
impl Drop for Node {
fn drop(&mut self) {
- let data_dir = self.persister.get_data_dir();
+ let data_dir = self.kv_store.get_data_dir();
match fs::remove_dir_all(data_dir.clone()) {
- Err(e) => println!("Failed to remove test persister directory {}: {}", data_dir, e),
+ Err(e) => println!("Failed to remove test store directory {}: {}", data_dir.display(), e),
_ => {}
}
}
graph_persistence_notifier: Option<SyncSender<()>>,
manager_error: Option<(std::io::ErrorKind, &'static str)>,
scorer_error: Option<(std::io::ErrorKind, &'static str)>,
- filesystem_persister: FilesystemPersister,
+ kv_store: FilesystemStore,
}
impl Persister {
- fn new(data_dir: String) -> Self {
- let filesystem_persister = FilesystemPersister::new(data_dir);
- Self { graph_error: None, graph_persistence_notifier: None, manager_error: None, scorer_error: None, filesystem_persister }
+ fn new(data_dir: PathBuf) -> Self {
+ let kv_store = FilesystemStore::new(data_dir);
+ Self { graph_error: None, graph_persistence_notifier: None, manager_error: None, scorer_error: None, kv_store }
}
fn with_graph_error(self, error: std::io::ErrorKind, message: &'static str) -> Self {
}
}
- impl KVStorePersister for Persister {
- fn persist<W: Writeable>(&self, key: &str, object: &W) -> std::io::Result<()> {
- if key == "manager" {
+ impl KVStore for Persister {
+ fn read(&self, namespace: &str, sub_namespace: &str, key: &str) -> lightning::io::Result<Vec<u8>> {
+ self.kv_store.read(namespace, sub_namespace, key)
+ }
+
+ fn write(&self, namespace: &str, sub_namespace: &str, key: &str, buf: &[u8]) -> lightning::io::Result<()> {
+ if namespace == CHANNEL_MANAGER_PERSISTENCE_NAMESPACE &&
+ sub_namespace == CHANNEL_MANAGER_PERSISTENCE_SUB_NAMESPACE &&
+ key == CHANNEL_MANAGER_PERSISTENCE_KEY
+ {
if let Some((error, message)) = self.manager_error {
return Err(std::io::Error::new(error, message))
}
}
- if key == "network_graph" {
+ if namespace == NETWORK_GRAPH_PERSISTENCE_NAMESPACE &&
+ sub_namespace == NETWORK_GRAPH_PERSISTENCE_SUB_NAMESPACE &&
+ key == NETWORK_GRAPH_PERSISTENCE_KEY
+ {
if let Some(sender) = &self.graph_persistence_notifier {
match sender.send(()) {
Ok(()) => {},
}
}
- if key == "scorer" {
+ if namespace == SCORER_PERSISTENCE_NAMESPACE &&
+ sub_namespace == SCORER_PERSISTENCE_SUB_NAMESPACE &&
+ key == SCORER_PERSISTENCE_KEY
+ {
if let Some((error, message)) = self.scorer_error {
return Err(std::io::Error::new(error, message))
}
}
- self.filesystem_persister.persist(key, object)
+ self.kv_store.write(namespace, sub_namespace, key, buf)
+ }
+
+ fn remove(&self, namespace: &str, sub_namespace: &str, key: &str, lazy: bool) -> lightning::io::Result<()> {
+ self.kv_store.remove(namespace, sub_namespace, key, lazy)
+ }
+
+ fn list(&self, namespace: &str, sub_namespace: &str) -> lightning::io::Result<Vec<String>> {
+ self.kv_store.list(namespace, sub_namespace)
}
}
fn write<W: lightning::util::ser::Writer>(&self, _: &mut W) -> Result<(), lightning::io::Error> { Ok(()) }
}
- impl Score for TestScorer {
+ impl ScoreLookUp for TestScorer {
type ScoreParams = ();
fn channel_penalty_msat(
&self, _short_channel_id: u64, _source: &NodeId, _target: &NodeId, _usage: ChannelUsage, _score_params: &Self::ScoreParams
) -> u64 { unimplemented!(); }
+ }
+ impl ScoreUpdate for TestScorer {
fn payment_path_failed(&mut self, actual_path: &Path, actual_short_channel_id: u64) {
if let Some(expectations) = &mut self.event_expectations {
match expectations.pop_front().unwrap() {
let seed = [i as u8; 32];
let router = Arc::new(DefaultRouter::new(network_graph.clone(), logger.clone(), seed, scorer.clone(), ()));
let chain_source = Arc::new(test_utils::TestChainSource::new(Network::Bitcoin));
- let persister = Arc::new(FilesystemPersister::new(format!("{}_persister_{}", &persist_dir, i)));
+ let kv_store = Arc::new(FilesystemStore::new(format!("{}_persister_{}", &persist_dir, i).into()));
let now = Duration::from_secs(genesis_block.header.time as u64);
let keys_manager = Arc::new(KeysManager::new(&seed, now.as_secs(), now.subsec_nanos()));
- let chain_monitor = Arc::new(chainmonitor::ChainMonitor::new(Some(chain_source.clone()), tx_broadcaster.clone(), logger.clone(), fee_estimator.clone(), persister.clone()));
+ let chain_monitor = Arc::new(chainmonitor::ChainMonitor::new(Some(chain_source.clone()), tx_broadcaster.clone(), logger.clone(), fee_estimator.clone(), kv_store.clone()));
let best_block = BestBlock::from_network(network);
let params = ChainParameters { network, best_block };
let manager = Arc::new(ChannelManager::new(fee_estimator.clone(), chain_monitor.clone(), tx_broadcaster.clone(), router.clone(), logger.clone(), keys_manager.clone(), keys_manager.clone(), keys_manager.clone(), UserConfig::default(), params, genesis_block.header.time));
onion_message_handler: IgnoringMessageHandler{}, custom_message_handler: IgnoringMessageHandler{}
};
let peer_manager = Arc::new(PeerManager::new(msg_handler, 0, &seed, logger.clone(), keys_manager.clone()));
- let node = Node { node: manager, p2p_gossip_sync, rapid_gossip_sync, peer_manager, chain_monitor, persister, tx_broadcaster, network_graph, logger, best_block, scorer };
+ let node = Node { node: manager, p2p_gossip_sync, rapid_gossip_sync, peer_manager, chain_monitor, kv_store, tx_broadcaster, network_graph, logger, best_block, scorer };
nodes.push(node);
}
let tx = open_channel!(nodes[0], nodes[1], 100000);
// Initiate the background processors to watch each node.
- let data_dir = nodes[0].persister.get_data_dir();
+ let data_dir = nodes[0].kv_store.get_data_dir();
let persister = Arc::new(Persister::new(data_dir));
let event_handler = |_: _| {};
let bg_processor = BackgroundProcessor::start(persister, event_handler, nodes[0].chain_monitor.clone(), nodes[0].node.clone(), nodes[0].p2p_gossip_sync(), nodes[0].peer_manager.clone(), nodes[0].logger.clone(), Some(nodes[0].scorer.clone()));
check_persisted_data!(nodes[0].node, filepath.clone());
loop {
- if !nodes[0].node.get_persistence_condvar_value() { break }
+ if !nodes[0].node.get_event_or_persist_condvar_value() { break }
}
// Force-close the channel.
// Check that the force-close updates are persisted.
check_persisted_data!(nodes[0].node, filepath.clone());
loop {
- if !nodes[0].node.get_persistence_condvar_value() { break }
+ if !nodes[0].node.get_event_or_persist_condvar_value() { break }
}
// Check network graph is persisted
// `ChainMonitor::rebroadcast_pending_claims` is called every `REBROADCAST_TIMER`, and
// `PeerManager::timer_tick_occurred` every `PING_TIMER`.
let (_, nodes) = create_nodes(1, "test_timer_tick_called");
- let data_dir = nodes[0].persister.get_data_dir();
+ let data_dir = nodes[0].kv_store.get_data_dir();
let persister = Arc::new(Persister::new(data_dir));
let event_handler = |_: _| {};
let bg_processor = BackgroundProcessor::start(persister, event_handler, nodes[0].chain_monitor.clone(), nodes[0].node.clone(), nodes[0].no_gossip_sync(), nodes[0].peer_manager.clone(), nodes[0].logger.clone(), Some(nodes[0].scorer.clone()));
let (_, nodes) = create_nodes(2, "test_persist_error");
open_channel!(nodes[0], nodes[1], 100000);
- let data_dir = nodes[0].persister.get_data_dir();
+ let data_dir = nodes[0].kv_store.get_data_dir();
let persister = Arc::new(Persister::new(data_dir).with_manager_error(std::io::ErrorKind::Other, "test"));
let event_handler = |_: _| {};
let bg_processor = BackgroundProcessor::start(persister, event_handler, nodes[0].chain_monitor.clone(), nodes[0].node.clone(), nodes[0].no_gossip_sync(), nodes[0].peer_manager.clone(), nodes[0].logger.clone(), Some(nodes[0].scorer.clone()));
let (_, nodes) = create_nodes(2, "test_persist_error_sync");
open_channel!(nodes[0], nodes[1], 100000);
- let data_dir = nodes[0].persister.get_data_dir();
+ let data_dir = nodes[0].kv_store.get_data_dir();
let persister = Arc::new(Persister::new(data_dir).with_manager_error(std::io::ErrorKind::Other, "test"));
let bp_future = super::process_events_async(
fn test_network_graph_persist_error() {
// Test that if we encounter an error during network graph persistence, an error gets returned.
let (_, nodes) = create_nodes(2, "test_persist_network_graph_error");
- let data_dir = nodes[0].persister.get_data_dir();
+ let data_dir = nodes[0].kv_store.get_data_dir();
let persister = Arc::new(Persister::new(data_dir).with_graph_error(std::io::ErrorKind::Other, "test"));
let event_handler = |_: _| {};
let bg_processor = BackgroundProcessor::start(persister, event_handler, nodes[0].chain_monitor.clone(), nodes[0].node.clone(), nodes[0].p2p_gossip_sync(), nodes[0].peer_manager.clone(), nodes[0].logger.clone(), Some(nodes[0].scorer.clone()));
fn test_scorer_persist_error() {
// Test that if we encounter an error during scorer persistence, an error gets returned.
let (_, nodes) = create_nodes(2, "test_persist_scorer_error");
- let data_dir = nodes[0].persister.get_data_dir();
+ let data_dir = nodes[0].kv_store.get_data_dir();
let persister = Arc::new(Persister::new(data_dir).with_scorer_error(std::io::ErrorKind::Other, "test"));
let event_handler = |_: _| {};
let bg_processor = BackgroundProcessor::start(persister, event_handler, nodes[0].chain_monitor.clone(), nodes[0].node.clone(), nodes[0].no_gossip_sync(), nodes[0].peer_manager.clone(), nodes[0].logger.clone(), Some(nodes[0].scorer.clone()));
fn test_background_event_handling() {
let (_, mut nodes) = create_nodes(2, "test_background_event_handling");
let channel_value = 100000;
- let data_dir = nodes[0].persister.get_data_dir();
+ let data_dir = nodes[0].kv_store.get_data_dir();
let persister = Arc::new(Persister::new(data_dir.clone()));
// Set up a background event handler for FundingGenerationReady events.
#[test]
fn test_scorer_persistence() {
let (_, nodes) = create_nodes(2, "test_scorer_persistence");
- let data_dir = nodes[0].persister.get_data_dir();
+ let data_dir = nodes[0].kv_store.get_data_dir();
let persister = Arc::new(Persister::new(data_dir));
let event_handler = |_: _| {};
let bg_processor = BackgroundProcessor::start(persister, event_handler, nodes[0].chain_monitor.clone(), nodes[0].node.clone(), nodes[0].no_gossip_sync(), nodes[0].peer_manager.clone(), nodes[0].logger.clone(), Some(nodes[0].scorer.clone()));
let (sender, receiver) = std::sync::mpsc::sync_channel(1);
let (_, nodes) = create_nodes(2, "test_not_pruning_network_graph_until_graph_sync_completion");
- let data_dir = nodes[0].persister.get_data_dir();
+ let data_dir = nodes[0].kv_store.get_data_dir();
let persister = Arc::new(Persister::new(data_dir).with_graph_persistence_notifier(sender));
let event_handler = |_: _| {};
let (sender, receiver) = std::sync::mpsc::sync_channel(1);
let (_, nodes) = create_nodes(2, "test_not_pruning_network_graph_until_graph_sync_completion_async");
- let data_dir = nodes[0].persister.get_data_dir();
+ let data_dir = nodes[0].kv_store.get_data_dir();
let persister = Arc::new(Persister::new(data_dir).with_graph_persistence_notifier(sender));
let (exit_sender, exit_receiver) = tokio::sync::watch::channel(());
channel_features: ChannelFeatures::empty(),
fee_msat: 0,
cltv_expiry_delta: MIN_CLTV_EXPIRY_DELTA as u32,
+ maybe_announced_channel: true,
}], blinded_tail: None };
$nodes[0].scorer.lock().unwrap().expect(TestResult::PaymentFailure { path: path.clone(), short_channel_id: scored_scid });
};
let (_, nodes) = create_nodes(1, "test_payment_path_scoring");
- let data_dir = nodes[0].persister.get_data_dir();
+ let data_dir = nodes[0].kv_store.get_data_dir();
let persister = Arc::new(Persister::new(data_dir));
let bg_processor = BackgroundProcessor::start(persister, event_handler, nodes[0].chain_monitor.clone(), nodes[0].node.clone(), nodes[0].no_gossip_sync(), nodes[0].peer_manager.clone(), nodes[0].logger.clone(), Some(nodes[0].scorer.clone()));
};
let (_, nodes) = create_nodes(1, "test_payment_path_scoring_async");
- let data_dir = nodes[0].persister.get_data_dir();
+ let data_dir = nodes[0].kv_store.get_data_dir();
let persister = Arc::new(Persister::new(data_dir));
let (exit_sender, exit_receiver) = tokio::sync::watch::channel(());
[package]
name = "lightning-block-sync"
-version = "0.0.116-alpha1"
+version = "0.0.117-alpha2"
authors = ["Jeffrey Czyz", "Matt Corallo"]
license = "MIT OR Apache-2.0"
-repository = "http://github.com/lightningdevkit/rust-lightning"
+repository = "https://github.com/lightningdevkit/rust-lightning"
description = """
Utilities to fetch the chain data from a block source and feed them into Rust Lightning.
"""
[dependencies]
bitcoin = "0.29.0"
-lightning = { version = "0.0.116-alpha1", path = "../lightning" }
+lightning = { version = "0.0.117-alpha2", 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.116-alpha1", path = "../lightning", features = ["_test_utils"] }
+lightning = { version = "0.0.117-alpha2", path = "../lightning", features = ["_test_utils"] }
tokio = { version = "1.14", features = [ "macros", "rt" ] }
use std::convert::From;
use std::convert::TryFrom;
use std::convert::TryInto;
+use std::str::FromStr;
use bitcoin::hashes::Hash;
+impl TryInto<serde_json::Value> for JsonResponse {
+ type Error = std::io::Error;
+ fn try_into(self) -> Result<serde_json::Value, std::io::Error> { Ok(self.0) }
+}
+
/// Conversion from `std::io::Error` into `BlockSourceError`.
impl From<std::io::Error> for BlockSourceError {
fn from(e: std::io::Error) -> BlockSourceError {
}
}
+/// Parses binary data as a block hash.
+impl TryInto<BlockHash> for BinaryResponse {
+ type Error = std::io::Error;
+
+ fn try_into(self) -> std::io::Result<BlockHash> {
+ BlockHash::from_slice(&self.0).map_err(|_|
+ std::io::Error::new(std::io::ErrorKind::InvalidData, "bad block hash length")
+ )
+ }
+}
+
/// Converts a JSON value into block header data. The JSON value may be an object representing a
/// block header or an array of such objects. In the latter case, the first object is converted.
impl TryInto<BlockHeaderData> for JsonResponse {
}
}
+impl TryInto<BlockHash> for JsonResponse {
+ type Error = std::io::Error;
+
+ fn try_into(self) -> std::io::Result<BlockHash> {
+ match self.0.as_str() {
+ None => Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "expected JSON string")),
+ Some(hex_data) if hex_data.len() != 64 =>
+ Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid hash length")),
+ Some(hex_data) => BlockHash::from_str(hex_data)
+ .map_err(|_| std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid hex data")),
+ }
+ }
+}
+
+/// The REST `getutxos` endpoint retuns a whole pile of data we don't care about and one bit we do
+/// - whether the `hit bitmap` field had any entries. Thus we condense the result down into only
+/// that.
+pub(crate) struct GetUtxosResponse {
+ pub(crate) hit_bitmap_nonempty: bool
+}
+
+impl TryInto<GetUtxosResponse> for JsonResponse {
+ type Error = std::io::Error;
+
+ fn try_into(self) -> std::io::Result<GetUtxosResponse> {
+ let bitmap_str =
+ self.0.as_object().ok_or(std::io::Error::new(std::io::ErrorKind::InvalidData, "expected an object"))?
+ .get("bitmap").ok_or(std::io::Error::new(std::io::ErrorKind::InvalidData, "missing bitmap field"))?
+ .as_str().ok_or(std::io::Error::new(std::io::ErrorKind::InvalidData, "bitmap should be an str"))?;
+ let mut hit_bitmap_nonempty = false;
+ for c in bitmap_str.chars() {
+ if c < '0' || c > '9' {
+ return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid byte"));
+ }
+ if c > '0' { hit_bitmap_nonempty = true; }
+ }
+ Ok(GetUtxosResponse { hit_bitmap_nonempty })
+ }
+}
+
#[cfg(test)]
pub(crate) mod tests {
use super::*;
--- /dev/null
+//! When fetching gossip from peers, lightning nodes need to validate that gossip against the
+//! current UTXO set. This module defines an implementation of the LDK API required to do so
+//! against a [`BlockSource`] which implements a few additional methods for accessing the UTXO set.
+
+use crate::{AsyncBlockSourceResult, BlockData, BlockSource, BlockSourceError};
+
+use bitcoin::blockdata::block::Block;
+use bitcoin::blockdata::transaction::{TxOut, OutPoint};
+use bitcoin::hash_types::BlockHash;
+
+use lightning::sign::NodeSigner;
+
+use lightning::ln::peer_handler::{CustomMessageHandler, PeerManager, SocketDescriptor};
+use lightning::ln::msgs::{ChannelMessageHandler, OnionMessageHandler};
+
+use lightning::routing::gossip::{NetworkGraph, P2PGossipSync};
+use lightning::routing::utxo::{UtxoFuture, UtxoLookup, UtxoResult, UtxoLookupError};
+
+use lightning::util::logger::Logger;
+
+use std::sync::{Arc, Mutex};
+use std::collections::VecDeque;
+use std::future::Future;
+use std::ops::Deref;
+use std::pin::Pin;
+use std::task::Poll;
+
+/// A trait which extends [`BlockSource`] and can be queried to fetch the block at a given height
+/// as well as whether a given output is unspent (i.e. a member of the current UTXO set).
+///
+/// Note that while this is implementable for a [`BlockSource`] which returns filtered block data
+/// (i.e. [`BlockData::HeaderOnly`] for [`BlockSource::get_block`] requests), such an
+/// implementation will reject all gossip as it is not fully able to verify the UTXOs referenced.
+pub trait UtxoSource : BlockSource + 'static {
+ /// Fetches the block hash of the block at the given height.
+ ///
+ /// This will, in turn, be passed to to [`BlockSource::get_block`] to fetch the block needed
+ /// for gossip validation.
+ fn get_block_hash_by_height<'a>(&'a self, block_height: u32) -> AsyncBlockSourceResult<'a, BlockHash>;
+
+ /// Returns true if the given output has *not* been spent, i.e. is a member of the current UTXO
+ /// set.
+ fn is_output_unspent<'a>(&'a self, outpoint: OutPoint) -> AsyncBlockSourceResult<'a, bool>;
+}
+
+/// A generic trait which is able to spawn futures in the background.
+///
+/// If the `tokio` feature is enabled, this is implemented on `TokioSpawner` struct which
+/// delegates to `tokio::spawn()`.
+pub trait FutureSpawner : Send + Sync + 'static {
+ /// Spawns the given future as a background task.
+ ///
+ /// This method MUST NOT block on the given future immediately.
+ fn spawn<T: Future<Output = ()> + Send + 'static>(&self, future: T);
+}
+
+#[cfg(feature = "tokio")]
+/// A trivial [`FutureSpawner`] which delegates to `tokio::spawn`.
+pub struct TokioSpawner;
+#[cfg(feature = "tokio")]
+impl FutureSpawner for TokioSpawner {
+ fn spawn<T: Future<Output = ()> + Send + 'static>(&self, future: T) {
+ tokio::spawn(future);
+ }
+}
+
+/// A trivial future which joins two other futures and polls them at the same time, returning only
+/// once both complete.
+pub(crate) struct Joiner<
+ A: Future<Output=Result<(BlockHash, Option<u32>), BlockSourceError>> + Unpin,
+ B: Future<Output=Result<BlockHash, BlockSourceError>> + Unpin,
+> {
+ pub a: A,
+ pub b: B,
+ a_res: Option<(BlockHash, Option<u32>)>,
+ b_res: Option<BlockHash>,
+}
+
+impl<
+ A: Future<Output=Result<(BlockHash, Option<u32>), BlockSourceError>> + Unpin,
+ B: Future<Output=Result<BlockHash, BlockSourceError>> + Unpin,
+> Joiner<A, B> {
+ fn new(a: A, b: B) -> Self { Self { a, b, a_res: None, b_res: None } }
+}
+
+impl<
+ A: Future<Output=Result<(BlockHash, Option<u32>), BlockSourceError>> + Unpin,
+ B: Future<Output=Result<BlockHash, BlockSourceError>> + Unpin,
+> Future for Joiner<A, B> {
+ type Output = Result<((BlockHash, Option<u32>), BlockHash), BlockSourceError>;
+ fn poll(mut self: Pin<&mut Self>, ctx: &mut core::task::Context<'_>) -> Poll<Self::Output> {
+ if self.a_res.is_none() {
+ match Pin::new(&mut self.a).poll(ctx) {
+ Poll::Ready(res) => {
+ if let Ok(ok) = res {
+ self.a_res = Some(ok);
+ } else {
+ return Poll::Ready(Err(res.unwrap_err()));
+ }
+ },
+ Poll::Pending => {},
+ }
+ }
+ if self.b_res.is_none() {
+ match Pin::new(&mut self.b).poll(ctx) {
+ Poll::Ready(res) => {
+ if let Ok(ok) = res {
+ self.b_res = Some(ok);
+ } else {
+ return Poll::Ready(Err(res.unwrap_err()));
+ }
+
+ },
+ Poll::Pending => {},
+ }
+ }
+ if let Some(b_res) = self.b_res {
+ if let Some(a_res) = self.a_res {
+ return Poll::Ready(Ok((a_res, b_res)))
+ }
+ }
+ Poll::Pending
+ }
+}
+
+/// A struct which wraps a [`UtxoSource`] and a few LDK objects and implements the LDK
+/// [`UtxoLookup`] trait.
+///
+/// Note that if you're using this against a Bitcoin Core REST or RPC server, you likely wish to
+/// increase the `rpcworkqueue` setting in Bitcoin Core as LDK attempts to parallelize requests (a
+/// value of 1024 should more than suffice), and ensure you have sufficient file descriptors
+/// available on both Bitcoin Core and your LDK application for each request to hold its own
+/// connection.
+pub struct GossipVerifier<S: FutureSpawner,
+ Blocks: Deref + Send + Sync + 'static + Clone,
+ L: Deref + Send + Sync + 'static,
+ Descriptor: SocketDescriptor + Send + Sync + 'static,
+ CM: Deref + Send + Sync + 'static,
+ OM: Deref + Send + Sync + 'static,
+ CMH: Deref + Send + Sync + 'static,
+ NS: Deref + Send + Sync + 'static,
+> where
+ Blocks::Target: UtxoSource,
+ L::Target: Logger,
+ CM::Target: ChannelMessageHandler,
+ OM::Target: OnionMessageHandler,
+ CMH::Target: CustomMessageHandler,
+ NS::Target: NodeSigner,
+{
+ source: Blocks,
+ peer_manager: Arc<PeerManager<Descriptor, CM, Arc<P2PGossipSync<Arc<NetworkGraph<L>>, Self, L>>, OM, L, CMH, NS>>,
+ gossiper: Arc<P2PGossipSync<Arc<NetworkGraph<L>>, Self, L>>,
+ spawn: S,
+ block_cache: Arc<Mutex<VecDeque<(u32, Block)>>>,
+}
+
+const BLOCK_CACHE_SIZE: usize = 5;
+
+impl<S: FutureSpawner,
+ Blocks: Deref + Send + Sync + Clone,
+ L: Deref + Send + Sync,
+ Descriptor: SocketDescriptor + Send + Sync,
+ CM: Deref + Send + Sync,
+ OM: Deref + Send + Sync,
+ CMH: Deref + Send + Sync,
+ NS: Deref + Send + Sync,
+> GossipVerifier<S, Blocks, L, Descriptor, CM, OM, CMH, NS> where
+ Blocks::Target: UtxoSource,
+ L::Target: Logger,
+ CM::Target: ChannelMessageHandler,
+ OM::Target: OnionMessageHandler,
+ CMH::Target: CustomMessageHandler,
+ NS::Target: NodeSigner,
+{
+ /// Constructs a new [`GossipVerifier`].
+ ///
+ /// This is expected to be given to a [`P2PGossipSync`] (initially constructed with `None` for
+ /// the UTXO lookup) via [`P2PGossipSync::add_utxo_lookup`].
+ pub fn new(source: Blocks, spawn: S, gossiper: Arc<P2PGossipSync<Arc<NetworkGraph<L>>, Self, L>>, peer_manager: Arc<PeerManager<Descriptor, CM, Arc<P2PGossipSync<Arc<NetworkGraph<L>>, Self, L>>, OM, L, CMH, NS>>) -> Self {
+ Self {
+ source, spawn, gossiper, peer_manager,
+ block_cache: Arc::new(Mutex::new(VecDeque::with_capacity(BLOCK_CACHE_SIZE))),
+ }
+ }
+
+ async fn retrieve_utxo(
+ source: Blocks, block_cache: Arc<Mutex<VecDeque<(u32, Block)>>>, short_channel_id: u64
+ ) -> Result<TxOut, UtxoLookupError> {
+ let block_height = (short_channel_id >> 5 * 8) as u32; // block height is most significant three bytes
+ let transaction_index = ((short_channel_id >> 2 * 8) & 0xffffff) as u32;
+ let output_index = (short_channel_id & 0xffff) as u16;
+
+ let (outpoint, output);
+
+ 'tx_found: loop { // Used as a simple goto
+ macro_rules! process_block {
+ ($block: expr) => { {
+ if transaction_index as usize >= $block.txdata.len() {
+ return Err(UtxoLookupError::UnknownTx);
+ }
+ let transaction = &$block.txdata[transaction_index as usize];
+ if output_index as usize >= transaction.output.len() {
+ return Err(UtxoLookupError::UnknownTx);
+ }
+
+ outpoint = OutPoint::new(transaction.txid(), output_index.into());
+ output = transaction.output[output_index as usize].clone();
+ } }
+ }
+ {
+ let recent_blocks = block_cache.lock().unwrap();
+ for (height, block) in recent_blocks.iter() {
+ if *height == block_height {
+ process_block!(block);
+ break 'tx_found;
+ }
+ }
+ }
+
+ let ((_, tip_height_opt), block_hash) =
+ Joiner::new(source.get_best_block(), source.get_block_hash_by_height(block_height))
+ .await
+ .map_err(|_| UtxoLookupError::UnknownTx)?;
+ if let Some(tip_height) = tip_height_opt {
+ // If the block doesn't yet have five confirmations, error out.
+ //
+ // The BOLT spec requires nodes wait for six confirmations before announcing a
+ // channel, and we give them one block of headroom in case we're delayed seeing a
+ // block.
+ if block_height + 5 > tip_height {
+ return Err(UtxoLookupError::UnknownTx);
+ }
+ }
+ let block_data = source.get_block(&block_hash).await
+ .map_err(|_| UtxoLookupError::UnknownTx)?;
+ let block = match block_data {
+ BlockData::HeaderOnly(_) => return Err(UtxoLookupError::UnknownTx),
+ BlockData::FullBlock(block) => block,
+ };
+ process_block!(block);
+ {
+ let mut recent_blocks = block_cache.lock().unwrap();
+ let mut insert = true;
+ for (height, _) in recent_blocks.iter() {
+ if *height == block_height {
+ insert = false;
+ }
+ }
+ if insert {
+ if recent_blocks.len() >= BLOCK_CACHE_SIZE {
+ recent_blocks.pop_front();
+ }
+ recent_blocks.push_back((block_height, block));
+ }
+ }
+ break 'tx_found;
+ };
+ let outpoint_unspent =
+ source.is_output_unspent(outpoint).await.map_err(|_| UtxoLookupError::UnknownTx)?;
+ if outpoint_unspent {
+ Ok(output)
+ } else {
+ Err(UtxoLookupError::UnknownTx)
+ }
+ }
+}
+
+impl<S: FutureSpawner,
+ Blocks: Deref + Send + Sync + Clone,
+ L: Deref + Send + Sync,
+ Descriptor: SocketDescriptor + Send + Sync,
+ CM: Deref + Send + Sync,
+ OM: Deref + Send + Sync,
+ CMH: Deref + Send + Sync,
+ NS: Deref + Send + Sync,
+> Deref for GossipVerifier<S, Blocks, L, Descriptor, CM, OM, CMH, NS> where
+ Blocks::Target: UtxoSource,
+ L::Target: Logger,
+ CM::Target: ChannelMessageHandler,
+ OM::Target: OnionMessageHandler,
+ CMH::Target: CustomMessageHandler,
+ NS::Target: NodeSigner,
+{
+ type Target = Self;
+ fn deref(&self) -> &Self { self }
+}
+
+
+impl<S: FutureSpawner,
+ Blocks: Deref + Send + Sync + Clone,
+ L: Deref + Send + Sync,
+ Descriptor: SocketDescriptor + Send + Sync,
+ CM: Deref + Send + Sync,
+ OM: Deref + Send + Sync,
+ CMH: Deref + Send + Sync,
+ NS: Deref + Send + Sync,
+> UtxoLookup for GossipVerifier<S, Blocks, L, Descriptor, CM, OM, CMH, NS> where
+ Blocks::Target: UtxoSource,
+ L::Target: Logger,
+ CM::Target: ChannelMessageHandler,
+ OM::Target: OnionMessageHandler,
+ CMH::Target: CustomMessageHandler,
+ NS::Target: NodeSigner,
+{
+ fn get_utxo(&self, _genesis_hash: &BlockHash, short_channel_id: u64) -> UtxoResult {
+ let res = UtxoFuture::new();
+ let fut = res.clone();
+ let source = self.source.clone();
+ let gossiper = Arc::clone(&self.gossiper);
+ let block_cache = Arc::clone(&self.block_cache);
+ let pm = Arc::clone(&self.peer_manager);
+ self.spawn.spawn(async move {
+ let res = Self::retrieve_utxo(source, block_cache, short_channel_id).await;
+ fut.resolve(gossiper.network_graph(), &*gossiper, res);
+ pm.process_events();
+ });
+ UtxoResult::Async(res)
+ }
+}
pub mod init;
pub mod poll;
+pub mod gossip;
+
#[cfg(feature = "rest-client")]
pub mod rest;
use crate::{BlockData, BlockHeaderData, BlockSource, AsyncBlockSourceResult};
use crate::http::{BinaryResponse, HttpEndpoint, HttpClient, JsonResponse};
+use crate::gossip::UtxoSource;
+use crate::convert::GetUtxosResponse;
+use bitcoin::OutPoint;
use bitcoin::hash_types::BlockHash;
use bitcoin::hashes::hex::ToHex;
}
}
+impl UtxoSource for RestClient {
+ fn get_block_hash_by_height<'a>(&'a self, block_height: u32) -> AsyncBlockSourceResult<'a, BlockHash> {
+ Box::pin(async move {
+ let resource_path = format!("blockhashbyheight/{}.bin", block_height);
+ Ok(self.request_resource::<BinaryResponse, _>(&resource_path).await?)
+ })
+ }
+
+ fn is_output_unspent<'a>(&'a self, outpoint: OutPoint) -> AsyncBlockSourceResult<'a, bool> {
+ Box::pin(async move {
+ let resource_path = format!("getutxos/{}-{}.json", outpoint.txid.to_hex(), outpoint.vout);
+ let utxo_result =
+ self.request_resource::<JsonResponse, GetUtxosResponse>(&resource_path).await?;
+ Ok(utxo_result.hit_bitmap_nonempty)
+ })
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
use crate::http::BinaryResponse;
use crate::http::client_tests::{HttpServer, MessageBody};
+ use bitcoin::hashes::Hash;
/// Parses binary data as a string-encoded `u32`.
impl TryInto<u32> for BinaryResponse {
Ok(n) => assert_eq!(n, 42),
}
}
+
+ #[tokio::test]
+ async fn parses_negative_getutxos() {
+ let server = HttpServer::responding_with_ok(MessageBody::Content(
+ // A real response contains a few more fields, but we actually only look at the
+ // "bitmap" field, so this should suffice for testing
+ "{\"chainHeight\": 1, \"bitmap\":\"0\",\"utxos\":[]}"
+ ));
+ let client = RestClient::new(server.endpoint()).unwrap();
+
+ let outpoint = OutPoint::new(bitcoin::Txid::from_inner([0; 32]), 0);
+ let unspent_output = client.is_output_unspent(outpoint).await.unwrap();
+ assert_eq!(unspent_output, false);
+ }
+
+ #[tokio::test]
+ async fn parses_positive_getutxos() {
+ let server = HttpServer::responding_with_ok(MessageBody::Content(
+ // A real response contains lots more data, but we actually only look at the "bitmap"
+ // field, so this should suffice for testing
+ "{\"chainHeight\": 1, \"bitmap\":\"1\",\"utxos\":[]}"
+ ));
+ let client = RestClient::new(server.endpoint()).unwrap();
+
+ let outpoint = OutPoint::new(bitcoin::Txid::from_inner([0; 32]), 0);
+ let unspent_output = client.is_output_unspent(outpoint).await.unwrap();
+ assert_eq!(unspent_output, true);
+ }
}
use crate::{BlockData, BlockHeaderData, BlockSource, AsyncBlockSourceResult};
use crate::http::{HttpClient, HttpEndpoint, HttpError, JsonResponse};
+use crate::gossip::UtxoSource;
use bitcoin::hash_types::BlockHash;
use bitcoin::hashes::hex::ToHex;
+use bitcoin::OutPoint;
use std::sync::Mutex;
return Err(std::io::Error::new(std::io::ErrorKind::Other, rpc_error));
}
- let result = &mut response["result"];
- if result.is_null() {
- return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "expected JSON result"));
- }
+ let result = match response.get_mut("result") {
+ Some(result) => result.take(),
+ None =>
+ return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "expected JSON result")),
+ };
- JsonResponse(result.take()).try_into()
+ JsonResponse(result).try_into()
}
}
}
}
+impl UtxoSource for RpcClient {
+ fn get_block_hash_by_height<'a>(&'a self, block_height: u32) -> AsyncBlockSourceResult<'a, BlockHash> {
+ Box::pin(async move {
+ let height_param = serde_json::json!(block_height);
+ Ok(self.call_method("getblockhash", &[height_param]).await?)
+ })
+ }
+
+ fn is_output_unspent<'a>(&'a self, outpoint: OutPoint) -> AsyncBlockSourceResult<'a, bool> {
+ Box::pin(async move {
+ let txid_param = serde_json::json!(outpoint.txid.to_hex());
+ let vout_param = serde_json::json!(outpoint.vout);
+ let include_mempool = serde_json::json!(false);
+ let utxo_opt: serde_json::Value = self.call_method(
+ "gettxout", &[txid_param, vout_param, include_mempool]).await?;
+ Ok(!utxo_opt.is_null())
+ })
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
use crate::http::client_tests::{HttpServer, MessageBody};
+ use bitcoin::hashes::Hash;
+
/// Credentials encoded in base64.
const CREDENTIALS: &'static str = "dXNlcjpwYXNzd29yZA==";
#[tokio::test]
async fn call_method_returning_missing_result() {
- let response = serde_json::json!({ "result": null });
+ let response = serde_json::json!({ });
let server = HttpServer::responding_with_ok(MessageBody::Content(response));
let client = RpcClient::new(CREDENTIALS, server.endpoint()).unwrap();
Ok(count) => assert_eq!(count, 654470),
}
}
+
+ #[tokio::test]
+ async fn fails_to_fetch_spent_utxo() {
+ let response = serde_json::json!({ "result": null });
+ let server = HttpServer::responding_with_ok(MessageBody::Content(response));
+ let client = RpcClient::new(CREDENTIALS, server.endpoint()).unwrap();
+ let outpoint = OutPoint::new(bitcoin::Txid::from_inner([0; 32]), 0);
+ let unspent_output = client.is_output_unspent(outpoint).await.unwrap();
+ assert_eq!(unspent_output, false);
+ }
+
+ #[tokio::test]
+ async fn fetches_utxo() {
+ let response = serde_json::json!({ "result": {"bestblock": 1, "confirmations": 42}});
+ let server = HttpServer::responding_with_ok(MessageBody::Content(response));
+ let client = RpcClient::new(CREDENTIALS, server.endpoint()).unwrap();
+ let outpoint = OutPoint::new(bitcoin::Txid::from_inner([0; 32]), 0);
+ let unspent_output = client.is_output_unspent(outpoint).await.unwrap();
+ assert_eq!(unspent_output, true);
+ }
}
[package]
name = "lightning-custom-message"
-version = "0.0.116-alpha1"
+version = "0.0.117-alpha2"
authors = ["Jeffrey Czyz"]
license = "MIT OR Apache-2.0"
-repository = "http://github.com/lightningdevkit/rust-lightning"
+repository = "https://github.com/lightningdevkit/rust-lightning"
description = """
Utilities for supporting custom peer-to-peer messages in LDK.
"""
[dependencies]
bitcoin = "0.29.0"
-lightning = { version = "0.0.116-alpha1", path = "../lightning" }
+lightning = { version = "0.0.117-alpha2", path = "../lightning" }
[package]
name = "lightning-invoice"
description = "Data structures to parse and serialize BOLT11 lightning invoices"
-version = "0.24.0-alpha1"
+version = "0.25.0-alpha2"
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.116-alpha1", path = "../lightning", default-features = false }
+lightning = { version = "0.0.117-alpha2", 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.116-alpha1", path = "../lightning", default-features = false, features = ["_test_utils"] }
+lightning = { version = "0.0.117-alpha2", path = "../lightning", default-features = false, features = ["_test_utils"] }
hex = "0.4"
serde_json = { version = "1"}
use secp256k1::ecdsa::{RecoveryId, RecoverableSignature};
use secp256k1::PublicKey;
-use super::{Invoice, Sha256, TaggedField, ExpiryTime, MinFinalCltvExpiryDelta, Fallback, PayeePubKey, InvoiceSignature, PositiveTimestamp,
- SemanticError, PrivateRoute, ParseError, ParseOrSemanticError, Description, RawTaggedField, Currency, RawHrp, SiPrefix, RawInvoice,
- constants, SignedRawInvoice, RawDataPart, InvoiceFeatures};
+use super::{Bolt11Invoice, Sha256, TaggedField, ExpiryTime, MinFinalCltvExpiryDelta, Fallback, PayeePubKey, Bolt11InvoiceSignature, PositiveTimestamp,
+ Bolt11SemanticError, PrivateRoute, Bolt11ParseError, ParseOrSemanticError, Description, RawTaggedField, Currency, RawHrp, SiPrefix, RawBolt11Invoice,
+ constants, SignedRawBolt11Invoice, RawDataPart, Bolt11InvoiceFeatures};
use self::hrp_sm::parse_hrp;
}
impl States {
- fn next_state(&self, read_symbol: char) -> Result<States, super::ParseError> {
+ fn next_state(&self, read_symbol: char) -> Result<States, super::Bolt11ParseError> {
match *self {
States::Start => {
if read_symbol == 'l' {
Ok(States::ParseL)
} else {
- Err(super::ParseError::MalformedHRP)
+ Err(super::Bolt11ParseError::MalformedHRP)
}
}
States::ParseL => {
if read_symbol == 'n' {
Ok(States::ParseN)
} else {
- Err(super::ParseError::MalformedHRP)
+ Err(super::Bolt11ParseError::MalformedHRP)
}
},
States::ParseN => {
} else if ['m', 'u', 'n', 'p'].contains(&read_symbol) {
Ok(States::ParseAmountSiPrefix)
} else {
- Err(super::ParseError::UnknownSiPrefix)
+ Err(super::Bolt11ParseError::UnknownSiPrefix)
}
},
- States::ParseAmountSiPrefix => Err(super::ParseError::MalformedHRP),
+ States::ParseAmountSiPrefix => Err(super::Bolt11ParseError::MalformedHRP),
}
}
*range = Some(new_range);
}
- fn step(&mut self, c: char) -> Result<(), super::ParseError> {
+ fn step(&mut self, c: char) -> Result<(), super::Bolt11ParseError> {
let next_state = self.state.next_state(c)?;
match next_state {
States::ParseCurrencyPrefix => {
}
}
- pub fn parse_hrp(input: &str) -> Result<(&str, &str, &str), super::ParseError> {
+ pub fn parse_hrp(input: &str) -> Result<(&str, &str, &str), super::Bolt11ParseError> {
let mut sm = StateMachine::new();
for c in input.chars() {
sm.step(c)?;
}
if !sm.is_final() {
- return Err(super::ParseError::MalformedHRP);
+ return Err(super::Bolt11ParseError::MalformedHRP);
}
let currency = sm.currency_prefix().clone()
impl FromStr for super::Currency {
- type Err = ParseError;
+ type Err = Bolt11ParseError;
- fn from_str(currency_prefix: &str) -> Result<Self, ParseError> {
+ fn from_str(currency_prefix: &str) -> Result<Self, Bolt11ParseError> {
match currency_prefix {
"bc" => Ok(Currency::Bitcoin),
"tb" => Ok(Currency::BitcoinTestnet),
"bcrt" => Ok(Currency::Regtest),
"sb" => Ok(Currency::Simnet),
"tbs" => Ok(Currency::Signet),
- _ => Err(ParseError::UnknownCurrency)
+ _ => Err(Bolt11ParseError::UnknownCurrency)
}
}
}
impl FromStr for SiPrefix {
- type Err = ParseError;
+ type Err = Bolt11ParseError;
- fn from_str(currency_prefix: &str) -> Result<Self, ParseError> {
+ fn from_str(currency_prefix: &str) -> Result<Self, Bolt11ParseError> {
use crate::SiPrefix::*;
match currency_prefix {
"m" => Ok(Milli),
"u" => Ok(Micro),
"n" => Ok(Nano),
"p" => Ok(Pico),
- _ => Err(ParseError::UnknownSiPrefix)
+ _ => Err(Bolt11ParseError::UnknownSiPrefix)
}
}
}
/// ```
-/// use lightning_invoice::Invoice;
+/// use lightning_invoice::Bolt11Invoice;
///
///
/// let invoice = "lnbc100p1psj9jhxdqud3jxktt5w46x7unfv9kz6mn0v3jsnp4q0d3p2sfluzdx45tqcs\
/// 8s0gyuxjjgux34w75dnc6xp2l35j7es3jd4ugt3lu0xzre26yg5m7ke54n2d5sym4xcmxtl8238xxvw5h5h5\
/// j5r6drg6k6zcqj0fcwg";
///
-/// assert!(invoice.parse::<Invoice>().is_ok());
+/// assert!(invoice.parse::<Bolt11Invoice>().is_ok());
/// ```
-impl FromStr for Invoice {
+impl FromStr for Bolt11Invoice {
type Err = ParseOrSemanticError;
fn from_str(s: &str) -> Result<Self, <Self as FromStr>::Err> {
- let signed = s.parse::<SignedRawInvoice>()?;
- Ok(Invoice::from_signed(signed)?)
+ let signed = s.parse::<SignedRawBolt11Invoice>()?;
+ Ok(Bolt11Invoice::from_signed(signed)?)
}
}
/// 8s0gyuxjjgux34w75dnc6xp2l35j7es3jd4ugt3lu0xzre26yg5m7ke54n2d5sym4xcmxtl8238xxvw5h5h5\
/// j5r6drg6k6zcqj0fcwg";
///
-/// let parsed_1 = invoice.parse::<Invoice>();
+/// let parsed_1 = invoice.parse::<Bolt11Invoice>();
///
-/// let parsed_2 = match invoice.parse::<SignedRawInvoice>() {
-/// Ok(signed) => match Invoice::from_signed(signed) {
+/// let parsed_2 = match invoice.parse::<SignedRawBolt11Invoice>() {
+/// Ok(signed) => match Bolt11Invoice::from_signed(signed) {
/// Ok(invoice) => Ok(invoice),
/// Err(e) => Err(ParseOrSemanticError::SemanticError(e)),
/// },
/// assert!(parsed_1.is_ok());
/// assert_eq!(parsed_1, parsed_2);
/// ```
-impl FromStr for SignedRawInvoice {
- type Err = ParseError;
+impl FromStr for SignedRawBolt11Invoice {
+ type Err = Bolt11ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (hrp, data, var) = bech32::decode(s)?;
if var == bech32::Variant::Bech32m {
// Consider Bech32m addresses to be "Invalid Checksum", since that is what we'd get if
// we didn't support Bech32m (which lightning does not use).
- return Err(ParseError::Bech32Error(bech32::Error::InvalidChecksum));
+ return Err(Bolt11ParseError::Bech32Error(bech32::Error::InvalidChecksum));
}
if data.len() < 104 {
- return Err(ParseError::TooShortDataPart);
+ return Err(Bolt11ParseError::TooShortDataPart);
}
let raw_hrp: RawHrp = hrp.parse()?;
let data_part = RawDataPart::from_base32(&data[..data.len()-104])?;
- Ok(SignedRawInvoice {
- raw_invoice: RawInvoice {
+ Ok(SignedRawBolt11Invoice {
+ raw_invoice: RawBolt11Invoice {
hrp: raw_hrp,
data: data_part,
},
- hash: RawInvoice::hash_from_parts(
+ hash: RawBolt11Invoice::hash_from_parts(
hrp.as_bytes(),
&data[..data.len()-104]
),
- signature: InvoiceSignature::from_base32(&data[data.len()-104..])?,
+ signature: Bolt11InvoiceSignature::from_base32(&data[data.len()-104..])?,
})
}
}
impl FromStr for RawHrp {
- type Err = ParseError;
+ type Err = Bolt11ParseError;
fn from_str(hrp: &str) -> Result<Self, <Self as FromStr>::Err> {
let parts = parse_hrp(hrp)?;
let si: SiPrefix = parts.2.parse()?;
if let Some(amt) = amount {
if amt.checked_mul(si.multiplier()).is_none() {
- return Err(ParseError::IntegerOverflowError);
+ return Err(Bolt11ParseError::IntegerOverflowError);
}
}
Some(si)
}
impl FromBase32 for RawDataPart {
- type Err = ParseError;
+ type Err = Bolt11ParseError;
fn from_base32(data: &[u5]) -> Result<Self, Self::Err> {
if data.len() < 7 { // timestamp length
- return Err(ParseError::TooShortDataPart);
+ return Err(Bolt11ParseError::TooShortDataPart);
}
let timestamp = PositiveTimestamp::from_base32(&data[0..7])?;
}
impl FromBase32 for PositiveTimestamp {
- type Err = ParseError;
+ type Err = Bolt11ParseError;
fn from_base32(b32: &[u5]) -> Result<Self, Self::Err> {
if b32.len() != 7 {
- return Err(ParseError::InvalidSliceLength("PositiveTimestamp::from_base32()".into()));
+ return Err(Bolt11ParseError::InvalidSliceLength("PositiveTimestamp::from_base32()".into()));
}
let timestamp: u64 = parse_int_be(b32, 32)
.expect("7*5bit < 64bit, no overflow possible");
}
}
-impl FromBase32 for InvoiceSignature {
- type Err = ParseError;
+impl FromBase32 for Bolt11InvoiceSignature {
+ type Err = Bolt11ParseError;
fn from_base32(signature: &[u5]) -> Result<Self, Self::Err> {
if signature.len() != 104 {
- return Err(ParseError::InvalidSliceLength("InvoiceSignature::from_base32()".into()));
+ return Err(Bolt11ParseError::InvalidSliceLength("Bolt11InvoiceSignature::from_base32()".into()));
}
let recoverable_signature_bytes = Vec::<u8>::from_base32(signature)?;
let signature = &recoverable_signature_bytes[0..64];
let recovery_id = RecoveryId::from_i32(recoverable_signature_bytes[64] as i32)?;
- Ok(InvoiceSignature(RecoverableSignature::from_compact(
+ Ok(Bolt11InvoiceSignature(RecoverableSignature::from_compact(
signature,
recovery_id
)?))
)
}
-fn parse_tagged_parts(data: &[u5]) -> Result<Vec<RawTaggedField>, ParseError> {
+fn parse_tagged_parts(data: &[u5]) -> Result<Vec<RawTaggedField>, Bolt11ParseError> {
let mut parts = Vec::<RawTaggedField>::new();
let mut data = data;
while !data.is_empty() {
if data.len() < 3 {
- return Err(ParseError::UnexpectedEndOfTaggedFields);
+ return Err(Bolt11ParseError::UnexpectedEndOfTaggedFields);
}
// Ignore tag at data[0], it will be handled in the TaggedField parsers and
let last_element = 3 + len;
if data.len() < last_element {
- return Err(ParseError::UnexpectedEndOfTaggedFields);
+ return Err(Bolt11ParseError::UnexpectedEndOfTaggedFields);
}
// Get the tagged field's data slice
Ok(field) => {
parts.push(RawTaggedField::KnownSemantics(field))
},
- Err(ParseError::Skip)|Err(ParseError::Bech32Error(bech32::Error::InvalidLength)) => {
+ Err(Bolt11ParseError::Skip)|Err(Bolt11ParseError::Bech32Error(bech32::Error::InvalidLength)) => {
parts.push(RawTaggedField::UnknownSemantics(field.into()))
},
Err(e) => {return Err(e)}
}
impl FromBase32 for TaggedField {
- type Err = ParseError;
+ type Err = Bolt11ParseError;
- fn from_base32(field: &[u5]) -> Result<TaggedField, ParseError> {
+ fn from_base32(field: &[u5]) -> Result<TaggedField, Bolt11ParseError> {
if field.len() < 3 {
- return Err(ParseError::UnexpectedEndOfTaggedFields);
+ return Err(Bolt11ParseError::UnexpectedEndOfTaggedFields);
}
let tag = field[0];
constants::TAG_PAYMENT_METADATA =>
Ok(TaggedField::PaymentMetadata(Vec::<u8>::from_base32(field_data)?)),
constants::TAG_FEATURES =>
- Ok(TaggedField::Features(InvoiceFeatures::from_base32(field_data)?)),
+ Ok(TaggedField::Features(Bolt11InvoiceFeatures::from_base32(field_data)?)),
_ => {
// "A reader MUST skip over unknown fields"
- Err(ParseError::Skip)
+ Err(Bolt11ParseError::Skip)
}
}
}
}
impl FromBase32 for Sha256 {
- type Err = ParseError;
+ type Err = Bolt11ParseError;
- fn from_base32(field_data: &[u5]) -> Result<Sha256, ParseError> {
+ fn from_base32(field_data: &[u5]) -> Result<Sha256, Bolt11ParseError> {
if field_data.len() != 52 {
// "A reader MUST skip over […] a p, [or] h […] field that does not have data_length 52 […]."
- Err(ParseError::Skip)
+ Err(Bolt11ParseError::Skip)
} else {
Ok(Sha256(sha256::Hash::from_slice(&Vec::<u8>::from_base32(field_data)?)
.expect("length was checked before (52 u5 -> 32 u8)")))
}
impl FromBase32 for Description {
- type Err = ParseError;
+ type Err = Bolt11ParseError;
- fn from_base32(field_data: &[u5]) -> Result<Description, ParseError> {
+ fn from_base32(field_data: &[u5]) -> Result<Description, Bolt11ParseError> {
let bytes = Vec::<u8>::from_base32(field_data)?;
let description = String::from(str::from_utf8(&bytes)?);
Ok(Description::new(description).expect(
}
impl FromBase32 for PayeePubKey {
- type Err = ParseError;
+ type Err = Bolt11ParseError;
- fn from_base32(field_data: &[u5]) -> Result<PayeePubKey, ParseError> {
+ fn from_base32(field_data: &[u5]) -> Result<PayeePubKey, Bolt11ParseError> {
if field_data.len() != 53 {
// "A reader MUST skip over […] a n […] field that does not have data_length 53 […]."
- Err(ParseError::Skip)
+ Err(Bolt11ParseError::Skip)
} else {
let data_bytes = Vec::<u8>::from_base32(field_data)?;
let pub_key = PublicKey::from_slice(&data_bytes)?;
}
impl FromBase32 for ExpiryTime {
- type Err = ParseError;
+ type Err = Bolt11ParseError;
- fn from_base32(field_data: &[u5]) -> Result<ExpiryTime, ParseError> {
+ fn from_base32(field_data: &[u5]) -> Result<ExpiryTime, Bolt11ParseError> {
match parse_int_be::<u64, u5>(field_data, 32)
.map(ExpiryTime::from_seconds)
{
Some(t) => Ok(t),
- None => Err(ParseError::IntegerOverflowError),
+ None => Err(Bolt11ParseError::IntegerOverflowError),
}
}
}
impl FromBase32 for MinFinalCltvExpiryDelta {
- type Err = ParseError;
+ type Err = Bolt11ParseError;
- fn from_base32(field_data: &[u5]) -> Result<MinFinalCltvExpiryDelta, ParseError> {
+ fn from_base32(field_data: &[u5]) -> Result<MinFinalCltvExpiryDelta, Bolt11ParseError> {
let expiry = parse_int_be::<u64, u5>(field_data, 32);
if let Some(expiry) = expiry {
Ok(MinFinalCltvExpiryDelta(expiry))
} else {
- Err(ParseError::IntegerOverflowError)
+ Err(Bolt11ParseError::IntegerOverflowError)
}
}
}
impl FromBase32 for Fallback {
- type Err = ParseError;
+ type Err = Bolt11ParseError;
- fn from_base32(field_data: &[u5]) -> Result<Fallback, ParseError> {
+ fn from_base32(field_data: &[u5]) -> Result<Fallback, Bolt11ParseError> {
if field_data.is_empty() {
- return Err(ParseError::UnexpectedEndOfTaggedFields);
+ return Err(Bolt11ParseError::UnexpectedEndOfTaggedFields);
}
let version = field_data[0];
match version.to_u8() {
0..=16 => {
if bytes.len() < 2 || bytes.len() > 40 {
- return Err(ParseError::InvalidSegWitProgramLength);
+ return Err(Bolt11ParseError::InvalidSegWitProgramLength);
}
let version = WitnessVersion::try_from(version).expect("0 through 16 are valid SegWit versions");
Ok(Fallback::SegWitProgram {
17 => {
let pkh = match PubkeyHash::from_slice(&bytes) {
Ok(pkh) => pkh,
- Err(bitcoin_hashes::Error::InvalidLength(_, _)) => return Err(ParseError::InvalidPubKeyHashLength),
+ Err(bitcoin_hashes::Error::InvalidLength(_, _)) => return Err(Bolt11ParseError::InvalidPubKeyHashLength),
};
Ok(Fallback::PubKeyHash(pkh))
}
18 => {
let sh = match ScriptHash::from_slice(&bytes) {
Ok(sh) => sh,
- Err(bitcoin_hashes::Error::InvalidLength(_, _)) => return Err(ParseError::InvalidScriptHashLength),
+ Err(bitcoin_hashes::Error::InvalidLength(_, _)) => return Err(Bolt11ParseError::InvalidScriptHashLength),
};
Ok(Fallback::ScriptHash(sh))
}
- _ => Err(ParseError::Skip)
+ _ => Err(Bolt11ParseError::Skip)
}
}
}
impl FromBase32 for PrivateRoute {
- type Err = ParseError;
+ type Err = Bolt11ParseError;
- fn from_base32(field_data: &[u5]) -> Result<PrivateRoute, ParseError> {
+ fn from_base32(field_data: &[u5]) -> Result<PrivateRoute, Bolt11ParseError> {
let bytes = Vec::<u8>::from_base32(field_data)?;
if bytes.len() % 51 != 0 {
- return Err(ParseError::UnexpectedEndOfTaggedFields);
+ return Err(Bolt11ParseError::UnexpectedEndOfTaggedFields);
}
let mut route_hops = Vec::<RouteHintHop>::new();
}
}
-impl Display for ParseError {
+impl Display for Bolt11ParseError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match *self {
// TODO: find a way to combine the first three arms (e as error::Error?)
- ParseError::Bech32Error(ref e) => {
+ Bolt11ParseError::Bech32Error(ref e) => {
write!(f, "Invalid bech32: {}", e)
}
- ParseError::ParseAmountError(ref e) => {
+ Bolt11ParseError::ParseAmountError(ref e) => {
write!(f, "Invalid amount in hrp ({})", e)
}
- ParseError::MalformedSignature(ref e) => {
+ Bolt11ParseError::MalformedSignature(ref e) => {
write!(f, "Invalid secp256k1 signature: {}", e)
}
- ParseError::DescriptionDecodeError(ref e) => {
+ Bolt11ParseError::DescriptionDecodeError(ref e) => {
write!(f, "Description is not a valid utf-8 string: {}", e)
}
- ParseError::InvalidSliceLength(ref function) => {
+ Bolt11ParseError::InvalidSliceLength(ref function) => {
write!(f, "Slice in function {} had the wrong length", function)
}
- ParseError::BadPrefix => f.write_str("did not begin with 'ln'"),
- ParseError::UnknownCurrency => f.write_str("currency code unknown"),
- ParseError::UnknownSiPrefix => f.write_str("unknown SI prefix"),
- ParseError::MalformedHRP => f.write_str("malformed human readable part"),
- ParseError::TooShortDataPart => {
+ Bolt11ParseError::BadPrefix => f.write_str("did not begin with 'ln'"),
+ Bolt11ParseError::UnknownCurrency => f.write_str("currency code unknown"),
+ Bolt11ParseError::UnknownSiPrefix => f.write_str("unknown SI prefix"),
+ Bolt11ParseError::MalformedHRP => f.write_str("malformed human readable part"),
+ Bolt11ParseError::TooShortDataPart => {
f.write_str("data part too short (should be at least 111 bech32 chars long)")
},
- ParseError::UnexpectedEndOfTaggedFields => {
+ Bolt11ParseError::UnexpectedEndOfTaggedFields => {
f.write_str("tagged fields part ended unexpectedly")
},
- ParseError::PaddingError => f.write_str("some data field had bad padding"),
- ParseError::IntegerOverflowError => {
+ Bolt11ParseError::PaddingError => f.write_str("some data field had bad padding"),
+ Bolt11ParseError::IntegerOverflowError => {
f.write_str("parsed integer doesn't fit into receiving type")
},
- ParseError::InvalidSegWitProgramLength => {
+ Bolt11ParseError::InvalidSegWitProgramLength => {
f.write_str("fallback SegWit program is too long or too short")
},
- ParseError::InvalidPubKeyHashLength => {
+ Bolt11ParseError::InvalidPubKeyHashLength => {
f.write_str("fallback public key hash has a length unequal 20 bytes")
},
- ParseError::InvalidScriptHashLength => {
+ Bolt11ParseError::InvalidScriptHashLength => {
f.write_str("fallback script hash has a length unequal 32 bytes")
},
- ParseError::InvalidRecoveryId => {
+ Bolt11ParseError::InvalidRecoveryId => {
f.write_str("recovery id is out of range (should be in [0,3])")
},
- ParseError::Skip => {
+ Bolt11ParseError::Skip => {
f.write_str("the tagged field has to be skipped because of an unexpected, but allowed property")
},
}
}
#[cfg(feature = "std")]
-impl error::Error for ParseError {}
+impl error::Error for Bolt11ParseError {}
#[cfg(feature = "std")]
impl error::Error for ParseOrSemanticError {}
macro_rules! from_error {
($my_error:expr, $extern_error:ty) => {
- impl From<$extern_error> for ParseError {
+ impl From<$extern_error> for Bolt11ParseError {
fn from(e: $extern_error) -> Self {
$my_error(e)
}
}
}
-from_error!(ParseError::MalformedSignature, secp256k1::Error);
-from_error!(ParseError::ParseAmountError, ParseIntError);
-from_error!(ParseError::DescriptionDecodeError, str::Utf8Error);
+from_error!(Bolt11ParseError::MalformedSignature, secp256k1::Error);
+from_error!(Bolt11ParseError::ParseAmountError, ParseIntError);
+from_error!(Bolt11ParseError::DescriptionDecodeError, str::Utf8Error);
-impl From<bech32::Error> for ParseError {
+impl From<bech32::Error> for Bolt11ParseError {
fn from(e: bech32::Error) -> Self {
match e {
- bech32::Error::InvalidPadding => ParseError::PaddingError,
- _ => ParseError::Bech32Error(e)
+ bech32::Error::InvalidPadding => Bolt11ParseError::PaddingError,
+ _ => Bolt11ParseError::Bech32Error(e)
}
}
}
-impl From<ParseError> for ParseOrSemanticError {
- fn from(e: ParseError) -> Self {
+impl From<Bolt11ParseError> for ParseOrSemanticError {
+ fn from(e: Bolt11ParseError) -> Self {
ParseOrSemanticError::ParseError(e)
}
}
-impl From<crate::SemanticError> for ParseOrSemanticError {
- fn from(e: SemanticError) -> Self {
+impl From<crate::Bolt11SemanticError> for ParseOrSemanticError {
+ fn from(e: Bolt11SemanticError) -> Self {
ParseOrSemanticError::SemanticError(e)
}
}
#[cfg(test)]
mod test {
- use crate::de::ParseError;
+ use crate::de::Bolt11ParseError;
use secp256k1::PublicKey;
use bech32::u5;
use bitcoin_hashes::hex::FromHex;
assert_eq!("bcrt".parse::<Currency>(), Ok(Currency::Regtest));
assert_eq!("sb".parse::<Currency>(), Ok(Currency::Simnet));
assert_eq!("tbs".parse::<Currency>(), Ok(Currency::Signet));
- assert_eq!("something_else".parse::<Currency>(), Err(ParseError::UnknownCurrency))
+ assert_eq!("something_else".parse::<Currency>(), Err(Bolt11ParseError::UnknownCurrency))
}
#[test]
let input_unexpected_length = from_bech32(
"qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypyq".as_bytes()
);
- assert_eq!(Sha256::from_base32(&input_unexpected_length), Err(ParseError::Skip));
+ assert_eq!(Sha256::from_base32(&input_unexpected_length), Err(Bolt11ParseError::Skip));
}
#[test]
let input_unexpected_length = from_bech32(
"q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhvq".as_bytes()
);
- assert_eq!(PayeePubKey::from_base32(&input_unexpected_length), Err(ParseError::Skip));
+ assert_eq!(PayeePubKey::from_base32(&input_unexpected_length), Err(Bolt11ParseError::Skip));
}
#[test]
assert_eq!(ExpiryTime::from_base32(&input), expected);
let input_too_large = from_bech32("sqqqqqqqqqqqq".as_bytes());
- assert_eq!(ExpiryTime::from_base32(&input_too_large), Err(ParseError::IntegerOverflowError));
+ assert_eq!(ExpiryTime::from_base32(&input_too_large), Err(Bolt11ParseError::IntegerOverflowError));
}
#[test]
),
(
vec![u5::try_from_u8(21).unwrap(); 41],
- Err(ParseError::Skip)
+ Err(Bolt11ParseError::Skip)
),
(
vec![],
- Err(ParseError::UnexpectedEndOfTaggedFields)
+ Err(Bolt11ParseError::UnexpectedEndOfTaggedFields)
),
(
vec![u5::try_from_u8(1).unwrap(); 81],
- Err(ParseError::InvalidSegWitProgramLength)
+ Err(Bolt11ParseError::InvalidSegWitProgramLength)
),
(
vec![u5::try_from_u8(17).unwrap(); 1],
- Err(ParseError::InvalidPubKeyHashLength)
+ Err(Bolt11ParseError::InvalidPubKeyHashLength)
),
(
vec![u5::try_from_u8(18).unwrap(); 1],
- Err(ParseError::InvalidScriptHashLength)
+ Err(Bolt11ParseError::InvalidScriptHashLength)
)
];
assert_eq!(
PrivateRoute::from_base32(&[u5::try_from_u8(0).unwrap(); 40][..]),
- Err(ParseError::UnexpectedEndOfTaggedFields)
+ Err(Bolt11ParseError::UnexpectedEndOfTaggedFields)
);
}
#[test]
fn test_payment_secret_and_features_de_and_ser() {
- use lightning::ln::features::InvoiceFeatures;
+ use lightning::ln::features::Bolt11InvoiceFeatures;
use secp256k1::ecdsa::{RecoveryId, RecoverableSignature};
use crate::TaggedField::*;
- use crate::{SiPrefix, SignedRawInvoice, InvoiceSignature, RawInvoice, RawHrp, RawDataPart,
+ use crate::{SiPrefix, SignedRawBolt11Invoice, Bolt11InvoiceSignature, RawBolt11Invoice, RawHrp, RawDataPart,
Currency, Sha256, PositiveTimestamp};
// Feature bits 9, 15, and 99 are set.
- let expected_features = InvoiceFeatures::from_le_bytes(vec![0, 130, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8]);
+ let expected_features = Bolt11InvoiceFeatures::from_le_bytes(vec![0, 130, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8]);
let invoice_str = "lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdeessp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q5sqqqqqqqqqqqqqqqpqsq67gye39hfg3zd8rgc80k32tvy9xk2xunwm5lzexnvpx6fd77en8qaq424dxgt56cag2dpt359k3ssyhetktkpqh24jqnjyw6uqd08sgptq44qu";
- let invoice = SignedRawInvoice {
- raw_invoice: RawInvoice {
+ let invoice = SignedRawBolt11Invoice {
+ raw_invoice: RawBolt11Invoice {
hrp: RawHrp {
currency: Currency::Bitcoin,
raw_amount: Some(25),
hash: [0xb1, 0x96, 0x46, 0xc3, 0xbc, 0x56, 0x76, 0x1d, 0x20, 0x65, 0x6e, 0x0e, 0x32,
0xec, 0xd2, 0x69, 0x27, 0xb7, 0x62, 0x6e, 0x2a, 0x8b, 0xe6, 0x97, 0x71, 0x9f,
0xf8, 0x7e, 0x44, 0x54, 0x55, 0xb9],
- signature: InvoiceSignature(RecoverableSignature::from_compact(
+ signature: Bolt11InvoiceSignature(RecoverableSignature::from_compact(
&[0xd7, 0x90, 0x4c, 0xc4, 0xb7, 0x4a, 0x22, 0x26, 0x9c, 0x68, 0xc1, 0xdf, 0x68,
0xa9, 0x6c, 0x21, 0x4d, 0x65, 0x1b, 0x93, 0x76, 0xe9, 0xf1, 0x64, 0xd3, 0x60,
0x4d, 0xa4, 0xb7, 0xde, 0xcc, 0xce, 0x0e, 0x82, 0xaa, 0xab, 0x4c, 0x85, 0xd3,
fn test_raw_signed_invoice_deserialization() {
use crate::TaggedField::*;
use secp256k1::ecdsa::{RecoveryId, RecoverableSignature};
- use crate::{SignedRawInvoice, InvoiceSignature, RawInvoice, RawHrp, RawDataPart, Currency, Sha256,
+ use crate::{SignedRawBolt11Invoice, Bolt11InvoiceSignature, RawBolt11Invoice, RawHrp, RawDataPart, Currency, Sha256,
PositiveTimestamp};
assert_eq!(
"lnbc1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmw\
wd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaq8rkx3yf5tcsyz3d73gafnh3cax9rn449d9p5uxz9\
ezhhypd0elx87sjle52x86fux2ypatgddc6k63n7erqz25le42c4u4ecky03ylcqca784w".parse(),
- Ok(SignedRawInvoice {
- raw_invoice: RawInvoice {
+ Ok(SignedRawBolt11Invoice {
+ raw_invoice: RawBolt11Invoice {
hrp: RawHrp {
currency: Currency::Bitcoin,
raw_amount: None,
0x7b, 0x1d, 0x85, 0x8d, 0xb1, 0xd1, 0xf7, 0xab, 0x71, 0x37, 0xdc, 0xb7,
0x83, 0x5d, 0xb2, 0xec, 0xd5, 0x18, 0xe1, 0xc9
],
- signature: InvoiceSignature(RecoverableSignature::from_compact(
+ signature: Bolt11InvoiceSignature(RecoverableSignature::from_compact(
& [
0x38u8, 0xec, 0x68, 0x91, 0x34, 0x5e, 0x20, 0x41, 0x45, 0xbe, 0x8a,
0x3a, 0x99, 0xde, 0x38, 0xe9, 0x8a, 0x39, 0xd6, 0xa5, 0x69, 0x43,
//! invoices and functions to create, encode and decode these. If you just want to use the standard
//! en-/decoding functionality this should get you started:
//!
-//! * For parsing use `str::parse::<Invoice>(&self)` (see [`Invoice::from_str`])
+//! * For parsing use `str::parse::<Bolt11Invoice>(&self)` (see [`Bolt11Invoice::from_str`])
//! * For constructing invoices use the [`InvoiceBuilder`]
//! * For serializing invoices use the [`Display`]/[`ToString`] traits
//!
-//! [`Invoice::from_str`]: crate::Invoice#impl-FromStr
+//! [`Bolt11Invoice::from_str`]: crate::Bolt11Invoice#impl-FromStr
#[cfg(not(any(feature = "std", feature = "no-std")))]
compile_error!("at least one of the `std` or `no-std` features must be enabled");
use bitcoin::{Address, Network, PubkeyHash, ScriptHash};
use bitcoin::util::address::{Payload, WitnessVersion};
use bitcoin_hashes::{Hash, sha256};
-use lightning::ln::PaymentSecret;
-use lightning::ln::features::InvoiceFeatures;
-#[cfg(any(doc, test))]
-use lightning::routing::gossip::RoutingFees;
-use lightning::routing::router::RouteHint;
+use lightning::ln::features::Bolt11InvoiceFeatures;
use lightning::util::invoice::construct_invoice_preimage;
use secp256k1::PublicKey;
#[cfg(feature = "serde")]
use serde::{Deserialize, Deserializer,Serialize, Serializer, de::Error};
+#[doc(no_inline)]
+pub use lightning::ln::PaymentSecret;
+#[doc(no_inline)]
+pub use lightning::routing::router::{RouteHint, RouteHintHop};
+#[doc(no_inline)]
+pub use lightning::routing::gossip::RoutingFees;
+
mod de;
mod ser;
mod tb;
/// reasons, but should generally result in an "invalid BOLT11 invoice" message for the user.
#[allow(missing_docs)]
#[derive(PartialEq, Eq, Debug, Clone)]
-pub enum ParseError {
+pub enum Bolt11ParseError {
Bech32Error(bech32::Error),
ParseAmountError(ParseIntError),
MalformedSignature(secp256k1::Error),
#[derive(PartialEq, Eq, Debug, Clone)]
pub enum ParseOrSemanticError {
/// The invoice couldn't be decoded
- ParseError(ParseError),
+ ParseError(Bolt11ParseError),
/// The invoice could be decoded but violates the BOLT11 standard
- SemanticError(crate::SemanticError),
+ SemanticError(crate::Bolt11SemanticError),
}
/// The number of bits used to represent timestamps as defined in BOLT 11.
/// [`MIN_FINAL_CLTV_EXPIRY_DELTA`]: lightning::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY_DELTA
pub const DEFAULT_MIN_FINAL_CLTV_EXPIRY_DELTA: u64 = 18;
-/// Builder for [`Invoice`]s. It's the most convenient and advised way to use this library. It ensures
-/// that only a semantically and syntactically correct Invoice can be built using it.
+/// Builder for [`Bolt11Invoice`]s. It's the most convenient and advised way to use this library. It
+/// ensures that only a semantically and syntactically correct invoice can be built using it.
///
/// ```
/// extern crate secp256k1;
/// Represents a syntactically and semantically correct lightning BOLT11 invoice.
///
-/// There are three ways to construct an `Invoice`:
+/// There are three ways to construct a `Bolt11Invoice`:
/// 1. using [`InvoiceBuilder`]
-/// 2. using [`Invoice::from_signed`]
-/// 3. using `str::parse::<Invoice>(&str)` (see [`Invoice::from_str`])
+/// 2. using [`Bolt11Invoice::from_signed`]
+/// 3. using `str::parse::<Bolt11Invoice>(&str)` (see [`Bolt11Invoice::from_str`])
///
-/// [`Invoice::from_str`]: crate::Invoice#impl-FromStr
+/// [`Bolt11Invoice::from_str`]: crate::Bolt11Invoice#impl-FromStr
#[derive(Eq, PartialEq, Debug, Clone, Hash, Ord, PartialOrd)]
-pub struct Invoice {
- signed_invoice: SignedRawInvoice,
+pub struct Bolt11Invoice {
+ signed_invoice: SignedRawBolt11Invoice,
}
/// Represents the description of an invoice which has to be either a directly included string or
/// This is not exported to bindings users as we don't have a good way to map the reference lifetimes making this
/// practically impossible to use safely in languages like C.
#[derive(Eq, PartialEq, Debug, Clone, Ord, PartialOrd)]
-pub enum InvoiceDescription<'f> {
+pub enum Bolt11InvoiceDescription<'f> {
/// Reference to the directly supplied description in the invoice
Direct(&'f Description),
Hash(&'f Sha256),
}
-/// Represents a signed [`RawInvoice`] with cached hash. The signature is not checked and may be
+/// Represents a signed [`RawBolt11Invoice`] with cached hash. The signature is not checked and may be
/// invalid.
///
/// # Invariants
-/// The hash has to be either from the deserialized invoice or from the serialized [`RawInvoice`].
+/// The hash has to be either from the deserialized invoice or from the serialized [`RawBolt11Invoice`].
#[derive(Eq, PartialEq, Debug, Clone, Hash, Ord, PartialOrd)]
-pub struct SignedRawInvoice {
- /// The rawInvoice that the signature belongs to
- raw_invoice: RawInvoice,
+pub struct SignedRawBolt11Invoice {
+ /// The raw invoice that the signature belongs to
+ raw_invoice: RawBolt11Invoice,
- /// Hash of the [`RawInvoice`] that will be used to check the signature.
+ /// Hash of the [`RawBolt11Invoice`] that will be used to check the signature.
///
- /// * if the `SignedRawInvoice` was deserialized the hash is of from the original encoded form,
+ /// * if the `SignedRawBolt11Invoice` was deserialized the hash is of from the original encoded form,
/// since it's not guaranteed that encoding it again will lead to the same result since integers
/// could have been encoded with leading zeroes etc.
- /// * if the `SignedRawInvoice` was constructed manually the hash will be the calculated hash
- /// from the [`RawInvoice`]
+ /// * if the `SignedRawBolt11Invoice` was constructed manually the hash will be the calculated hash
+ /// from the [`RawBolt11Invoice`]
hash: [u8; 32],
/// signature of the payment request
- signature: InvoiceSignature,
+ signature: Bolt11InvoiceSignature,
}
-/// Represents an syntactically correct [`Invoice`] for a payment on the lightning network,
+/// Represents an syntactically correct [`Bolt11Invoice`] for a payment on the lightning network,
/// but without the signature information.
/// Decoding and encoding should not lead to information loss but may lead to different hashes.
///
-/// For methods without docs see the corresponding methods in [`Invoice`].
+/// For methods without docs see the corresponding methods in [`Bolt11Invoice`].
#[derive(Eq, PartialEq, Debug, Clone, Hash, Ord, PartialOrd)]
-pub struct RawInvoice {
+pub struct RawBolt11Invoice {
/// human readable part
pub hrp: RawHrp,
pub data: RawDataPart,
}
-/// Data of the [`RawInvoice`] that is encoded in the human readable part.
+/// Data of the [`RawBolt11Invoice`] that is encoded in the human readable part.
///
/// This is not exported to bindings users as we don't yet support `Option<Enum>`
#[derive(Eq, PartialEq, Debug, Clone, Hash, Ord, PartialOrd)]
pub si_prefix: Option<SiPrefix>,
}
-/// Data of the [`RawInvoice`] that is encoded in the data part
+/// Data of the [`RawBolt11Invoice`] that is encoded in the data part
#[derive(Eq, PartialEq, Debug, Clone, Hash, Ord, PartialOrd)]
pub struct RawDataPart {
/// generation time of the invoice
PrivateRoute(PrivateRoute),
PaymentSecret(PaymentSecret),
PaymentMetadata(Vec<u8>),
- Features(InvoiceFeatures),
+ Features(Bolt11InvoiceFeatures),
}
/// SHA-256 hash
/// Recoverable signature
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
-pub struct InvoiceSignature(pub RecoverableSignature);
+pub struct Bolt11InvoiceSignature(pub RecoverableSignature);
-impl PartialOrd for InvoiceSignature {
+impl PartialOrd for Bolt11InvoiceSignature {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.0.serialize_compact().1.partial_cmp(&other.0.serialize_compact().1)
}
}
-impl Ord for InvoiceSignature {
+impl Ord for Bolt11InvoiceSignature {
fn cmp(&self, other: &Self) -> Ordering {
self.0.serialize_compact().1.cmp(&other.0.serialize_compact().1)
}
}
impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool, M: tb::Bool> InvoiceBuilder<D, H, tb::True, C, S, M> {
- /// Builds a [`RawInvoice`] if no [`CreationError`] occurred while construction any of the
+ /// Builds a [`RawBolt11Invoice`] if no [`CreationError`] occurred while construction any of the
/// fields.
- pub fn build_raw(self) -> Result<RawInvoice, CreationError> {
+ pub fn build_raw(self) -> Result<RawBolt11Invoice, CreationError> {
// If an error occurred at any time before, return it now
if let Some(e) = self.error {
tagged_fields,
};
- Ok(RawInvoice {
+ Ok(RawBolt11Invoice {
hrp,
data,
})
}
/// Set the description or description hash. This function is only available if no description (hash) was set.
- pub fn invoice_description(self, description: InvoiceDescription) -> InvoiceBuilder<tb::True, H, T, C, S, M> {
+ pub fn invoice_description(self, description: Bolt11InvoiceDescription) -> InvoiceBuilder<tb::True, H, T, C, S, M> {
match description {
- InvoiceDescription::Direct(desc) => {
+ Bolt11InvoiceDescription::Direct(desc) => {
self.description(desc.clone().into_inner())
}
- InvoiceDescription::Hash(hash) => {
+ Bolt11InvoiceDescription::Hash(hash) => {
self.description_hash(hash.0)
}
}
}
self.tagged_fields.push(TaggedField::PaymentSecret(payment_secret));
if !found_features {
- let mut features = InvoiceFeatures::empty();
+ let mut features = Bolt11InvoiceFeatures::empty();
features.set_variable_length_onion_required();
features.set_payment_secret_required();
self.tagged_fields.push(TaggedField::Features(features));
}
}
if !found_features {
- let mut features = InvoiceFeatures::empty();
+ let mut features = Bolt11InvoiceFeatures::empty();
features.set_payment_metadata_optional();
self.tagged_fields.push(TaggedField::Features(features));
}
/// Builds and signs an invoice using the supplied `sign_function`. This function MAY NOT fail
/// and MUST produce a recoverable signature valid for the given hash and if applicable also for
/// the included payee public key.
- pub fn build_signed<F>(self, sign_function: F) -> Result<Invoice, CreationError>
+ pub fn build_signed<F>(self, sign_function: F) -> Result<Bolt11Invoice, CreationError>
where F: FnOnce(&Message) -> RecoverableSignature
{
let invoice = self.try_build_signed::<_, ()>(|hash| {
/// Builds and signs an invoice using the supplied `sign_function`. This function MAY fail with
/// an error of type `E` and MUST produce a recoverable signature valid for the given hash and
/// if applicable also for the included payee public key.
- pub fn try_build_signed<F, E>(self, sign_function: F) -> Result<Invoice, SignOrCreationError<E>>
+ pub fn try_build_signed<F, E>(self, sign_function: F) -> Result<Bolt11Invoice, SignOrCreationError<E>>
where F: FnOnce(&Message) -> Result<RecoverableSignature, E>
{
let raw = match self.build_raw() {
Err(e) => return Err(SignOrCreationError::SignError(e)),
};
- let invoice = Invoice {
+ let invoice = Bolt11Invoice {
signed_invoice: signed,
};
}
-impl SignedRawInvoice {
- /// Disassembles the `SignedRawInvoice` into its three parts:
+impl SignedRawBolt11Invoice {
+ /// Disassembles the `SignedRawBolt11Invoice` into its three parts:
/// 1. raw invoice
/// 2. hash of the raw invoice
/// 3. signature
- pub fn into_parts(self) -> (RawInvoice, [u8; 32], InvoiceSignature) {
+ pub fn into_parts(self) -> (RawBolt11Invoice, [u8; 32], Bolt11InvoiceSignature) {
(self.raw_invoice, self.hash, self.signature)
}
- /// The [`RawInvoice`] which was signed.
- pub fn raw_invoice(&self) -> &RawInvoice {
+ /// The [`RawBolt11Invoice`] which was signed.
+ pub fn raw_invoice(&self) -> &RawBolt11Invoice {
&self.raw_invoice
}
- /// The hash of the [`RawInvoice`] that was signed.
+ /// The hash of the [`RawBolt11Invoice`] that was signed.
pub fn signable_hash(&self) -> &[u8; 32] {
&self.hash
}
/// Signature for the invoice.
- pub fn signature(&self) -> &InvoiceSignature {
+ pub fn signature(&self) -> &Bolt11InvoiceSignature {
&self.signature
}
}
#[allow(missing_docs)]
-impl RawInvoice {
+impl RawBolt11Invoice {
/// Hash the HRP as bytes and signatureless data part.
fn hash_from_parts(hrp_bytes: &[u8], data_without_signature: &[u5]) -> [u8; 32] {
let preimage = construct_invoice_preimage(hrp_bytes, data_without_signature);
hash
}
- /// Calculate the hash of the encoded `RawInvoice` which should be signed.
+ /// Calculate the hash of the encoded `RawBolt11Invoice` which should be signed.
pub fn signable_hash(&self) -> [u8; 32] {
use bech32::ToBase32;
- RawInvoice::hash_from_parts(
+ RawBolt11Invoice::hash_from_parts(
self.hrp.to_string().as_bytes(),
&self.data.to_base32()
)
}
/// Signs the invoice using the supplied `sign_method`. This function MAY fail with an error of
- /// type `E`. Since the signature of a [`SignedRawInvoice`] is not required to be valid there
+ /// type `E`. Since the signature of a [`SignedRawBolt11Invoice`] is not required to be valid there
/// are no constraints regarding the validity of the produced signature.
///
/// This is not exported to bindings users as we don't currently support passing function pointers into methods
/// explicitly.
- pub fn sign<F, E>(self, sign_method: F) -> Result<SignedRawInvoice, E>
+ pub fn sign<F, E>(self, sign_method: F) -> Result<SignedRawBolt11Invoice, E>
where F: FnOnce(&Message) -> Result<RecoverableSignature, E>
{
let raw_hash = self.signable_hash();
.expect("Hash is 32 bytes long, same as MESSAGE_SIZE");
let signature = sign_method(&hash)?;
- Ok(SignedRawInvoice {
+ Ok(SignedRawBolt11Invoice {
raw_invoice: self,
hash: raw_hash,
- signature: InvoiceSignature(signature),
+ signature: Bolt11InvoiceSignature(signature),
})
}
find_extract!(self.known_tagged_fields(), TaggedField::PaymentMetadata(ref x), x)
}
- pub fn features(&self) -> Option<&InvoiceFeatures> {
+ pub fn features(&self) -> Option<&Bolt11InvoiceFeatures> {
find_extract!(self.known_tagged_fields(), TaggedField::Features(ref x), x)
}
}
}
-impl Invoice {
- /// The hash of the [`RawInvoice`] that was signed.
+impl Bolt11Invoice {
+ /// The hash of the [`RawBolt11Invoice`] that was signed.
pub fn signable_hash(&self) -> [u8; 32] {
self.signed_invoice.hash
}
- /// Transform the `Invoice` into it's unchecked version
- pub fn into_signed_raw(self) -> SignedRawInvoice {
+ /// Transform the `Bolt11Invoice` into its unchecked version.
+ pub fn into_signed_raw(self) -> SignedRawBolt11Invoice {
self.signed_invoice
}
/// Check that all mandatory fields are present
- fn check_field_counts(&self) -> Result<(), SemanticError> {
+ fn check_field_counts(&self) -> Result<(), Bolt11SemanticError> {
// "A writer MUST include exactly one p field […]."
let payment_hash_cnt = self.tagged_fields().filter(|&tf| match *tf {
TaggedField::PaymentHash(_) => true,
_ => false,
}).count();
if payment_hash_cnt < 1 {
- return Err(SemanticError::NoPaymentHash);
+ return Err(Bolt11SemanticError::NoPaymentHash);
} else if payment_hash_cnt > 1 {
- return Err(SemanticError::MultiplePaymentHashes);
+ return Err(Bolt11SemanticError::MultiplePaymentHashes);
}
// "A writer MUST include either exactly one d or exactly one h field."
_ => false,
}).count();
if description_cnt < 1 {
- return Err(SemanticError::NoDescription);
+ return Err(Bolt11SemanticError::NoDescription);
} else if description_cnt > 1 {
- return Err(SemanticError::MultipleDescriptions);
+ return Err(Bolt11SemanticError::MultipleDescriptions);
}
self.check_payment_secret()?;
}
/// Checks that there is exactly one payment secret field
- fn check_payment_secret(&self) -> Result<(), SemanticError> {
+ fn check_payment_secret(&self) -> Result<(), Bolt11SemanticError> {
// "A writer MUST include exactly one `s` field."
let payment_secret_count = self.tagged_fields().filter(|&tf| match *tf {
TaggedField::PaymentSecret(_) => true,
_ => false,
}).count();
if payment_secret_count < 1 {
- return Err(SemanticError::NoPaymentSecret);
+ return Err(Bolt11SemanticError::NoPaymentSecret);
} else if payment_secret_count > 1 {
- return Err(SemanticError::MultiplePaymentSecrets);
+ return Err(Bolt11SemanticError::MultiplePaymentSecrets);
}
Ok(())
}
/// Check that amount is a whole number of millisatoshis
- fn check_amount(&self) -> Result<(), SemanticError> {
+ fn check_amount(&self) -> Result<(), Bolt11SemanticError> {
if let Some(amount_pico_btc) = self.amount_pico_btc() {
if amount_pico_btc % 10 != 0 {
- return Err(SemanticError::ImpreciseAmount);
+ return Err(Bolt11SemanticError::ImpreciseAmount);
}
}
Ok(())
}
/// Check that feature bits are set as required
- fn check_feature_bits(&self) -> Result<(), SemanticError> {
+ fn check_feature_bits(&self) -> Result<(), Bolt11SemanticError> {
self.check_payment_secret()?;
// "A writer MUST set an s field if and only if the payment_secret feature is set."
_ => false,
});
match features {
- None => Err(SemanticError::InvalidFeatures),
+ None => Err(Bolt11SemanticError::InvalidFeatures),
Some(TaggedField::Features(features)) => {
if features.requires_unknown_bits() {
- Err(SemanticError::InvalidFeatures)
+ Err(Bolt11SemanticError::InvalidFeatures)
} else if !features.supports_payment_secret() {
- Err(SemanticError::InvalidFeatures)
+ Err(Bolt11SemanticError::InvalidFeatures)
} else {
Ok(())
}
}
/// Check that the invoice is signed correctly and that key recovery works
- pub fn check_signature(&self) -> Result<(), SemanticError> {
+ pub fn check_signature(&self) -> Result<(), Bolt11SemanticError> {
match self.signed_invoice.recover_payee_pub_key() {
Err(secp256k1::Error::InvalidRecoveryId) =>
- return Err(SemanticError::InvalidRecoveryId),
+ return Err(Bolt11SemanticError::InvalidRecoveryId),
Err(secp256k1::Error::InvalidSignature) =>
- return Err(SemanticError::InvalidSignature),
+ return Err(Bolt11SemanticError::InvalidSignature),
Err(e) => panic!("no other error may occur, got {:?}", e),
Ok(_) => {},
}
if !self.signed_invoice.check_signature() {
- return Err(SemanticError::InvalidSignature);
+ return Err(Bolt11SemanticError::InvalidSignature);
}
Ok(())
}
- /// Constructs an `Invoice` from a [`SignedRawInvoice`] by checking all its invariants.
+ /// Constructs a `Bolt11Invoice` from a [`SignedRawBolt11Invoice`] by checking all its invariants.
/// ```
/// use lightning_invoice::*;
///
/// 8s0gyuxjjgux34w75dnc6xp2l35j7es3jd4ugt3lu0xzre26yg5m7ke54n2d5sym4xcmxtl8238xxvw5h5h5\
/// j5r6drg6k6zcqj0fcwg";
///
- /// let signed = invoice.parse::<SignedRawInvoice>().unwrap();
+ /// let signed = invoice.parse::<SignedRawBolt11Invoice>().unwrap();
///
- /// assert!(Invoice::from_signed(signed).is_ok());
+ /// assert!(Bolt11Invoice::from_signed(signed).is_ok());
/// ```
- pub fn from_signed(signed_invoice: SignedRawInvoice) -> Result<Self, SemanticError> {
- let invoice = Invoice {
+ pub fn from_signed(signed_invoice: SignedRawBolt11Invoice) -> Result<Self, Bolt11SemanticError> {
+ let invoice = Bolt11Invoice {
signed_invoice,
};
invoice.check_field_counts()?;
Ok(invoice)
}
- /// Returns the `Invoice`'s timestamp (should equal its creation time)
+ /// Returns the `Bolt11Invoice`'s timestamp (should equal its creation time)
#[cfg(feature = "std")]
pub fn timestamp(&self) -> SystemTime {
self.signed_invoice.raw_invoice().data.timestamp.as_time()
}
- /// Returns the `Invoice`'s timestamp as a duration since the Unix epoch
+ /// Returns the `Bolt11Invoice`'s timestamp as a duration since the Unix epoch
pub fn duration_since_epoch(&self) -> Duration {
self.signed_invoice.raw_invoice().data.timestamp.0
}
- /// Returns an iterator over all tagged fields of this Invoice.
+ /// Returns an iterator over all tagged fields of this `Bolt11Invoice`.
///
/// This is not exported to bindings users as there is not yet a manual mapping for a FilterMap
pub fn tagged_fields(&self)
/// Return the description or a hash of it for longer ones
///
- /// This is not exported to bindings users because we don't yet export InvoiceDescription
- pub fn description(&self) -> InvoiceDescription {
+ /// This is not exported to bindings users because we don't yet export Bolt11InvoiceDescription
+ pub fn description(&self) -> Bolt11InvoiceDescription {
if let Some(direct) = self.signed_invoice.description() {
- return InvoiceDescription::Direct(direct);
+ return Bolt11InvoiceDescription::Direct(direct);
} else if let Some(hash) = self.signed_invoice.description_hash() {
- return InvoiceDescription::Hash(hash);
+ return Bolt11InvoiceDescription::Hash(hash);
}
unreachable!("ensured by constructor");
}
}
/// Get the invoice features if they were included in the invoice
- pub fn features(&self) -> Option<&InvoiceFeatures> {
+ pub fn features(&self) -> Option<&Bolt11InvoiceFeatures> {
self.signed_invoice.features()
}
}
}
-impl Deref for InvoiceSignature {
+impl Deref for Bolt11InvoiceSignature {
type Target = RecoverableSignature;
fn deref(&self) -> &RecoverableSignature {
}
}
-impl Deref for SignedRawInvoice {
- type Target = RawInvoice;
+impl Deref for SignedRawBolt11Invoice {
+ type Target = RawBolt11Invoice;
- fn deref(&self) -> &RawInvoice {
+ fn deref(&self) -> &RawBolt11Invoice {
&self.raw_invoice
}
}
-/// Errors that may occur when constructing a new [`RawInvoice`] or [`Invoice`]
+/// Errors that may occur when constructing a new [`RawBolt11Invoice`] or [`Bolt11Invoice`]
#[derive(Eq, PartialEq, Debug, Clone)]
pub enum CreationError {
/// The supplied description string was longer than 639 __bytes__ (see [`Description::new`])
#[cfg(feature = "std")]
impl std::error::Error for CreationError { }
-/// Errors that may occur when converting a [`RawInvoice`] to an [`Invoice`]. They relate to the
-/// requirements sections in BOLT #11
+/// Errors that may occur when converting a [`RawBolt11Invoice`] to a [`Bolt11Invoice`]. They relate to
+/// the requirements sections in BOLT #11
#[derive(Eq, PartialEq, Debug, Clone)]
-pub enum SemanticError {
+pub enum Bolt11SemanticError {
/// The invoice is missing the mandatory payment hash
NoPaymentHash,
ImpreciseAmount,
}
-impl Display for SemanticError {
+impl Display for Bolt11SemanticError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
- SemanticError::NoPaymentHash => f.write_str("The invoice is missing the mandatory payment hash"),
- SemanticError::MultiplePaymentHashes => f.write_str("The invoice has multiple payment hashes which isn't allowed"),
- SemanticError::NoDescription => f.write_str("No description or description hash are part of the invoice"),
- SemanticError::MultipleDescriptions => f.write_str("The invoice contains multiple descriptions and/or description hashes which isn't allowed"),
- SemanticError::NoPaymentSecret => f.write_str("The invoice is missing the mandatory payment secret"),
- SemanticError::MultiplePaymentSecrets => f.write_str("The invoice contains multiple payment secrets"),
- SemanticError::InvalidFeatures => f.write_str("The invoice's features are invalid"),
- SemanticError::InvalidRecoveryId => f.write_str("The recovery id doesn't fit the signature/pub key"),
- SemanticError::InvalidSignature => f.write_str("The invoice's signature is invalid"),
- SemanticError::ImpreciseAmount => f.write_str("The invoice's amount was not a whole number of millisatoshis"),
+ Bolt11SemanticError::NoPaymentHash => f.write_str("The invoice is missing the mandatory payment hash"),
+ Bolt11SemanticError::MultiplePaymentHashes => f.write_str("The invoice has multiple payment hashes which isn't allowed"),
+ Bolt11SemanticError::NoDescription => f.write_str("No description or description hash are part of the invoice"),
+ Bolt11SemanticError::MultipleDescriptions => f.write_str("The invoice contains multiple descriptions and/or description hashes which isn't allowed"),
+ Bolt11SemanticError::NoPaymentSecret => f.write_str("The invoice is missing the mandatory payment secret"),
+ Bolt11SemanticError::MultiplePaymentSecrets => f.write_str("The invoice contains multiple payment secrets"),
+ Bolt11SemanticError::InvalidFeatures => f.write_str("The invoice's features are invalid"),
+ Bolt11SemanticError::InvalidRecoveryId => f.write_str("The recovery id doesn't fit the signature/pub key"),
+ Bolt11SemanticError::InvalidSignature => f.write_str("The invoice's signature is invalid"),
+ Bolt11SemanticError::ImpreciseAmount => f.write_str("The invoice's amount was not a whole number of millisatoshis"),
}
}
}
#[cfg(feature = "std")]
-impl std::error::Error for SemanticError { }
+impl std::error::Error for Bolt11SemanticError { }
/// When signing using a fallible method either an user-supplied `SignError` or a [`CreationError`]
/// may occur.
}
#[cfg(feature = "serde")]
-impl Serialize for Invoice {
+impl Serialize for Bolt11Invoice {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer {
serializer.serialize_str(self.to_string().as_str())
}
}
#[cfg(feature = "serde")]
-impl<'de> Deserialize<'de> for Invoice {
- fn deserialize<D>(deserializer: D) -> Result<Invoice, D::Error> where D: Deserializer<'de> {
+impl<'de> Deserialize<'de> for Bolt11Invoice {
+ fn deserialize<D>(deserializer: D) -> Result<Bolt11Invoice, D::Error> where D: Deserializer<'de> {
let bolt11 = String::deserialize(deserializer)?
- .parse::<Invoice>()
+ .parse::<Bolt11Invoice>()
.map_err(|e| D::Error::custom(format_args!("{:?}", e)))?;
Ok(bolt11)
#[test]
fn test_calc_invoice_hash() {
- use crate::{RawInvoice, RawHrp, RawDataPart, Currency, PositiveTimestamp};
+ use crate::{RawBolt11Invoice, RawHrp, RawDataPart, Currency, PositiveTimestamp};
use crate::TaggedField::*;
- let invoice = RawInvoice {
+ let invoice = RawBolt11Invoice {
hrp: RawHrp {
currency: Currency::Bitcoin,
raw_amount: None,
use secp256k1::Secp256k1;
use secp256k1::ecdsa::{RecoveryId, RecoverableSignature};
use secp256k1::{SecretKey, PublicKey};
- use crate::{SignedRawInvoice, InvoiceSignature, RawInvoice, RawHrp, RawDataPart, Currency, Sha256,
+ use crate::{SignedRawBolt11Invoice, Bolt11InvoiceSignature, RawBolt11Invoice, RawHrp, RawDataPart, Currency, Sha256,
PositiveTimestamp};
- let invoice = SignedRawInvoice {
- raw_invoice: RawInvoice {
+ let invoice = SignedRawBolt11Invoice {
+ raw_invoice: RawBolt11Invoice {
hrp: RawHrp {
currency: Currency::Bitcoin,
raw_amount: None,
0x7b, 0x1d, 0x85, 0x8d, 0xb1, 0xd1, 0xf7, 0xab, 0x71, 0x37, 0xdc, 0xb7,
0x83, 0x5d, 0xb2, 0xec, 0xd5, 0x18, 0xe1, 0xc9
],
- signature: InvoiceSignature(RecoverableSignature::from_compact(
+ signature: Bolt11InvoiceSignature(RecoverableSignature::from_compact(
& [
0x38u8, 0xec, 0x68, 0x91, 0x34, 0x5e, 0x20, 0x41, 0x45, 0xbe, 0x8a,
0x3a, 0x99, 0xde, 0x38, 0xe9, 0x8a, 0x39, 0xd6, 0xa5, 0x69, 0x43,
#[test]
fn test_check_feature_bits() {
use crate::TaggedField::*;
- use lightning::ln::features::InvoiceFeatures;
+ use lightning::ln::features::Bolt11InvoiceFeatures;
use secp256k1::Secp256k1;
use secp256k1::SecretKey;
- use crate::{RawInvoice, RawHrp, RawDataPart, Currency, Sha256, PositiveTimestamp, Invoice,
- SemanticError};
+ use crate::{Bolt11Invoice, RawBolt11Invoice, RawHrp, RawDataPart, Currency, Sha256, PositiveTimestamp,
+ Bolt11SemanticError};
let private_key = SecretKey::from_slice(&[42; 32]).unwrap();
let payment_secret = lightning::ln::PaymentSecret([21; 32]);
- let invoice_template = RawInvoice {
+ let invoice_template = RawBolt11Invoice {
hrp: RawHrp {
currency: Currency::Bitcoin,
raw_amount: None,
invoice.data.tagged_fields.push(PaymentSecret(payment_secret).into());
invoice.sign::<_, ()>(|hash| Ok(Secp256k1::new().sign_ecdsa_recoverable(hash, &private_key)))
}.unwrap();
- assert_eq!(Invoice::from_signed(invoice), Err(SemanticError::InvalidFeatures));
+ assert_eq!(Bolt11Invoice::from_signed(invoice), Err(Bolt11SemanticError::InvalidFeatures));
// Missing feature bits
let invoice = {
let mut invoice = invoice_template.clone();
invoice.data.tagged_fields.push(PaymentSecret(payment_secret).into());
- invoice.data.tagged_fields.push(Features(InvoiceFeatures::empty()).into());
+ invoice.data.tagged_fields.push(Features(Bolt11InvoiceFeatures::empty()).into());
invoice.sign::<_, ()>(|hash| Ok(Secp256k1::new().sign_ecdsa_recoverable(hash, &private_key)))
}.unwrap();
- assert_eq!(Invoice::from_signed(invoice), Err(SemanticError::InvalidFeatures));
+ assert_eq!(Bolt11Invoice::from_signed(invoice), Err(Bolt11SemanticError::InvalidFeatures));
- let mut payment_secret_features = InvoiceFeatures::empty();
+ let mut payment_secret_features = Bolt11InvoiceFeatures::empty();
payment_secret_features.set_payment_secret_required();
// Including payment secret and feature bits
invoice.data.tagged_fields.push(Features(payment_secret_features.clone()).into());
invoice.sign::<_, ()>(|hash| Ok(Secp256k1::new().sign_ecdsa_recoverable(hash, &private_key)))
}.unwrap();
- assert!(Invoice::from_signed(invoice).is_ok());
+ assert!(Bolt11Invoice::from_signed(invoice).is_ok());
// No payment secret or features
let invoice = {
let invoice = invoice_template.clone();
invoice.sign::<_, ()>(|hash| Ok(Secp256k1::new().sign_ecdsa_recoverable(hash, &private_key)))
}.unwrap();
- assert_eq!(Invoice::from_signed(invoice), Err(SemanticError::NoPaymentSecret));
+ assert_eq!(Bolt11Invoice::from_signed(invoice), Err(Bolt11SemanticError::NoPaymentSecret));
// No payment secret or feature bits
let invoice = {
let mut invoice = invoice_template.clone();
- invoice.data.tagged_fields.push(Features(InvoiceFeatures::empty()).into());
+ invoice.data.tagged_fields.push(Features(Bolt11InvoiceFeatures::empty()).into());
invoice.sign::<_, ()>(|hash| Ok(Secp256k1::new().sign_ecdsa_recoverable(hash, &private_key)))
}.unwrap();
- assert_eq!(Invoice::from_signed(invoice), Err(SemanticError::NoPaymentSecret));
+ assert_eq!(Bolt11Invoice::from_signed(invoice), Err(Bolt11SemanticError::NoPaymentSecret));
// Missing payment secret
let invoice = {
invoice.data.tagged_fields.push(Features(payment_secret_features).into());
invoice.sign::<_, ()>(|hash| Ok(Secp256k1::new().sign_ecdsa_recoverable(hash, &private_key)))
}.unwrap();
- assert_eq!(Invoice::from_signed(invoice), Err(SemanticError::NoPaymentSecret));
+ assert_eq!(Bolt11Invoice::from_signed(invoice), Err(Bolt11SemanticError::NoPaymentSecret));
// Multiple payment secrets
let invoice = {
invoice.data.tagged_fields.push(PaymentSecret(payment_secret).into());
invoice.sign::<_, ()>(|hash| Ok(Secp256k1::new().sign_ecdsa_recoverable(hash, &private_key)))
}.unwrap();
- assert_eq!(Invoice::from_signed(invoice), Err(SemanticError::MultiplePaymentSecrets));
+ assert_eq!(Bolt11Invoice::from_signed(invoice), Err(Bolt11SemanticError::MultiplePaymentSecrets));
}
#[test]
assert_eq!(invoice.private_routes(), vec![&PrivateRoute(route_1), &PrivateRoute(route_2)]);
assert_eq!(
invoice.description(),
- InvoiceDescription::Hash(&Sha256(sha256::Hash::from_slice(&[3;32][..]).unwrap()))
+ Bolt11InvoiceDescription::Hash(&Sha256(sha256::Hash::from_slice(&[3;32][..]).unwrap()))
);
assert_eq!(invoice.payment_hash(), &sha256::Hash::from_slice(&[21;32][..]).unwrap());
assert_eq!(invoice.payment_secret(), &PaymentSecret([42; 32]));
- let mut expected_features = InvoiceFeatures::empty();
+ let mut expected_features = Bolt11InvoiceFeatures::empty();
expected_features.set_variable_length_onion_required();
expected_features.set_payment_secret_required();
expected_features.set_basic_mpp_optional();
Ok(secp_ctx.sign_ecdsa_recoverable(hash, &privkey))
})
.unwrap();
- let invoice = Invoice::from_signed(signed_invoice).unwrap();
+ let invoice = Bolt11Invoice::from_signed(signed_invoice).unwrap();
assert_eq!(invoice.min_final_cltv_expiry_delta(), DEFAULT_MIN_FINAL_CLTV_EXPIRY_DELTA);
assert_eq!(invoice.expiry_time(), Duration::from_secs(DEFAULT_EXPIRY_TIME));
Ok(secp_ctx.sign_ecdsa_recoverable(hash, &privkey))
})
.unwrap();
- let invoice = Invoice::from_signed(signed_invoice).unwrap();
+ let invoice = Bolt11Invoice::from_signed(signed_invoice).unwrap();
assert!(invoice.would_expire(Duration::from_secs(1234567 + DEFAULT_EXPIRY_TIME + 1)));
}
p62g49p7569ev48cmulecsxe59lvaw3wlxm7r982zxa9zzj7z5l0cqqxusqqyqqqqlgqqqqqzsqygarl9fh3\
8s0gyuxjjgux34w75dnc6xp2l35j7es3jd4ugt3lu0xzre26yg5m7ke54n2d5sym4xcmxtl8238xxvw5h5h5\
j5r6drg6k6zcqj0fcwg";
- let invoice = invoice_str.parse::<super::Invoice>().unwrap();
+ let invoice = invoice_str.parse::<super::Bolt11Invoice>().unwrap();
let serialized_invoice = serde_json::to_string(&invoice).unwrap();
- let deserialized_invoice: super::Invoice = serde_json::from_str(serialized_invoice.as_str()).unwrap();
+ let deserialized_invoice: super::Bolt11Invoice = serde_json::from_str(serialized_invoice.as_str()).unwrap();
assert_eq!(invoice, deserialized_invoice);
assert_eq!(invoice_str, deserialized_invoice.to_string().as_str());
assert_eq!(invoice_str, serialized_invoice.as_str().trim_matches('\"'));
// You may not use this file except in accordance with one or both of these
// licenses.
-//! Convenient utilities for paying Lightning invoices and sending spontaneous payments.
+//! Convenient utilities for paying Lightning invoices.
-use crate::Invoice;
+use crate::{Bolt11Invoice, Vec};
use bitcoin_hashes::Hash;
use lightning::chain::chaininterface::{BroadcasterInterface, FeeEstimator};
use lightning::sign::{NodeSigner, SignerProvider, EntropySource};
use lightning::ln::PaymentHash;
-use lightning::ln::channelmanager::{ChannelManager, PaymentId, Retry, RetryableSendFailure, RecipientOnionFields};
+use lightning::ln::channelmanager::{AChannelManager, ChannelManager, PaymentId, Retry, RetryableSendFailure, RecipientOnionFields, ProbeSendFailure};
use lightning::routing::router::{PaymentParameters, RouteParameters, Router};
use lightning::util::logger::Logger;
use core::ops::Deref;
use core::time::Duration;
-/// Pays the given [`Invoice`], retrying if needed based on [`Retry`].
+/// Pays the given [`Bolt11Invoice`], retrying if needed based on [`Retry`].
///
-/// [`Invoice::payment_hash`] is used as the [`PaymentId`], which ensures idempotency as long
+/// [`Bolt11Invoice::payment_hash`] is used as the [`PaymentId`], which ensures idempotency as long
/// as the payment is still pending. If the payment succeeds, you must ensure that a second payment
/// with the same [`PaymentHash`] is never sent.
///
/// If you wish to use a different payment idempotency token, see [`pay_invoice_with_id`].
-pub fn pay_invoice<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref>(
- invoice: &Invoice, retry_strategy: Retry,
- channelmanager: &ChannelManager<M, T, ES, NS, SP, F, R, L>
+pub fn pay_invoice<C: AChannelManager>(
+ invoice: &Bolt11Invoice, retry_strategy: Retry, channelmanager: &C
) -> Result<PaymentId, PaymentError>
-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,
{
let payment_id = PaymentId(invoice.payment_hash().into_inner());
- pay_invoice_with_id(invoice, payment_id, retry_strategy, channelmanager)
+ pay_invoice_with_id(invoice, payment_id, retry_strategy, channelmanager.get_cm())
.map(|()| payment_id)
}
-/// Pays the given [`Invoice`] with a custom idempotency key, retrying if needed based on [`Retry`].
+/// Pays the given [`Bolt11Invoice`] with a custom idempotency key, retrying if needed based on
+/// [`Retry`].
///
/// Note that idempotency is only guaranteed as long as the payment is still pending. Once the
/// payment completes or fails, no idempotency guarantees are made.
///
-/// You should ensure that the [`Invoice::payment_hash`] is unique and the same [`PaymentHash`]
-/// has never been paid before.
+/// You should ensure that the [`Bolt11Invoice::payment_hash`] is unique and the same
+/// [`PaymentHash`] has never been paid before.
///
/// See [`pay_invoice`] for a variant which uses the [`PaymentHash`] for the idempotency token.
-pub fn pay_invoice_with_id<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref>(
- invoice: &Invoice, payment_id: PaymentId, retry_strategy: Retry,
- channelmanager: &ChannelManager<M, T, ES, NS, SP, F, R, L>
+pub fn pay_invoice_with_id<C: AChannelManager>(
+ invoice: &Bolt11Invoice, payment_id: PaymentId, retry_strategy: Retry, channelmanager: &C
) -> Result<(), PaymentError>
-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,
{
let amt_msat = invoice.amount_milli_satoshis().ok_or(PaymentError::Invoice("amount missing"))?;
- pay_invoice_using_amount(invoice, amt_msat, payment_id, retry_strategy, channelmanager)
+ pay_invoice_using_amount(invoice, amt_msat, payment_id, retry_strategy, channelmanager.get_cm())
}
-/// Pays the given zero-value [`Invoice`] using the given amount, retrying if needed based on
+/// Pays the given zero-value [`Bolt11Invoice`] using the given amount, retrying if needed based on
/// [`Retry`].
///
-/// [`Invoice::payment_hash`] is used as the [`PaymentId`], which ensures idempotency as long
+/// [`Bolt11Invoice::payment_hash`] is used as the [`PaymentId`], which ensures idempotency as long
/// as the payment is still pending. If the payment succeeds, you must ensure that a second payment
/// with the same [`PaymentHash`] is never sent.
///
/// If you wish to use a different payment idempotency token, see
/// [`pay_zero_value_invoice_with_id`].
-pub fn pay_zero_value_invoice<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref>(
- invoice: &Invoice, amount_msats: u64, retry_strategy: Retry,
- channelmanager: &ChannelManager<M, T, ES, NS, SP, F, R, L>
+pub fn pay_zero_value_invoice<C: AChannelManager>(
+ invoice: &Bolt11Invoice, amount_msats: u64, retry_strategy: Retry, channelmanager: &C
) -> Result<PaymentId, PaymentError>
-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,
{
let payment_id = PaymentId(invoice.payment_hash().into_inner());
pay_zero_value_invoice_with_id(invoice, amount_msats, payment_id, retry_strategy,
.map(|()| payment_id)
}
-/// Pays the given zero-value [`Invoice`] using the given amount and custom idempotency key,
+/// Pays the given zero-value [`Bolt11Invoice`] using the given amount and custom idempotency key,
/// retrying if needed based on [`Retry`].
///
/// Note that idempotency is only guaranteed as long as the payment is still pending. Once the
/// payment completes or fails, no idempotency guarantees are made.
///
-/// You should ensure that the [`Invoice::payment_hash`] is unique and the same [`PaymentHash`]
-/// has never been paid before.
+/// You should ensure that the [`Bolt11Invoice::payment_hash`] is unique and the same
+/// [`PaymentHash`] has never been paid before.
///
/// See [`pay_zero_value_invoice`] for a variant which uses the [`PaymentHash`] for the
/// idempotency token.
-pub fn pay_zero_value_invoice_with_id<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref>(
- invoice: &Invoice, amount_msats: u64, payment_id: PaymentId, retry_strategy: Retry,
- channelmanager: &ChannelManager<M, T, ES, NS, SP, F, R, L>
+pub fn pay_zero_value_invoice_with_id<C: AChannelManager>(
+ invoice: &Bolt11Invoice, amount_msats: u64, payment_id: PaymentId, retry_strategy: Retry,
+ channelmanager: &C
) -> Result<(), PaymentError>
-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,
{
if invoice.amount_milli_satoshis().is_some() {
Err(PaymentError::Invoice("amount unexpected"))
} else {
pay_invoice_using_amount(invoice, amount_msats, payment_id, retry_strategy,
- channelmanager)
+ channelmanager.get_cm())
}
}
fn pay_invoice_using_amount<P: Deref>(
- invoice: &Invoice, amount_msats: u64, payment_id: PaymentId, retry_strategy: Retry,
+ invoice: &Bolt11Invoice, amount_msats: u64, payment_id: PaymentId, retry_strategy: Retry,
payer: P
) -> Result<(), PaymentError> where P::Target: Payer {
let payment_hash = PaymentHash((*invoice.payment_hash()).into_inner());
- let recipient_onion = RecipientOnionFields {
- payment_secret: Some(*invoice.payment_secret()),
- payment_metadata: invoice.payment_metadata().map(|v| v.clone()),
- };
+ let mut recipient_onion = RecipientOnionFields::secret_only(*invoice.payment_secret());
+ recipient_onion.payment_metadata = invoice.payment_metadata().map(|v| v.clone());
let mut payment_params = PaymentParameters::from_node_id(invoice.recover_payee_pub_key(),
invoice.min_final_cltv_expiry_delta() as u32)
.with_expiry_time(expiry_time_from_unix_epoch(invoice).as_secs())
if let Some(features) = invoice.features() {
payment_params = payment_params.with_bolt11_features(features.clone()).unwrap();
}
- let route_params = RouteParameters {
- payment_params,
- final_value_msat: amount_msats,
- };
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, amount_msats);
payer.send_payment(payment_hash, recipient_onion, payment_id, route_params, retry_strategy)
}
-fn expiry_time_from_unix_epoch(invoice: &Invoice) -> Duration {
+/// Sends payment probes over all paths of a route that would be used to pay the given invoice.
+///
+/// See [`ChannelManager::send_preflight_probes`] for more information.
+pub fn preflight_probe_invoice<C: AChannelManager>(
+ invoice: &Bolt11Invoice, channelmanager: &C, liquidity_limit_multiplier: Option<u64>,
+) -> Result<Vec<(PaymentHash, PaymentId)>, ProbingError>
+{
+ let amount_msat = if let Some(invoice_amount_msat) = invoice.amount_milli_satoshis() {
+ invoice_amount_msat
+ } else {
+ return Err(ProbingError::Invoice("Failed to send probe as no amount was given in the invoice."));
+ };
+
+ let mut payment_params = PaymentParameters::from_node_id(
+ invoice.recover_payee_pub_key(),
+ invoice.min_final_cltv_expiry_delta() as u32,
+ )
+ .with_expiry_time(expiry_time_from_unix_epoch(invoice).as_secs())
+ .with_route_hints(invoice.route_hints())
+ .unwrap();
+
+ if let Some(features) = invoice.features() {
+ payment_params = payment_params.with_bolt11_features(features.clone()).unwrap();
+ }
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, amount_msat);
+
+ channelmanager.get_cm().send_preflight_probes(route_params, liquidity_limit_multiplier)
+ .map_err(ProbingError::Sending)
+}
+
+/// Sends payment probes over all paths of a route that would be used to pay the given zero-value
+/// invoice using the given amount.
+///
+/// See [`ChannelManager::send_preflight_probes`] for more information.
+pub fn preflight_probe_zero_value_invoice<C: AChannelManager>(
+ invoice: &Bolt11Invoice, amount_msat: u64, channelmanager: &C,
+ liquidity_limit_multiplier: Option<u64>,
+) -> Result<Vec<(PaymentHash, PaymentId)>, ProbingError>
+{
+ if invoice.amount_milli_satoshis().is_some() {
+ return Err(ProbingError::Invoice("amount unexpected"));
+ }
+
+ let mut payment_params = PaymentParameters::from_node_id(
+ invoice.recover_payee_pub_key(),
+ invoice.min_final_cltv_expiry_delta() as u32,
+ )
+ .with_expiry_time(expiry_time_from_unix_epoch(invoice).as_secs())
+ .with_route_hints(invoice.route_hints())
+ .unwrap();
+
+ if let Some(features) = invoice.features() {
+ payment_params = payment_params.with_bolt11_features(features.clone()).unwrap();
+ }
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, amount_msat);
+
+ channelmanager.get_cm().send_preflight_probes(route_params, liquidity_limit_multiplier)
+ .map_err(ProbingError::Sending)
+}
+
+fn expiry_time_from_unix_epoch(invoice: &Bolt11Invoice) -> Duration {
invoice.signed_invoice.raw_invoice.data.timestamp.0 + invoice.expiry_time()
}
/// An error that may occur when making a payment.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum PaymentError {
- /// An error resulting from the provided [`Invoice`] or payment hash.
+ /// An error resulting from the provided [`Bolt11Invoice`] or payment hash.
Invoice(&'static str),
/// An error occurring when sending a payment.
Sending(RetryableSendFailure),
}
-/// A trait defining behavior of an [`Invoice`] payer.
+/// An error that may occur when sending a payment probe.
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum ProbingError {
+ /// An error resulting from the provided [`Bolt11Invoice`].
+ Invoice(&'static str),
+ /// An error occurring when sending a payment probe.
+ Sending(ProbeSendFailure),
+}
+
+/// A trait defining behavior of a [`Bolt11Invoice`] payer.
///
/// Useful for unit testing internal methods.
trait Payer {
duration_since_epoch
}
- fn invoice(payment_preimage: PaymentPreimage) -> Invoice {
+ fn invoice(payment_preimage: PaymentPreimage) -> Bolt11Invoice {
let payment_hash = Sha256::hash(&payment_preimage.0);
let private_key = SecretKey::from_slice(&[42; 32]).unwrap();
.unwrap()
}
- fn zero_value_invoice(payment_preimage: PaymentPreimage) -> Invoice {
+ fn zero_value_invoice(payment_preimage: PaymentPreimage) -> Bolt11Invoice {
let payment_hash = Sha256::hash(&payment_preimage.0);
let private_key = SecretKey::from_slice(&[42; 32]).unwrap();
use bech32::{ToBase32, u5, WriteBase32, Base32Len};
use crate::prelude::*;
-use super::{Invoice, Sha256, TaggedField, ExpiryTime, MinFinalCltvExpiryDelta, Fallback, PayeePubKey, InvoiceSignature, PositiveTimestamp,
- PrivateRoute, Description, RawTaggedField, Currency, RawHrp, SiPrefix, constants, SignedRawInvoice, RawDataPart};
+use super::{Bolt11Invoice, Sha256, TaggedField, ExpiryTime, MinFinalCltvExpiryDelta, Fallback, PayeePubKey, Bolt11InvoiceSignature, PositiveTimestamp,
+ PrivateRoute, Description, RawTaggedField, Currency, RawHrp, SiPrefix, constants, SignedRawBolt11Invoice, RawDataPart};
/// Converts a stream of bytes written to it to base32. On finalization the according padding will
/// be applied. That means the results of writing two data blocks with one or two `BytesToBase32`
}
}
-impl Display for Invoice {
+impl Display for Bolt11Invoice {
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
self.signed_invoice.fmt(f)
}
}
-impl Display for SignedRawInvoice {
+impl Display for SignedRawBolt11Invoice {
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
let hrp = self.raw_invoice.hrp.to_string();
let mut data = self.raw_invoice.data.to_base32();
}
}
-impl ToBase32 for InvoiceSignature {
+impl ToBase32 for Bolt11InvoiceSignature {
fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
let mut converter = BytesToBase32::new(writer);
let (recovery_id, signature) = self.0.serialize_compact();
//! Convenient utilities to create an invoice.
-use crate::{CreationError, Currency, Invoice, InvoiceBuilder, SignOrCreationError};
+use crate::{Bolt11Invoice, CreationError, Currency, InvoiceBuilder, SignOrCreationError};
-use crate::{prelude::*, Description, InvoiceDescription, Sha256};
+use crate::{prelude::*, Description, Bolt11InvoiceDescription, Sha256};
use bech32::ToBase32;
use bitcoin_hashes::Hash;
use lightning::chain;
amt_msat: Option<u64>, payment_hash: Option<PaymentHash>, description: String,
invoice_expiry_delta_secs: u32, phantom_route_hints: Vec<PhantomRouteHints>, entropy_source: ES,
node_signer: NS, logger: L, network: Currency, min_final_cltv_expiry_delta: Option<u16>, duration_since_epoch: Duration,
-) -> Result<Invoice, SignOrCreationError<()>>
+) -> Result<Bolt11Invoice, SignOrCreationError<()>>
where
ES::Target: EntropySource,
NS::Target: NodeSigner,
L::Target: Logger,
{
let description = Description::new(description).map_err(SignOrCreationError::CreationError)?;
- let description = InvoiceDescription::Direct(&description,);
+ let description = Bolt11InvoiceDescription::Direct(&description,);
_create_phantom_invoice::<ES, NS, L>(
amt_msat, payment_hash, description, invoice_expiry_delta_secs, phantom_route_hints,
entropy_source, node_signer, logger, network, min_final_cltv_expiry_delta, duration_since_epoch,
amt_msat: Option<u64>, payment_hash: Option<PaymentHash>, invoice_expiry_delta_secs: u32,
description_hash: Sha256, phantom_route_hints: Vec<PhantomRouteHints>, entropy_source: ES,
node_signer: NS, logger: L, network: Currency, min_final_cltv_expiry_delta: Option<u16>, duration_since_epoch: Duration,
-) -> Result<Invoice, SignOrCreationError<()>>
+) -> Result<Bolt11Invoice, SignOrCreationError<()>>
where
ES::Target: EntropySource,
NS::Target: NodeSigner,
L::Target: Logger,
{
_create_phantom_invoice::<ES, NS, L>(
- amt_msat, payment_hash, InvoiceDescription::Hash(&description_hash),
+ amt_msat, payment_hash, Bolt11InvoiceDescription::Hash(&description_hash),
invoice_expiry_delta_secs, phantom_route_hints, entropy_source, node_signer, logger, network,
min_final_cltv_expiry_delta, duration_since_epoch,
)
const MAX_CHANNEL_HINTS: usize = 3;
fn _create_phantom_invoice<ES: Deref, NS: Deref, L: Deref>(
- amt_msat: Option<u64>, payment_hash: Option<PaymentHash>, description: InvoiceDescription,
+ amt_msat: Option<u64>, payment_hash: Option<PaymentHash>, description: Bolt11InvoiceDescription,
invoice_expiry_delta_secs: u32, phantom_route_hints: Vec<PhantomRouteHints>, entropy_source: ES,
node_signer: NS, logger: L, network: Currency, min_final_cltv_expiry_delta: Option<u16>, duration_since_epoch: Duration,
-) -> Result<Invoice, SignOrCreationError<()>>
+) -> Result<Bolt11Invoice, SignOrCreationError<()>>
where
ES::Target: EntropySource,
NS::Target: NodeSigner,
}
let invoice = match description {
- InvoiceDescription::Direct(description) => {
+ Bolt11InvoiceDescription::Direct(description) => {
InvoiceBuilder::new(network).description(description.0.clone())
}
- InvoiceDescription::Hash(hash) => InvoiceBuilder::new(network).description_hash(hash.0),
+ Bolt11InvoiceDescription::Hash(hash) => InvoiceBuilder::new(network).description_hash(hash.0),
};
// If we ever see performance here being too slow then we should probably take this ExpandedKey as a parameter instead.
};
log_trace!(logger, "Creating phantom invoice from {} participating nodes with payment hash {}",
- phantom_route_hints.len(), log_bytes!(payment_hash.0));
+ phantom_route_hints.len(), &payment_hash);
let mut invoice = invoice
.duration_since_epoch(duration_since_epoch)
let data_without_signature = raw_invoice.data.to_base32();
let signed_raw_invoice = raw_invoice.sign(|_| node_signer.sign_invoice(hrp_bytes, &data_without_signature, Recipient::PhantomNode));
match signed_raw_invoice {
- Ok(inv) => Ok(Invoice::from_signed(inv).unwrap()),
+ Ok(inv) => Ok(Bolt11Invoice::from_signed(inv).unwrap()),
Err(e) => Err(SignOrCreationError::SignError(e))
}
}
channelmanager: &ChannelManager<M, T, ES, NS, SP, F, R, L>, node_signer: NS, logger: L,
network: Currency, amt_msat: Option<u64>, description: String, invoice_expiry_delta_secs: u32,
min_final_cltv_expiry_delta: Option<u16>,
-) -> Result<Invoice, SignOrCreationError<()>>
+) -> Result<Bolt11Invoice, SignOrCreationError<()>>
where
M::Target: chain::Watch<<SP::Target as SignerProvider>::Signer>,
T::Target: BroadcasterInterface,
channelmanager: &ChannelManager<M, T, ES, NS, SP, F, R, L>, node_signer: NS, logger: L,
network: Currency, amt_msat: Option<u64>, description_hash: Sha256,
invoice_expiry_delta_secs: u32, min_final_cltv_expiry_delta: Option<u16>,
-) -> Result<Invoice, SignOrCreationError<()>>
+) -> Result<Bolt11Invoice, SignOrCreationError<()>>
where
M::Target: chain::Watch<<SP::Target as SignerProvider>::Signer>,
T::Target: BroadcasterInterface,
channelmanager: &ChannelManager<M, T, ES, NS, SP, F, R, L>, node_signer: NS, logger: L,
network: Currency, amt_msat: Option<u64>, description_hash: Sha256,
duration_since_epoch: Duration, invoice_expiry_delta_secs: u32, min_final_cltv_expiry_delta: Option<u16>,
-) -> Result<Invoice, SignOrCreationError<()>>
+) -> Result<Bolt11Invoice, SignOrCreationError<()>>
where
M::Target: chain::Watch<<SP::Target as SignerProvider>::Signer>,
T::Target: BroadcasterInterface,
{
_create_invoice_from_channelmanager_and_duration_since_epoch(
channelmanager, node_signer, logger, network, amt_msat,
- InvoiceDescription::Hash(&description_hash),
+ Bolt11InvoiceDescription::Hash(&description_hash),
duration_since_epoch, invoice_expiry_delta_secs, min_final_cltv_expiry_delta,
)
}
channelmanager: &ChannelManager<M, T, ES, NS, SP, F, R, L>, node_signer: NS, logger: L,
network: Currency, amt_msat: Option<u64>, description: String, duration_since_epoch: Duration,
invoice_expiry_delta_secs: u32, min_final_cltv_expiry_delta: Option<u16>,
-) -> Result<Invoice, SignOrCreationError<()>>
+) -> Result<Bolt11Invoice, SignOrCreationError<()>>
where
M::Target: chain::Watch<<SP::Target as SignerProvider>::Signer>,
T::Target: BroadcasterInterface,
{
_create_invoice_from_channelmanager_and_duration_since_epoch(
channelmanager, node_signer, logger, network, amt_msat,
- InvoiceDescription::Direct(
+ Bolt11InvoiceDescription::Direct(
&Description::new(description).map_err(SignOrCreationError::CreationError)?,
),
duration_since_epoch, invoice_expiry_delta_secs, min_final_cltv_expiry_delta,
fn _create_invoice_from_channelmanager_and_duration_since_epoch<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref>(
channelmanager: &ChannelManager<M, T, ES, NS, SP, F, R, L>, node_signer: NS, logger: L,
- network: Currency, amt_msat: Option<u64>, description: InvoiceDescription,
+ network: Currency, amt_msat: Option<u64>, description: Bolt11InvoiceDescription,
duration_since_epoch: Duration, invoice_expiry_delta_secs: u32, min_final_cltv_expiry_delta: Option<u16>,
-) -> Result<Invoice, SignOrCreationError<()>>
+) -> Result<Bolt11Invoice, SignOrCreationError<()>>
where
M::Target: chain::Watch<<SP::Target as SignerProvider>::Signer>,
T::Target: BroadcasterInterface,
channelmanager: &ChannelManager<M, T, ES, NS, SP, F, R, L>, node_signer: NS, logger: L,
network: Currency, amt_msat: Option<u64>, description: String, duration_since_epoch: Duration,
invoice_expiry_delta_secs: u32, payment_hash: PaymentHash, min_final_cltv_expiry_delta: Option<u16>,
-) -> Result<Invoice, SignOrCreationError<()>>
+) -> Result<Bolt11Invoice, SignOrCreationError<()>>
where
M::Target: chain::Watch<<SP::Target as SignerProvider>::Signer>,
T::Target: BroadcasterInterface,
.map_err(|()| SignOrCreationError::CreationError(CreationError::InvalidAmount))?;
_create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_hash(
channelmanager, node_signer, logger, network, amt_msat,
- InvoiceDescription::Direct(
+ Bolt11InvoiceDescription::Direct(
&Description::new(description).map_err(SignOrCreationError::CreationError)?,
),
duration_since_epoch, invoice_expiry_delta_secs, payment_hash, payment_secret,
fn _create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_hash<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref>(
channelmanager: &ChannelManager<M, T, ES, NS, SP, F, R, L>, node_signer: NS, logger: L,
- network: Currency, amt_msat: Option<u64>, description: InvoiceDescription, duration_since_epoch: Duration,
- invoice_expiry_delta_secs: u32, payment_hash: PaymentHash, payment_secret: PaymentSecret,
- min_final_cltv_expiry_delta: Option<u16>,
-) -> Result<Invoice, SignOrCreationError<()>>
+ network: Currency, amt_msat: Option<u64>, description: Bolt11InvoiceDescription,
+ duration_since_epoch: Duration, invoice_expiry_delta_secs: u32, payment_hash: PaymentHash,
+ payment_secret: PaymentSecret, min_final_cltv_expiry_delta: Option<u16>,
+) -> Result<Bolt11Invoice, SignOrCreationError<()>>
where
M::Target: chain::Watch<<SP::Target as SignerProvider>::Signer>,
T::Target: BroadcasterInterface,
return Err(SignOrCreationError::CreationError(CreationError::MinFinalCltvExpiryDeltaTooShort));
}
- log_trace!(logger, "Creating invoice with payment hash {}", log_bytes!(payment_hash.0));
+ log_trace!(logger, "Creating invoice with payment hash {}", &payment_hash);
let invoice = match description {
- InvoiceDescription::Direct(description) => {
+ Bolt11InvoiceDescription::Direct(description) => {
InvoiceBuilder::new(network).description(description.0.clone())
}
- InvoiceDescription::Hash(hash) => InvoiceBuilder::new(network).description_hash(hash.0),
+ Bolt11InvoiceDescription::Hash(hash) => InvoiceBuilder::new(network).description_hash(hash.0),
};
let mut invoice = invoice
let data_without_signature = raw_invoice.data.to_base32();
let signed_raw_invoice = raw_invoice.sign(|_| node_signer.sign_invoice(hrp_bytes, &data_without_signature, Recipient::Node));
match signed_raw_invoice {
- Ok(inv) => Ok(Invoice::from_signed(inv).unwrap()),
+ Ok(inv) => Ok(Bolt11Invoice::from_signed(inv).unwrap()),
Err(e) => Err(SignOrCreationError::SignError(e))
}
}
log_trace!(logger, "Considering {} channels for invoice route hints", channels.len());
for channel in channels.into_iter().filter(|chan| chan.is_channel_ready) {
if channel.get_inbound_payment_scid().is_none() || channel.counterparty.forwarding_info.is_none() {
- log_trace!(logger, "Ignoring channel {} for invoice route hints", log_bytes!(channel.channel_id));
+ log_trace!(logger, "Ignoring channel {} for invoice route hints", &channel.channel_id);
continue;
}
// If any public channel exists, return no hints and let the sender
// look at the public channels instead.
log_trace!(logger, "Not including channels in invoice route hints on account of public channel {}",
- log_bytes!(channel.channel_id));
+ &channel.channel_id);
return vec![].into_iter().take(MAX_CHANNEL_HINTS).map(route_hint_from_channel);
}
}
log_trace!(logger,
"Preferring counterparty {} channel {} (SCID {:?}, {} msats) over {} (SCID {:?}, {} msats) for invoice route hints",
log_pubkey!(channel.counterparty.node_id),
- log_bytes!(channel.channel_id), channel.short_channel_id,
+ &channel.channel_id, channel.short_channel_id,
channel.inbound_capacity_msat,
- log_bytes!(entry.get().channel_id), entry.get().short_channel_id,
+ &entry.get().channel_id, entry.get().short_channel_id,
current_max_capacity);
entry.insert(channel);
} else {
log_trace!(logger,
"Preferring counterparty {} channel {} (SCID {:?}, {} msats) over {} (SCID {:?}, {} msats) for invoice route hints",
log_pubkey!(channel.counterparty.node_id),
- log_bytes!(entry.get().channel_id), entry.get().short_channel_id,
+ &entry.get().channel_id, entry.get().short_channel_id,
current_max_capacity,
- log_bytes!(channel.channel_id), channel.short_channel_id,
+ &channel.channel_id, channel.short_channel_id,
channel.inbound_capacity_msat);
}
}
if include_channel {
log_trace!(logger, "Including channel {} in invoice route hints",
- log_bytes!(channel.channel_id));
+ &channel.channel_id);
} else if !has_enough_capacity {
log_trace!(logger, "Ignoring channel {} without enough capacity for invoice route hints",
- log_bytes!(channel.channel_id));
+ &channel.channel_id);
} else {
debug_assert!(!channel.is_usable || (has_pub_unconf_chan && !channel.is_public));
log_trace!(logger, "Ignoring channel {} with disconnected peer",
- log_bytes!(channel.channel_id));
+ &channel.channel_id);
}
include_channel
#[cfg(test)]
mod test {
+ use core::cell::RefCell;
use core::time::Duration;
- use crate::{Currency, Description, InvoiceDescription, SignOrCreationError, CreationError};
+ use crate::{Currency, Description, Bolt11InvoiceDescription, SignOrCreationError, CreationError};
use bitcoin_hashes::{Hash, sha256};
use bitcoin_hashes::sha256::Hash as Sha256;
use lightning::sign::PhantomKeysManager;
- use lightning::events::{MessageSendEvent, MessageSendEventsProvider, Event};
+ use lightning::events::{MessageSendEvent, MessageSendEventsProvider, Event, EventsProvider};
use lightning::ln::{PaymentPreimage, PaymentHash};
use lightning::ln::channelmanager::{PhantomRouteHints, MIN_FINAL_CLTV_EXPIRY_DELTA, PaymentId, RecipientOnionFields, Retry};
use lightning::ln::functional_test_utils::*;
assert_eq!(invoice.amount_pico_btc(), Some(100_000));
// If no `min_final_cltv_expiry_delta` is specified, then it should be `MIN_FINAL_CLTV_EXPIRY_DELTA`.
assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64);
- assert_eq!(invoice.description(), InvoiceDescription::Direct(&Description("test".to_string())));
+ assert_eq!(invoice.description(), Bolt11InvoiceDescription::Direct(&Description("test".to_string())));
assert_eq!(invoice.expiry_time(), Duration::from_secs(non_default_invoice_expiry_secs.into()));
// Invoice SCIDs should always use inbound SCID aliases over the real channel ID, if one is
invoice.min_final_cltv_expiry_delta() as u32)
.with_bolt11_features(invoice.features().unwrap().clone()).unwrap()
.with_route_hints(invoice.route_hints()).unwrap();
- let route_params = RouteParameters {
- payment_params,
- final_value_msat: invoice.amount_milli_satoshis().unwrap(),
- };
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params, invoice.amount_milli_satoshis().unwrap());
let payment_event = {
let mut payment_hash = PaymentHash([0; 32]);
payment_hash.0.copy_from_slice(&invoice.payment_hash().as_ref()[0..32]);
).unwrap();
assert_eq!(invoice.amount_pico_btc(), Some(100_000));
assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64);
- assert_eq!(invoice.description(), InvoiceDescription::Hash(&crate::Sha256(Sha256::hash("Testing description_hash".as_bytes()))));
+ assert_eq!(invoice.description(), Bolt11InvoiceDescription::Hash(&crate::Sha256(Sha256::hash("Testing description_hash".as_bytes()))));
}
#[test]
).unwrap();
assert_eq!(invoice.amount_pico_btc(), Some(100_000));
assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64);
- assert_eq!(invoice.description(), InvoiceDescription::Direct(&Description("test".to_string())));
+ assert_eq!(invoice.description(), Bolt11InvoiceDescription::Direct(&Description("test".to_string())));
assert_eq!(invoice.payment_hash(), &sha256::Hash::from_slice(&payment_hash.0[..]).unwrap());
}
};
assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64);
- assert_eq!(invoice.description(), InvoiceDescription::Direct(&Description("test".to_string())));
+ assert_eq!(invoice.description(), Bolt11InvoiceDescription::Direct(&Description("test".to_string())));
assert_eq!(invoice.route_hints().len(), 2);
assert_eq!(invoice.expiry_time(), Duration::from_secs(non_default_invoice_expiry_secs.into()));
assert!(!invoice.features().unwrap().supports_basic_mpp());
invoice.min_final_cltv_expiry_delta() as u32)
.with_bolt11_features(invoice.features().unwrap().clone()).unwrap()
.with_route_hints(invoice.route_hints()).unwrap();
- let params = RouteParameters {
- payment_params,
- final_value_msat: invoice.amount_milli_satoshis().unwrap(),
- };
+ let params = RouteParameters::from_payment_params_and_value(
+ payment_params, invoice.amount_milli_satoshis().unwrap());
let (payment_event, fwd_idx) = {
let mut payment_hash = PaymentHash([0; 32]);
payment_hash.0.copy_from_slice(&invoice.payment_hash().as_ref()[0..32]);
// Note that we have to "forward pending HTLCs" twice before we see the PaymentClaimable as
// this "emulates" the payment taking two hops, providing some privacy to make phantom node
// payments "look real" by taking more time.
- expect_pending_htlcs_forwardable_ignore!(nodes[fwd_idx]);
- nodes[fwd_idx].node.process_pending_htlc_forwards();
- expect_pending_htlcs_forwardable_ignore!(nodes[fwd_idx]);
- nodes[fwd_idx].node.process_pending_htlc_forwards();
+ let other_events = RefCell::new(Vec::new());
+ let forward_event_handler = |event: Event| {
+ if let Event::PendingHTLCsForwardable { .. } = event {
+ nodes[fwd_idx].node.process_pending_htlc_forwards();
+ } else {
+ other_events.borrow_mut().push(event);
+ }
+ };
+ nodes[fwd_idx].node.process_pending_events(&forward_event_handler);
+ nodes[fwd_idx].node.process_pending_events(&forward_event_handler);
let payment_preimage_opt = if user_generated_pmt_hash { None } else { Some(payment_preimage) };
- expect_payment_claimable!(&nodes[fwd_idx], payment_hash, payment_secret, payment_amt, payment_preimage_opt, invoice.recover_payee_pub_key());
+ assert_eq!(other_events.borrow().len(), 1);
+ check_payment_claimable(&other_events.borrow()[0], payment_hash, payment_secret, payment_amt, payment_preimage_opt, invoice.recover_payee_pub_key());
do_claim_payment_along_route(&nodes[0], &[&vec!(&nodes[fwd_idx])[..]], false, payment_preimage);
- let events = nodes[0].node.get_and_clear_pending_events();
- assert_eq!(events.len(), 2);
- match events[0] {
- Event::PaymentSent { payment_preimage: ref ev_preimage, payment_hash: ref ev_hash, ref fee_paid_msat, .. } => {
- assert_eq!(payment_preimage, *ev_preimage);
- assert_eq!(payment_hash, *ev_hash);
- assert_eq!(fee_paid_msat, &Some(0));
- },
- _ => panic!("Unexpected event")
- }
- match events[1] {
- Event::PaymentPathSuccessful { payment_hash: hash, .. } => {
- assert_eq!(hash, Some(payment_hash));
- },
- _ => panic!("Unexpected event")
- }
+ expect_payment_sent(&nodes[0], payment_preimage, None, true, true);
}
#[test]
assert_eq!(invoice.amount_pico_btc(), Some(200_000));
assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64);
assert_eq!(invoice.expiry_time(), Duration::from_secs(non_default_invoice_expiry_secs.into()));
- assert_eq!(invoice.description(), InvoiceDescription::Hash(&crate::Sha256(Sha256::hash("Description hash phantom invoice".as_bytes()))));
+ assert_eq!(invoice.description(), Bolt11InvoiceDescription::Hash(&crate::Sha256(Sha256::hash("Description hash phantom invoice".as_bytes()))));
}
#[test]
use std::time::Duration;
use std::str::FromStr;
-fn get_test_tuples() -> Vec<(String, SignedRawInvoice, bool, bool)> {
+fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> {
vec![
(
"lnbc1pvjluezsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaq9qrsgq357wnc5r2ueh7ck6q93dj32dlqnls087fxdwk8qakdyafkq3yap9us6v52vjjsrvywa6rt52cm9r9zqt8r2t7mlcwspyetp5h2tztugp9lfyql".to_owned(),
fn invoice_deserialize() {
for (serialized, deserialized, ignore_feature_diff, ignore_unknown_fields) in get_test_tuples() {
eprintln!("Testing invoice {}...", serialized);
- let parsed = serialized.parse::<SignedRawInvoice>().unwrap();
+ let parsed = serialized.parse::<SignedRawBolt11Invoice>().unwrap();
let (parsed_invoice, _, parsed_sig) = parsed.into_parts();
let (deserialized_invoice, _, deserialized_sig) = deserialized.into_parts();
}
assert_eq!(deserialized_hunks, parsed_hunks);
- Invoice::from_signed(serialized.parse::<SignedRawInvoice>().unwrap()).unwrap();
+ Bolt11Invoice::from_signed(serialized.parse::<SignedRawBolt11Invoice>().unwrap()).unwrap();
}
}
#[test]
fn test_bolt_invalid_invoices() {
// Tests the BOLT 11 invalid invoice test vectors
- assert_eq!(Invoice::from_str(
+ assert_eq!(Bolt11Invoice::from_str(
"lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdeessp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q4psqqqqqqqqqqqqqqqqsgqtqyx5vggfcsll4wu246hz02kp85x4katwsk9639we5n5yngc3yhqkm35jnjw4len8vrnqnf5ejh0mzj9n3vz2px97evektfm2l6wqccp3y7372"
- ), Err(ParseOrSemanticError::SemanticError(SemanticError::InvalidFeatures)));
- assert_eq!(Invoice::from_str(
+ ), Err(ParseOrSemanticError::SemanticError(Bolt11SemanticError::InvalidFeatures)));
+ assert_eq!(Bolt11Invoice::from_str(
"lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpquwpc4curk03c9wlrswe78q4eyqc7d8d0xqzpuyk0sg5g70me25alkluzd2x62aysf2pyy8edtjeevuv4p2d5p76r4zkmneet7uvyakky2zr4cusd45tftc9c5fh0nnqpnl2jfll544esqchsrnt"
- ), Err(ParseOrSemanticError::ParseError(ParseError::Bech32Error(bech32::Error::InvalidChecksum))));
- assert_eq!(Invoice::from_str(
+ ), Err(ParseOrSemanticError::ParseError(Bolt11ParseError::Bech32Error(bech32::Error::InvalidChecksum))));
+ assert_eq!(Bolt11Invoice::from_str(
"pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpquwpc4curk03c9wlrswe78q4eyqc7d8d0xqzpuyk0sg5g70me25alkluzd2x62aysf2pyy8edtjeevuv4p2d5p76r4zkmneet7uvyakky2zr4cusd45tftc9c5fh0nnqpnl2jfll544esqchsrny"
- ), Err(ParseOrSemanticError::ParseError(ParseError::Bech32Error(bech32::Error::MissingSeparator))));
- assert_eq!(Invoice::from_str(
+ ), Err(ParseOrSemanticError::ParseError(Bolt11ParseError::Bech32Error(bech32::Error::MissingSeparator))));
+ assert_eq!(Bolt11Invoice::from_str(
"LNBC2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpquwpc4curk03c9wlrswe78q4eyqc7d8d0xqzpuyk0sg5g70me25alkluzd2x62aysf2pyy8edtjeevuv4p2d5p76r4zkmneet7uvyakky2zr4cusd45tftc9c5fh0nnqpnl2jfll544esqchsrny"
- ), Err(ParseOrSemanticError::ParseError(ParseError::Bech32Error(bech32::Error::MixedCase))));
- assert_eq!(Invoice::from_str(
+ ), Err(ParseOrSemanticError::ParseError(Bolt11ParseError::Bech32Error(bech32::Error::MixedCase))));
+ assert_eq!(Bolt11Invoice::from_str(
"lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpusp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9qrsgqwgt7mcn5yqw3yx0w94pswkpq6j9uh6xfqqqtsk4tnarugeektd4hg5975x9am52rz4qskukxdmjemg92vvqz8nvmsye63r5ykel43pgz7zq0g2"
- ), Err(ParseOrSemanticError::SemanticError(SemanticError::InvalidSignature)));
- assert_eq!(Invoice::from_str(
+ ), Err(ParseOrSemanticError::SemanticError(Bolt11SemanticError::InvalidSignature)));
+ assert_eq!(Bolt11Invoice::from_str(
"lnbc1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6na6hlh"
- ), Err(ParseOrSemanticError::ParseError(ParseError::TooShortDataPart)));
- assert_eq!(Invoice::from_str(
+ ), Err(ParseOrSemanticError::ParseError(Bolt11ParseError::TooShortDataPart)));
+ assert_eq!(Bolt11Invoice::from_str(
"lnbc2500x1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpusp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9qrsgqrrzc4cvfue4zp3hggxp47ag7xnrlr8vgcmkjxk3j5jqethnumgkpqp23z9jclu3v0a7e0aruz366e9wqdykw6dxhdzcjjhldxq0w6wgqcnu43j"
- ), Err(ParseOrSemanticError::ParseError(ParseError::UnknownSiPrefix)));
- assert_eq!(Invoice::from_str(
+ ), Err(ParseOrSemanticError::ParseError(Bolt11ParseError::UnknownSiPrefix)));
+ assert_eq!(Bolt11Invoice::from_str(
"lnbc2500000001p1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpusp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9qrsgq0lzc236j96a95uv0m3umg28gclm5lqxtqqwk32uuk4k6673k6n5kfvx3d2h8s295fad45fdhmusm8sjudfhlf6dcsxmfvkeywmjdkxcp99202x"
- ), Err(ParseOrSemanticError::SemanticError(SemanticError::ImpreciseAmount)));
+ ), Err(ParseOrSemanticError::SemanticError(Bolt11SemanticError::ImpreciseAmount)));
}
[package]
name = "lightning-net-tokio"
-version = "0.0.116-alpha1"
+version = "0.0.117-alpha2"
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.116-alpha1", path = "../lightning" }
-tokio = { version = "1.0", features = [ "io-util", "macros", "rt", "sync", "net", "time" ] }
+lightning = { version = "0.0.117-alpha2", path = "../lightning" }
+tokio = { version = "1.0", features = [ "rt", "sync", "net", "time" ] }
[dev-dependencies]
-tokio = { version = "1.14", features = [ "io-util", "macros", "rt", "rt-multi-thread", "sync", "net", "time" ] }
-lightning = { version = "0.0.116-alpha1", path = "../lightning", features = ["_test_utils"] }
+tokio = { version = "1.14", features = [ "macros", "rt", "rt-multi-thread", "sync", "net", "time" ] }
+lightning = { version = "0.0.117-alpha2", path = "../lightning", features = ["_test_utils"] }
use bitcoin::secp256k1::PublicKey;
use tokio::net::TcpStream;
-use tokio::{io, time};
+use tokio::time;
use tokio::sync::mpsc;
-use tokio::io::{AsyncReadExt, AsyncWrite, AsyncWriteExt};
use lightning::ln::peer_handler;
use lightning::ln::peer_handler::SocketDescriptor as LnSocketTrait;
use lightning::ln::peer_handler::APeerManager;
-use lightning::ln::msgs::NetAddress;
+use lightning::ln::msgs::SocketAddress;
use std::ops::Deref;
-use std::task;
+use std::task::{self, Poll};
+use std::future::Future;
use std::net::SocketAddr;
use std::net::TcpStream as StdTcpStream;
use std::sync::{Arc, Mutex};
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::Duration;
+use std::pin::Pin;
use std::hash::Hash;
static ID_COUNTER: AtomicU64 = AtomicU64::new(0);
+// We only need to select over multiple futures in one place, and taking on the full `tokio/macros`
+// dependency tree in order to do so (which has broken our MSRV before) is excessive. Instead, we
+// define a trivial two- and three- select macro with the specific types we need and just use that.
+
+pub(crate) enum SelectorOutput {
+ A(Option<()>), B(Option<()>), C(tokio::io::Result<()>),
+}
+
+pub(crate) struct TwoSelector<
+ A: Future<Output=Option<()>> + Unpin, B: Future<Output=Option<()>> + Unpin
+> {
+ pub a: A,
+ pub b: B,
+}
+
+impl<
+ A: Future<Output=Option<()>> + Unpin, B: Future<Output=Option<()>> + Unpin
+> Future for TwoSelector<A, B> {
+ type Output = SelectorOutput;
+ fn poll(mut self: Pin<&mut Self>, ctx: &mut task::Context<'_>) -> Poll<SelectorOutput> {
+ match Pin::new(&mut self.a).poll(ctx) {
+ Poll::Ready(res) => { return Poll::Ready(SelectorOutput::A(res)); },
+ Poll::Pending => {},
+ }
+ match Pin::new(&mut self.b).poll(ctx) {
+ Poll::Ready(res) => { return Poll::Ready(SelectorOutput::B(res)); },
+ Poll::Pending => {},
+ }
+ Poll::Pending
+ }
+}
+
+pub(crate) struct ThreeSelector<
+ A: Future<Output=Option<()>> + Unpin, B: Future<Output=Option<()>> + Unpin, C: Future<Output=tokio::io::Result<()>> + Unpin
+> {
+ pub a: A,
+ pub b: B,
+ pub c: C,
+}
+
+impl<
+ A: Future<Output=Option<()>> + Unpin, B: Future<Output=Option<()>> + Unpin, C: Future<Output=tokio::io::Result<()>> + Unpin
+> Future for ThreeSelector<A, B, C> {
+ type Output = SelectorOutput;
+ fn poll(mut self: Pin<&mut Self>, ctx: &mut task::Context<'_>) -> Poll<SelectorOutput> {
+ match Pin::new(&mut self.a).poll(ctx) {
+ Poll::Ready(res) => { return Poll::Ready(SelectorOutput::A(res)); },
+ Poll::Pending => {},
+ }
+ match Pin::new(&mut self.b).poll(ctx) {
+ Poll::Ready(res) => { return Poll::Ready(SelectorOutput::B(res)); },
+ Poll::Pending => {},
+ }
+ match Pin::new(&mut self.c).poll(ctx) {
+ Poll::Ready(res) => { return Poll::Ready(SelectorOutput::C(res)); },
+ Poll::Pending => {},
+ }
+ Poll::Pending
+ }
+}
+
/// Connection contains all our internal state for a connection - we hold a reference to the
/// Connection object (in an Arc<Mutex<>>) in each SocketDescriptor we create as well as in the
/// read future (which is returned by schedule_read).
struct Connection {
- writer: Option<io::WriteHalf<TcpStream>>,
+ writer: Option<Arc<TcpStream>>,
// Because our PeerManager is templated by user-provided types, and we can't (as far as I can
// tell) have a const RawWakerVTable built out of templated functions, we need some indirection
// between being woken up with write-ready and calling PeerManager::write_buffer_space_avail.
async fn schedule_read<PM: Deref + 'static + Send + Sync + Clone>(
peer_manager: PM,
us: Arc<Mutex<Self>>,
- mut reader: io::ReadHalf<TcpStream>,
+ reader: Arc<TcpStream>,
mut read_wake_receiver: mpsc::Receiver<()>,
mut write_avail_receiver: mpsc::Receiver<()>,
) where PM::Target: APeerManager<Descriptor = SocketDescriptor> {
}
us_lock.read_paused
};
- tokio::select! {
- v = write_avail_receiver.recv() => {
+ // TODO: Drop the Box'ing of the futures once Rust has pin-on-stack support.
+ let select_result = if read_paused {
+ TwoSelector {
+ a: Box::pin(write_avail_receiver.recv()),
+ b: Box::pin(read_wake_receiver.recv()),
+ }.await
+ } else {
+ ThreeSelector {
+ a: Box::pin(write_avail_receiver.recv()),
+ b: Box::pin(read_wake_receiver.recv()),
+ c: Box::pin(reader.readable()),
+ }.await
+ };
+ match select_result {
+ SelectorOutput::A(v) => {
assert!(v.is_some()); // We can't have dropped the sending end, its in the us Arc!
if peer_manager.as_ref().write_buffer_space_avail(&mut our_descriptor).is_err() {
break Disconnect::CloseConnection;
}
},
- _ = read_wake_receiver.recv() => {},
- read = reader.read(&mut buf), if !read_paused => match read {
- Ok(0) => break Disconnect::PeerDisconnected,
- Ok(len) => {
- let read_res = peer_manager.as_ref().read_event(&mut our_descriptor, &buf[0..len]);
- let mut us_lock = us.lock().unwrap();
- match read_res {
- Ok(pause_read) => {
- if pause_read {
- us_lock.read_paused = true;
- }
- },
- Err(_) => break Disconnect::CloseConnection,
- }
- },
- Err(_) => break Disconnect::PeerDisconnected,
+ SelectorOutput::B(_) => {},
+ SelectorOutput::C(res) => {
+ if res.is_err() { break Disconnect::PeerDisconnected; }
+ match reader.try_read(&mut buf) {
+ Ok(0) => break Disconnect::PeerDisconnected,
+ Ok(len) => {
+ let read_res = peer_manager.as_ref().read_event(&mut our_descriptor, &buf[0..len]);
+ let mut us_lock = us.lock().unwrap();
+ match read_res {
+ Ok(pause_read) => {
+ if pause_read {
+ us_lock.read_paused = true;
+ }
+ },
+ Err(_) => break Disconnect::CloseConnection,
+ }
+ },
+ Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
+ // readable() is allowed to spuriously wake, so we have to handle
+ // WouldBlock here.
+ },
+ Err(_) => break Disconnect::PeerDisconnected,
+ }
},
}
let _ = event_waker.try_send(());
// here.
let _ = tokio::task::yield_now().await;
};
- let writer_option = us.lock().unwrap().writer.take();
- if let Some(mut writer) = writer_option {
- // If the socket is already closed, shutdown() will fail, so just ignore it.
- let _ = writer.shutdown().await;
- }
+ us.lock().unwrap().writer.take();
if let Disconnect::PeerDisconnected = disconnect_type {
peer_manager.as_ref().socket_disconnected(&our_descriptor);
peer_manager.as_ref().process_events();
}
}
- fn new(stream: StdTcpStream) -> (io::ReadHalf<TcpStream>, mpsc::Receiver<()>, mpsc::Receiver<()>, Arc<Mutex<Self>>) {
+ fn new(stream: StdTcpStream) -> (Arc<TcpStream>, mpsc::Receiver<()>, mpsc::Receiver<()>, Arc<Mutex<Self>>) {
// We only ever need a channel of depth 1 here: if we returned a non-full write to the
// PeerManager, we will eventually get notified that there is room in the socket to write
// new bytes, which will generate an event. That event will be popped off the queue before
// false.
let (read_waker, read_receiver) = mpsc::channel(1);
stream.set_nonblocking(true).unwrap();
- let (reader, writer) = io::split(TcpStream::from_std(stream).unwrap());
+ let tokio_stream = Arc::new(TcpStream::from_std(stream).unwrap());
- (reader, write_receiver, read_receiver,
+ (Arc::clone(&tokio_stream), write_receiver, read_receiver,
Arc::new(Mutex::new(Self {
- writer: Some(writer), write_avail, read_waker, read_paused: false,
+ writer: Some(tokio_stream), write_avail, read_waker, read_paused: false,
rl_requested_disconnect: false,
id: ID_COUNTER.fetch_add(1, Ordering::AcqRel)
})))
}
}
-fn get_addr_from_stream(stream: &StdTcpStream) -> Option<NetAddress> {
+fn get_addr_from_stream(stream: &StdTcpStream) -> Option<SocketAddress> {
match stream.peer_addr() {
- Ok(SocketAddr::V4(sockaddr)) => Some(NetAddress::IPv4 {
+ Ok(SocketAddr::V4(sockaddr)) => Some(SocketAddress::TcpIpV4 {
addr: sockaddr.ip().octets(),
port: sockaddr.port(),
}),
- Ok(SocketAddr::V6(sockaddr)) => Some(NetAddress::IPv6 {
+ Ok(SocketAddr::V6(sockaddr)) => Some(SocketAddress::TcpIpV6 {
addr: sockaddr.ip().octets(),
port: sockaddr.port(),
}),
}
impl peer_handler::SocketDescriptor for SocketDescriptor {
fn send_data(&mut self, data: &[u8], resume_read: bool) -> usize {
- // To send data, we take a lock on our Connection to access the WriteHalf of the TcpStream,
- // writing to it if there's room in the kernel buffer, or otherwise create a new Waker with
- // a SocketDescriptor in it which can wake up the write_avail Sender, waking up the
+ // To send data, we take a lock on our Connection to access the TcpStream, writing to it if
+ // there's room in the kernel buffer, or otherwise create a new Waker with a
+ // SocketDescriptor in it which can wake up the write_avail Sender, waking up the
// processing future which will call write_buffer_space_avail and we'll end up back here.
let mut us = self.conn.lock().unwrap();
if us.writer.is_none() {
let mut ctx = task::Context::from_waker(&waker);
let mut written_len = 0;
loop {
- match std::pin::Pin::new(us.writer.as_mut().unwrap()).poll_write(&mut ctx, &data[written_len..]) {
- task::Poll::Ready(Ok(res)) => {
- // The tokio docs *seem* to indicate this can't happen, and I certainly don't
- // know how to handle it if it does (cause it should be a Poll::Pending
- // instead):
- assert_ne!(res, 0);
- written_len += res;
- if written_len == data.len() { return written_len; }
- },
- task::Poll::Ready(Err(e)) => {
- // The tokio docs *seem* to indicate this can't happen, and I certainly don't
- // know how to handle it if it does (cause it should be a Poll::Pending
- // instead):
- assert_ne!(e.kind(), io::ErrorKind::WouldBlock);
- // Probably we've already been closed, just return what we have and let the
- // read thread handle closing logic.
- return written_len;
+ match us.writer.as_ref().unwrap().poll_write_ready(&mut ctx) {
+ task::Poll::Ready(Ok(())) => {
+ match us.writer.as_ref().unwrap().try_write(&data[written_len..]) {
+ Ok(res) => {
+ debug_assert_ne!(res, 0);
+ written_len += res;
+ if written_len == data.len() { return written_len; }
+ },
+ Err(_) => return written_len,
+ }
},
+ task::Poll::Ready(Err(_)) => return written_len,
task::Poll::Pending => {
// We're queued up for a write event now, but we need to make sure we also
// pause read given we're now waiting on the remote end to ACK (and in
[package]
name = "lightning-persister"
-version = "0.0.116-alpha1"
+version = "0.0.117-alpha2"
authors = ["Valentine Wallace", "Matt Corallo"]
license = "MIT OR Apache-2.0"
-repository = "https://github.com/lightningdevkit/rust-lightning/"
+repository = "https://github.com/lightningdevkit/rust-lightning"
description = """
-Utilities to manage Rust-Lightning channel data persistence and retrieval.
+Utilities for LDK data persistence and retrieval.
"""
edition = "2018"
[dependencies]
bitcoin = "0.29.0"
-lightning = { version = "0.0.116-alpha1", path = "../lightning" }
-libc = "0.2"
+lightning = { version = "0.0.117-alpha2", path = "../lightning" }
[target.'cfg(windows)'.dependencies]
-winapi = { version = "0.3", features = ["winbase"] }
+windows-sys = { version = "0.48.0", default-features = false, features = ["Win32_Storage_FileSystem", "Win32_Foundation"] }
[target.'cfg(ldk_bench)'.dependencies]
criterion = { version = "0.4", optional = true, default-features = false }
[dev-dependencies]
-lightning = { version = "0.0.116-alpha1", path = "../lightning", features = ["_test_utils"] }
+lightning = { version = "0.0.117-alpha2", path = "../lightning", features = ["_test_utils"] }
+bitcoin = { version = "0.29.0", default-features = false }
--- /dev/null
+//! Objects related to [`FilesystemStore`] live here.
+use crate::utils::{check_namespace_key_validity, is_valid_kvstore_str};
+
+use lightning::util::persist::KVStore;
+use lightning::util::string::PrintableString;
+
+use std::collections::HashMap;
+use std::fs;
+use std::io::{Read, Write};
+use std::path::{Path, PathBuf};
+use std::sync::atomic::{AtomicUsize, Ordering};
+use std::sync::{Arc, Mutex, RwLock};
+
+#[cfg(target_os = "windows")]
+use {std::ffi::OsStr, std::os::windows::ffi::OsStrExt};
+
+#[cfg(target_os = "windows")]
+macro_rules! call {
+ ($e: expr) => {
+ if $e != 0 {
+ Ok(())
+ } else {
+ Err(std::io::Error::last_os_error())
+ }
+ };
+}
+
+#[cfg(target_os = "windows")]
+fn path_to_windows_str<T: AsRef<OsStr>>(path: &T) -> Vec<u16> {
+ path.as_ref().encode_wide().chain(Some(0)).collect()
+}
+
+// The number of read/write/remove/list operations after which we clean up our `locks` HashMap.
+const GC_LOCK_INTERVAL: usize = 25;
+
+/// A [`KVStore`] implementation that writes to and reads from the file system.
+pub struct FilesystemStore {
+ data_dir: PathBuf,
+ tmp_file_counter: AtomicUsize,
+ gc_counter: AtomicUsize,
+ locks: Mutex<HashMap<PathBuf, Arc<RwLock<()>>>>,
+}
+
+impl FilesystemStore {
+ /// Constructs a new [`FilesystemStore`].
+ pub fn new(data_dir: PathBuf) -> Self {
+ let locks = Mutex::new(HashMap::new());
+ let tmp_file_counter = AtomicUsize::new(0);
+ let gc_counter = AtomicUsize::new(1);
+ Self { data_dir, tmp_file_counter, gc_counter, locks }
+ }
+
+ /// Returns the data directory.
+ pub fn get_data_dir(&self) -> PathBuf {
+ self.data_dir.clone()
+ }
+
+ fn garbage_collect_locks(&self) {
+ let gc_counter = self.gc_counter.fetch_add(1, Ordering::AcqRel);
+
+ if gc_counter % GC_LOCK_INTERVAL == 0 {
+ // Take outer lock for the cleanup.
+ let mut outer_lock = self.locks.lock().unwrap();
+
+ // Garbage collect all lock entries that are not referenced anymore.
+ outer_lock.retain(|_, v| Arc::strong_count(&v) > 1);
+ }
+ }
+
+ fn get_dest_dir_path(&self, namespace: &str, sub_namespace: &str) -> std::io::Result<PathBuf> {
+ let mut dest_dir_path = {
+ #[cfg(target_os = "windows")]
+ {
+ let data_dir = self.data_dir.clone();
+ fs::create_dir_all(data_dir.clone())?;
+ fs::canonicalize(data_dir)?
+ }
+ #[cfg(not(target_os = "windows"))]
+ {
+ self.data_dir.clone()
+ }
+ };
+
+ dest_dir_path.push(namespace);
+ if !sub_namespace.is_empty() {
+ dest_dir_path.push(sub_namespace);
+ }
+
+ Ok(dest_dir_path)
+ }
+}
+
+impl KVStore for FilesystemStore {
+ fn read(&self, namespace: &str, sub_namespace: &str, key: &str) -> std::io::Result<Vec<u8>> {
+ check_namespace_key_validity(namespace, sub_namespace, Some(key), "read")?;
+
+ let mut dest_file_path = self.get_dest_dir_path(namespace, sub_namespace)?;
+ dest_file_path.push(key);
+
+ let mut buf = Vec::new();
+ {
+ let inner_lock_ref = {
+ let mut outer_lock = self.locks.lock().unwrap();
+ Arc::clone(&outer_lock.entry(dest_file_path.clone()).or_default())
+ };
+ let _guard = inner_lock_ref.read().unwrap();
+
+ let mut f = fs::File::open(dest_file_path)?;
+ f.read_to_end(&mut buf)?;
+ }
+
+ self.garbage_collect_locks();
+
+ Ok(buf)
+ }
+
+ fn write(&self, namespace: &str, sub_namespace: &str, key: &str, buf: &[u8]) -> std::io::Result<()> {
+ check_namespace_key_validity(namespace, sub_namespace, Some(key), "write")?;
+
+ let mut dest_file_path = self.get_dest_dir_path(namespace, sub_namespace)?;
+ dest_file_path.push(key);
+
+ let parent_directory = dest_file_path
+ .parent()
+ .ok_or_else(|| {
+ let msg =
+ format!("Could not retrieve parent directory of {}.", dest_file_path.display());
+ std::io::Error::new(std::io::ErrorKind::InvalidInput, msg)
+ })?;
+ fs::create_dir_all(&parent_directory)?;
+
+ // Do a crazy dance with lots of fsync()s to be overly cautious here...
+ // We never want to end up in a state where we've lost the old data, or end up using the
+ // old data on power loss after we've returned.
+ // The way to atomically write a file on Unix platforms is:
+ // open(tmpname), write(tmpfile), fsync(tmpfile), close(tmpfile), rename(), fsync(dir)
+ let mut tmp_file_path = dest_file_path.clone();
+ let tmp_file_ext = format!("{}.tmp", self.tmp_file_counter.fetch_add(1, Ordering::AcqRel));
+ tmp_file_path.set_extension(tmp_file_ext);
+
+ {
+ let mut tmp_file = fs::File::create(&tmp_file_path)?;
+ tmp_file.write_all(&buf)?;
+ tmp_file.sync_all()?;
+ }
+
+ let res = {
+ let inner_lock_ref = {
+ let mut outer_lock = self.locks.lock().unwrap();
+ Arc::clone(&outer_lock.entry(dest_file_path.clone()).or_default())
+ };
+ let _guard = inner_lock_ref.write().unwrap();
+
+ #[cfg(not(target_os = "windows"))]
+ {
+ fs::rename(&tmp_file_path, &dest_file_path)?;
+ let dir_file = fs::OpenOptions::new().read(true).open(&parent_directory)?;
+ dir_file.sync_all()?;
+ Ok(())
+ }
+
+ #[cfg(target_os = "windows")]
+ {
+ let res = if dest_file_path.exists() {
+ call!(unsafe {
+ windows_sys::Win32::Storage::FileSystem::ReplaceFileW(
+ path_to_windows_str(&dest_file_path).as_ptr(),
+ path_to_windows_str(&tmp_file_path).as_ptr(),
+ std::ptr::null(),
+ windows_sys::Win32::Storage::FileSystem::REPLACEFILE_IGNORE_MERGE_ERRORS,
+ std::ptr::null_mut() as *const core::ffi::c_void,
+ std::ptr::null_mut() as *const core::ffi::c_void,
+ )
+ })
+ } else {
+ call!(unsafe {
+ windows_sys::Win32::Storage::FileSystem::MoveFileExW(
+ path_to_windows_str(&tmp_file_path).as_ptr(),
+ path_to_windows_str(&dest_file_path).as_ptr(),
+ windows_sys::Win32::Storage::FileSystem::MOVEFILE_WRITE_THROUGH
+ | windows_sys::Win32::Storage::FileSystem::MOVEFILE_REPLACE_EXISTING,
+ )
+ })
+ };
+
+ match res {
+ Ok(()) => {
+ // We fsync the dest file in hopes this will also flush the metadata to disk.
+ let dest_file = fs::OpenOptions::new().read(true).write(true)
+ .open(&dest_file_path)?;
+ dest_file.sync_all()?;
+ Ok(())
+ }
+ Err(e) => Err(e),
+ }
+ }
+ };
+
+ self.garbage_collect_locks();
+
+ res
+ }
+
+ fn remove(&self, namespace: &str, sub_namespace: &str, key: &str, lazy: bool) -> std::io::Result<()> {
+ check_namespace_key_validity(namespace, sub_namespace, Some(key), "remove")?;
+
+ let mut dest_file_path = self.get_dest_dir_path(namespace, sub_namespace)?;
+ dest_file_path.push(key);
+
+ if !dest_file_path.is_file() {
+ return Ok(());
+ }
+
+ {
+ let inner_lock_ref = {
+ let mut outer_lock = self.locks.lock().unwrap();
+ Arc::clone(&outer_lock.entry(dest_file_path.clone()).or_default())
+ };
+ let _guard = inner_lock_ref.write().unwrap();
+
+ if lazy {
+ // If we're lazy we just call remove and be done with it.
+ fs::remove_file(&dest_file_path)?;
+ } else {
+ // If we're not lazy we try our best to persist the updated metadata to ensure
+ // atomicity of this call.
+ #[cfg(not(target_os = "windows"))]
+ {
+ fs::remove_file(&dest_file_path)?;
+
+ let parent_directory = dest_file_path.parent().ok_or_else(|| {
+ let msg =
+ format!("Could not retrieve parent directory of {}.", dest_file_path.display());
+ std::io::Error::new(std::io::ErrorKind::InvalidInput, msg)
+ })?;
+ let dir_file = fs::OpenOptions::new().read(true).open(parent_directory)?;
+ // The above call to `fs::remove_file` corresponds to POSIX `unlink`, whose changes
+ // to the inode might get cached (and hence possibly lost on crash), depending on
+ // the target platform and file system.
+ //
+ // In order to assert we permanently removed the file in question we therefore
+ // call `fsync` on the parent directory on platforms that support it.
+ dir_file.sync_all()?;
+ }
+
+ #[cfg(target_os = "windows")]
+ {
+ // Since Windows `DeleteFile` API is not persisted until the last open file handle
+ // is dropped, and there seemingly is no reliable way to flush the directory
+ // metadata, we here fall back to use a 'recycling bin' model, i.e., first move the
+ // file to be deleted to a temporary trash file and remove the latter file
+ // afterwards.
+ //
+ // This should be marginally better, as, according to the documentation,
+ // `MoveFileExW` APIs should offer stronger persistence guarantees,
+ // at least if `MOVEFILE_WRITE_THROUGH`/`MOVEFILE_REPLACE_EXISTING` is set.
+ // However, all this is partially based on assumptions and local experiments, as
+ // Windows API is horribly underdocumented.
+ let mut trash_file_path = dest_file_path.clone();
+ let trash_file_ext = format!("{}.trash",
+ self.tmp_file_counter.fetch_add(1, Ordering::AcqRel));
+ trash_file_path.set_extension(trash_file_ext);
+
+ call!(unsafe {
+ windows_sys::Win32::Storage::FileSystem::MoveFileExW(
+ path_to_windows_str(&dest_file_path).as_ptr(),
+ path_to_windows_str(&trash_file_path).as_ptr(),
+ windows_sys::Win32::Storage::FileSystem::MOVEFILE_WRITE_THROUGH
+ | windows_sys::Win32::Storage::FileSystem::MOVEFILE_REPLACE_EXISTING,
+ )
+ })?;
+
+ {
+ // We fsync the trash file in hopes this will also flush the original's file
+ // metadata to disk.
+ let trash_file = fs::OpenOptions::new().read(true).write(true)
+ .open(&trash_file_path.clone())?;
+ trash_file.sync_all()?;
+ }
+
+ // We're fine if this remove would fail as the trash file will be cleaned up in
+ // list eventually.
+ fs::remove_file(trash_file_path).ok();
+ }
+ }
+ }
+
+ self.garbage_collect_locks();
+
+ Ok(())
+ }
+
+ fn list(&self, namespace: &str, sub_namespace: &str) -> std::io::Result<Vec<String>> {
+ check_namespace_key_validity(namespace, sub_namespace, None, "list")?;
+
+ let prefixed_dest = self.get_dest_dir_path(namespace, sub_namespace)?;
+ let mut keys = Vec::new();
+
+ if !Path::new(&prefixed_dest).exists() {
+ return Ok(Vec::new());
+ }
+
+ for entry in fs::read_dir(&prefixed_dest)? {
+ let entry = entry?;
+ let p = entry.path();
+
+ if let Some(ext) = p.extension() {
+ #[cfg(target_os = "windows")]
+ {
+ // Clean up any trash files lying around.
+ if ext == "trash" {
+ fs::remove_file(p).ok();
+ continue;
+ }
+ }
+ if ext == "tmp" {
+ continue;
+ }
+ }
+
+ let metadata = p.metadata()?;
+
+ // We allow the presence of directories in the empty namespace and just skip them.
+ if metadata.is_dir() {
+ continue;
+ }
+
+ // If we otherwise don't find a file at the given path something went wrong.
+ if !metadata.is_file() {
+ debug_assert!(false, "Failed to list keys of {}/{}: file couldn't be accessed.",
+ PrintableString(namespace), PrintableString(sub_namespace));
+ let msg = format!("Failed to list keys of {}/{}: file couldn't be accessed.",
+ PrintableString(namespace), PrintableString(sub_namespace));
+ return Err(std::io::Error::new(std::io::ErrorKind::Other, msg));
+ }
+
+ match p.strip_prefix(&prefixed_dest) {
+ Ok(stripped_path) => {
+ if let Some(relative_path) = stripped_path.to_str() {
+ if is_valid_kvstore_str(relative_path) {
+ keys.push(relative_path.to_string())
+ }
+ } else {
+ debug_assert!(false, "Failed to list keys of {}/{}: file path is not valid UTF-8",
+ PrintableString(namespace), PrintableString(sub_namespace));
+ let msg = format!("Failed to list keys of {}/{}: file path is not valid UTF-8",
+ PrintableString(namespace), PrintableString(sub_namespace));
+ return Err(std::io::Error::new(std::io::ErrorKind::Other, msg));
+ }
+ }
+ Err(e) => {
+ debug_assert!(false, "Failed to list keys of {}/{}: {}",
+ PrintableString(namespace), PrintableString(sub_namespace), e);
+ let msg = format!("Failed to list keys of {}/{}: {}",
+ PrintableString(namespace), PrintableString(sub_namespace), e);
+ return Err(std::io::Error::new(std::io::ErrorKind::Other, msg));
+ }
+ }
+ }
+
+ self.garbage_collect_locks();
+
+ Ok(keys)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::test_utils::{do_read_write_remove_list_persist, do_test_store};
+
+ use bitcoin::hashes::hex::FromHex;
+ use bitcoin::Txid;
+
+ use lightning::chain::ChannelMonitorUpdateStatus;
+ use lightning::chain::chainmonitor::Persist;
+ use lightning::chain::transaction::OutPoint;
+ use lightning::check_closed_event;
+ use lightning::events::{ClosureReason, MessageSendEventsProvider};
+ use lightning::ln::functional_test_utils::*;
+ use lightning::util::test_utils;
+ use lightning::util::persist::read_channel_monitors;
+ use std::fs;
+ #[cfg(target_os = "windows")]
+ use {
+ lightning::get_event_msg,
+ lightning::ln::msgs::ChannelMessageHandler,
+ };
+
+ impl Drop for FilesystemStore {
+ fn drop(&mut self) {
+ // We test for invalid directory names, so it's OK if directory removal
+ // fails.
+ match fs::remove_dir_all(&self.data_dir) {
+ Err(e) => println!("Failed to remove test persister directory: {}", e),
+ _ => {}
+ }
+ }
+ }
+
+ #[test]
+ fn read_write_remove_list_persist() {
+ let mut temp_path = std::env::temp_dir();
+ temp_path.push("test_read_write_remove_list_persist");
+ let fs_store = FilesystemStore::new(temp_path);
+ do_read_write_remove_list_persist(&fs_store);
+ }
+
+ #[test]
+ fn test_if_monitors_is_not_dir() {
+ let store = FilesystemStore::new("test_monitors_is_not_dir".into());
+
+ fs::create_dir_all(&store.get_data_dir()).unwrap();
+ let mut path = std::path::PathBuf::from(&store.get_data_dir());
+ path.push("monitors");
+ fs::File::create(path).unwrap();
+
+ let chanmon_cfgs = create_chanmon_cfgs(1);
+ let mut node_cfgs = create_node_cfgs(1, &chanmon_cfgs);
+ let chain_mon_0 = test_utils::TestChainMonitor::new(Some(&chanmon_cfgs[0].chain_source), &chanmon_cfgs[0].tx_broadcaster, &chanmon_cfgs[0].logger, &chanmon_cfgs[0].fee_estimator, &store, node_cfgs[0].keys_manager);
+ node_cfgs[0].chain_monitor = chain_mon_0;
+ let node_chanmgrs = create_node_chanmgrs(1, &node_cfgs, &[None]);
+ let nodes = create_network(1, &node_cfgs, &node_chanmgrs);
+
+ // Check that read_channel_monitors() returns error if monitors/ is not a
+ // directory.
+ assert!(read_channel_monitors(&store, nodes[0].keys_manager, nodes[0].keys_manager).is_err());
+ }
+
+ #[test]
+ fn test_filesystem_store() {
+ // Create the nodes, giving them FilesystemStores for data stores.
+ let store_0 = FilesystemStore::new("test_filesystem_store_0".into());
+ let store_1 = FilesystemStore::new("test_filesystem_store_1".into());
+ do_test_store(&store_0, &store_1)
+ }
+
+ // Test that if the store's path to channel data is read-only, writing a
+ // monitor to it results in the store returning an InProgress.
+ // Windows ignores the read-only flag for folders, so this test is Unix-only.
+ #[cfg(not(target_os = "windows"))]
+ #[test]
+ fn test_readonly_dir_perm_failure() {
+ let store = FilesystemStore::new("test_readonly_dir_perm_failure".into());
+ fs::create_dir_all(&store.get_data_dir()).unwrap();
+
+ // Set up a dummy channel and force close. This will produce a monitor
+ // that we can then use to test persistence.
+ let chanmon_cfgs = create_chanmon_cfgs(2);
+ let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
+ let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
+ let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+ let chan = create_announced_chan_between_nodes(&nodes, 0, 1);
+ nodes[1].node.force_close_broadcasting_latest_txn(&chan.2, &nodes[0].node.get_our_node_id()).unwrap();
+ check_closed_event!(nodes[1], 1, ClosureReason::HolderForceClosed, [nodes[0].node.get_our_node_id()], 100000);
+ let mut added_monitors = nodes[1].chain_monitor.added_monitors.lock().unwrap();
+ let update_map = nodes[1].chain_monitor.latest_monitor_update_id.lock().unwrap();
+ let update_id = update_map.get(&added_monitors[0].0.to_channel_id()).unwrap();
+
+ // Set the store's directory to read-only, which should result in
+ // returning a permanent failure when we then attempt to persist a
+ // channel update.
+ let path = &store.get_data_dir();
+ let mut perms = fs::metadata(path).unwrap().permissions();
+ perms.set_readonly(true);
+ fs::set_permissions(path, perms).unwrap();
+
+ let test_txo = OutPoint {
+ txid: Txid::from_hex("8984484a580b825b9972d7adb15050b3ab624ccd731946b3eeddb92f4e7ef6be").unwrap(),
+ index: 0
+ };
+ match store.persist_new_channel(test_txo, &added_monitors[0].1, update_id.2) {
+ ChannelMonitorUpdateStatus::UnrecoverableError => {},
+ _ => panic!("unexpected result from persisting new channel")
+ }
+
+ nodes[1].node.get_and_clear_pending_msg_events();
+ added_monitors.clear();
+ }
+
+ // Test that if a store's directory name is invalid, monitor persistence
+ // will fail.
+ #[cfg(target_os = "windows")]
+ #[test]
+ fn test_fail_on_open() {
+ // Set up a dummy channel and force close. This will produce a monitor
+ // that we can then use to test persistence.
+ let chanmon_cfgs = create_chanmon_cfgs(2);
+ let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
+ let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
+ let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+ let chan = create_announced_chan_between_nodes(&nodes, 0, 1);
+ nodes[1].node.force_close_broadcasting_latest_txn(&chan.2, &nodes[0].node.get_our_node_id()).unwrap();
+ check_closed_event!(nodes[1], 1, ClosureReason::HolderForceClosed, [nodes[0].node.get_our_node_id()], 100000);
+ let mut added_monitors = nodes[1].chain_monitor.added_monitors.lock().unwrap();
+ let update_map = nodes[1].chain_monitor.latest_monitor_update_id.lock().unwrap();
+ let update_id = update_map.get(&added_monitors[0].0.to_channel_id()).unwrap();
+
+ // Create the store with an invalid directory name and test that the
+ // channel fails to open because the directories fail to be created. There
+ // don't seem to be invalid filename characters on Unix that Rust doesn't
+ // handle, hence why the test is Windows-only.
+ let store = FilesystemStore::new(":<>/".into());
+
+ let test_txo = OutPoint {
+ txid: Txid::from_hex("8984484a580b825b9972d7adb15050b3ab624ccd731946b3eeddb92f4e7ef6be").unwrap(),
+ index: 0
+ };
+ match store.persist_new_channel(test_txo, &added_monitors[0].1, update_id.2) {
+ ChannelMonitorUpdateStatus::UnrecoverableError => {},
+ _ => panic!("unexpected result from persisting new channel")
+ }
+
+ nodes[1].node.get_and_clear_pending_msg_events();
+ added_monitors.clear();
+ }
+}
+
+#[cfg(ldk_bench)]
+/// Benches
+pub mod bench {
+ use criterion::Criterion;
+
+ /// Bench!
+ pub fn bench_sends(bench: &mut Criterion) {
+ let store_a = super::FilesystemStore::new("bench_filesystem_store_a".into());
+ let store_b = super::FilesystemStore::new("bench_filesystem_store_b".into());
+ lightning::ln::channelmanager::bench::bench_two_sends(
+ bench, "bench_filesystem_persisted_sends", store_a, store_b);
+ }
+}
-//! Utilities that handle persisting Rust-Lightning data to disk via standard filesystem APIs.
-
-// Prefix these with `rustdoc::` when we update our MSRV to be >= 1.52 to remove warnings.
+//! Provides utilities for LDK data persistence and retrieval.
+//
+// TODO: Prefix these with `rustdoc::` when we update our MSRV to be >= 1.52 to remove warnings.
#![deny(broken_intra_doc_links)]
#![deny(private_intra_doc_links)]
#[cfg(ldk_bench)] extern crate criterion;
-mod util;
-
-extern crate lightning;
-extern crate bitcoin;
-extern crate libc;
-
-use bitcoin::hash_types::{BlockHash, Txid};
-use bitcoin::hashes::hex::FromHex;
-use lightning::chain::channelmonitor::ChannelMonitor;
-use lightning::sign::{EntropySource, SignerProvider};
-use lightning::util::ser::{ReadableArgs, Writeable};
-use lightning::util::persist::KVStorePersister;
-use std::fs;
-use std::io::Cursor;
-use std::ops::Deref;
-use std::path::{Path, PathBuf};
-
-/// FilesystemPersister persists channel data on disk, where each channel's
-/// data is stored in a file named after its funding outpoint.
-///
-/// Warning: this module does the best it can with calls to persist data, but it
-/// can only guarantee that the data is passed to the drive. It is up to the
-/// drive manufacturers to do the actual persistence properly, which they often
-/// don't (especially on consumer-grade hardware). Therefore, it is up to the
-/// user to validate their entire storage stack, to ensure the writes are
-/// persistent.
-/// Corollary: especially when dealing with larger amounts of money, it is best
-/// practice to have multiple channel data backups and not rely only on one
-/// FilesystemPersister.
-pub struct FilesystemPersister {
- path_to_channel_data: String,
-}
-
-impl FilesystemPersister {
- /// Initialize a new FilesystemPersister and set the path to the individual channels'
- /// files.
- pub fn new(path_to_channel_data: String) -> Self {
- Self {
- path_to_channel_data,
- }
- }
-
- /// Get the directory which was provided when this persister was initialized.
- pub fn get_data_dir(&self) -> String {
- self.path_to_channel_data.clone()
- }
-
- /// Read `ChannelMonitor`s from disk.
- pub fn read_channelmonitors<ES: Deref, SP: Deref> (
- &self, entropy_source: ES, signer_provider: SP
- ) -> std::io::Result<Vec<(BlockHash, ChannelMonitor<<SP::Target as SignerProvider>::Signer>)>>
- where
- ES::Target: EntropySource + Sized,
- SP::Target: SignerProvider + Sized
- {
- let mut path = PathBuf::from(&self.path_to_channel_data);
- path.push("monitors");
- if !Path::new(&path).exists() {
- return Ok(Vec::new());
- }
- let mut res = Vec::new();
- for file_option in fs::read_dir(path)? {
- let file = file_option.unwrap();
- let owned_file_name = file.file_name();
- let filename = owned_file_name.to_str()
- .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::InvalidData,
- "File name is not a valid utf8 string"))?;
- if !filename.is_ascii() || filename.len() < 65 {
- return Err(std::io::Error::new(
- std::io::ErrorKind::InvalidData,
- "Invalid ChannelMonitor file name",
- ));
- }
- if filename.ends_with(".tmp") {
- // If we were in the middle of committing an new update and crashed, it should be
- // safe to ignore the update - we should never have returned to the caller and
- // irrevocably committed to the new state in any way.
- continue;
- }
-
- let txid: Txid = Txid::from_hex(filename.split_at(64).0)
- .map_err(|_| std::io::Error::new(
- std::io::ErrorKind::InvalidData,
- "Invalid tx ID in filename",
- ))?;
-
- let index: u16 = filename.split_at(65).1.parse()
- .map_err(|_| std::io::Error::new(
- std::io::ErrorKind::InvalidData,
- "Invalid tx index in filename",
- ))?;
+pub mod fs_store;
- let contents = fs::read(&file.path())?;
- let mut buffer = Cursor::new(&contents);
- match <(BlockHash, ChannelMonitor<<SP::Target as SignerProvider>::Signer>)>::read(&mut buffer, (&*entropy_source, &*signer_provider)) {
- Ok((blockhash, channel_monitor)) => {
- if channel_monitor.get_funding_txo().0.txid != txid || channel_monitor.get_funding_txo().0.index != index {
- return Err(std::io::Error::new(std::io::ErrorKind::InvalidData,
- "ChannelMonitor was stored in the wrong file"));
- }
- res.push((blockhash, channel_monitor));
- }
- Err(e) => return Err(std::io::Error::new(
- std::io::ErrorKind::InvalidData,
- format!("Failed to deserialize ChannelMonitor: {}", e),
- ))
- }
- }
- Ok(res)
- }
-}
-
-impl KVStorePersister for FilesystemPersister {
- fn persist<W: Writeable>(&self, key: &str, object: &W) -> std::io::Result<()> {
- let mut dest_file = PathBuf::from(self.path_to_channel_data.clone());
- dest_file.push(key);
- util::write_to_file(dest_file, object)
- }
-}
+mod utils;
#[cfg(test)]
-mod tests {
- extern crate lightning;
- extern crate bitcoin;
- use crate::FilesystemPersister;
- use bitcoin::hashes::hex::FromHex;
- use bitcoin::Txid;
- use lightning::chain::ChannelMonitorUpdateStatus;
- use lightning::chain::chainmonitor::Persist;
- use lightning::chain::channelmonitor::CLOSED_CHANNEL_UPDATE_ID;
- use lightning::chain::transaction::OutPoint;
- use lightning::{check_closed_broadcast, check_closed_event, check_added_monitors};
- use lightning::events::{ClosureReason, MessageSendEventsProvider};
- use lightning::ln::functional_test_utils::*;
- use lightning::util::test_utils;
- use std::fs;
- #[cfg(target_os = "windows")]
- use {
- lightning::get_event_msg,
- lightning::ln::msgs::ChannelMessageHandler,
- };
-
- impl Drop for FilesystemPersister {
- fn drop(&mut self) {
- // We test for invalid directory names, so it's OK if directory removal
- // fails.
- match fs::remove_dir_all(&self.path_to_channel_data) {
- Err(e) => println!("Failed to remove test persister directory: {}", e),
- _ => {}
- }
- }
- }
-
- #[test]
- fn test_if_monitors_is_not_dir() {
- let persister = FilesystemPersister::new("test_monitors_is_not_dir".to_string());
-
- fs::create_dir_all(&persister.path_to_channel_data).unwrap();
- let mut path = std::path::PathBuf::from(&persister.path_to_channel_data);
- path.push("monitors");
- fs::File::create(path).unwrap();
-
- let chanmon_cfgs = create_chanmon_cfgs(1);
- let mut node_cfgs = create_node_cfgs(1, &chanmon_cfgs);
- let chain_mon_0 = test_utils::TestChainMonitor::new(Some(&chanmon_cfgs[0].chain_source), &chanmon_cfgs[0].tx_broadcaster, &chanmon_cfgs[0].logger, &chanmon_cfgs[0].fee_estimator, &persister, node_cfgs[0].keys_manager);
- node_cfgs[0].chain_monitor = chain_mon_0;
- let node_chanmgrs = create_node_chanmgrs(1, &node_cfgs, &[None]);
- let nodes = create_network(1, &node_cfgs, &node_chanmgrs);
-
- // Check that read_channelmonitors() returns error if monitors/ is not a
- // directory.
- assert!(persister.read_channelmonitors(nodes[0].keys_manager, nodes[0].keys_manager).is_err());
- }
-
- // Integration-test the FilesystemPersister. Test relaying a few payments
- // and check that the persisted data is updated the appropriate number of
- // times.
- #[test]
- fn test_filesystem_persister() {
- // Create the nodes, giving them FilesystemPersisters for data persisters.
- let persister_0 = FilesystemPersister::new("test_filesystem_persister_0".to_string());
- let persister_1 = FilesystemPersister::new("test_filesystem_persister_1".to_string());
- let chanmon_cfgs = create_chanmon_cfgs(2);
- let mut node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
- let chain_mon_0 = test_utils::TestChainMonitor::new(Some(&chanmon_cfgs[0].chain_source), &chanmon_cfgs[0].tx_broadcaster, &chanmon_cfgs[0].logger, &chanmon_cfgs[0].fee_estimator, &persister_0, node_cfgs[0].keys_manager);
- let chain_mon_1 = test_utils::TestChainMonitor::new(Some(&chanmon_cfgs[1].chain_source), &chanmon_cfgs[1].tx_broadcaster, &chanmon_cfgs[1].logger, &chanmon_cfgs[1].fee_estimator, &persister_1, node_cfgs[1].keys_manager);
- node_cfgs[0].chain_monitor = chain_mon_0;
- node_cfgs[1].chain_monitor = chain_mon_1;
- let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
- let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
-
- // Check that the persisted channel data is empty before any channels are
- // open.
- let mut persisted_chan_data_0 = persister_0.read_channelmonitors(nodes[0].keys_manager, nodes[0].keys_manager).unwrap();
- assert_eq!(persisted_chan_data_0.len(), 0);
- let mut persisted_chan_data_1 = persister_1.read_channelmonitors(nodes[1].keys_manager, nodes[1].keys_manager).unwrap();
- assert_eq!(persisted_chan_data_1.len(), 0);
-
- // Helper to make sure the channel is on the expected update ID.
- macro_rules! check_persisted_data {
- ($expected_update_id: expr) => {
- persisted_chan_data_0 = persister_0.read_channelmonitors(nodes[0].keys_manager, nodes[0].keys_manager).unwrap();
- assert_eq!(persisted_chan_data_0.len(), 1);
- for (_, mon) in persisted_chan_data_0.iter() {
- assert_eq!(mon.get_latest_update_id(), $expected_update_id);
- }
- persisted_chan_data_1 = persister_1.read_channelmonitors(nodes[1].keys_manager, nodes[1].keys_manager).unwrap();
- assert_eq!(persisted_chan_data_1.len(), 1);
- for (_, mon) in persisted_chan_data_1.iter() {
- assert_eq!(mon.get_latest_update_id(), $expected_update_id);
- }
- }
- }
-
- // Create some initial channel and check that a channel was persisted.
- let _ = create_announced_chan_between_nodes(&nodes, 0, 1);
- check_persisted_data!(0);
-
- // Send a few payments and make sure the monitors are updated to the latest.
- send_payment(&nodes[0], &vec!(&nodes[1])[..], 8000000);
- check_persisted_data!(5);
- send_payment(&nodes[1], &vec!(&nodes[0])[..], 4000000);
- check_persisted_data!(10);
-
- // Force close because cooperative close doesn't result in any persisted
- // updates.
- nodes[0].node.force_close_broadcasting_latest_txn(&nodes[0].node.list_channels()[0].channel_id, &nodes[1].node.get_our_node_id()).unwrap();
- check_closed_event!(nodes[0], 1, ClosureReason::HolderForceClosed);
- check_closed_broadcast!(nodes[0], true);
- check_added_monitors!(nodes[0], 1);
-
- let node_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap();
- assert_eq!(node_txn.len(), 1);
-
- connect_block(&nodes[1], &create_dummy_block(nodes[0].best_block_hash(), 42, vec![node_txn[0].clone(), node_txn[0].clone()]));
- check_closed_broadcast!(nodes[1], true);
- check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
- check_added_monitors!(nodes[1], 1);
-
- // Make sure everything is persisted as expected after close.
- check_persisted_data!(CLOSED_CHANNEL_UPDATE_ID);
- }
-
- // Test that if the persister's path to channel data is read-only, writing a
- // monitor to it results in the persister returning a PermanentFailure.
- // Windows ignores the read-only flag for folders, so this test is Unix-only.
- #[cfg(not(target_os = "windows"))]
- #[test]
- fn test_readonly_dir_perm_failure() {
- let persister = FilesystemPersister::new("test_readonly_dir_perm_failure".to_string());
- fs::create_dir_all(&persister.path_to_channel_data).unwrap();
-
- // Set up a dummy channel and force close. This will produce a monitor
- // that we can then use to test persistence.
- let chanmon_cfgs = create_chanmon_cfgs(2);
- let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
- let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
- let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
- let chan = create_announced_chan_between_nodes(&nodes, 0, 1);
- nodes[1].node.force_close_broadcasting_latest_txn(&chan.2, &nodes[0].node.get_our_node_id()).unwrap();
- check_closed_event!(nodes[1], 1, ClosureReason::HolderForceClosed);
- let mut added_monitors = nodes[1].chain_monitor.added_monitors.lock().unwrap();
- let update_map = nodes[1].chain_monitor.latest_monitor_update_id.lock().unwrap();
- let update_id = update_map.get(&added_monitors[0].0.to_channel_id()).unwrap();
-
- // Set the persister's directory to read-only, which should result in
- // returning a permanent failure when we then attempt to persist a
- // channel update.
- let path = &persister.path_to_channel_data;
- let mut perms = fs::metadata(path).unwrap().permissions();
- perms.set_readonly(true);
- fs::set_permissions(path, perms).unwrap();
-
- let test_txo = OutPoint {
- txid: Txid::from_hex("8984484a580b825b9972d7adb15050b3ab624ccd731946b3eeddb92f4e7ef6be").unwrap(),
- index: 0
- };
- match persister.persist_new_channel(test_txo, &added_monitors[0].1, update_id.2) {
- ChannelMonitorUpdateStatus::PermanentFailure => {},
- _ => panic!("unexpected result from persisting new channel")
- }
-
- nodes[1].node.get_and_clear_pending_msg_events();
- added_monitors.clear();
- }
-
- // Test that if a persister's directory name is invalid, monitor persistence
- // will fail.
- #[cfg(target_os = "windows")]
- #[test]
- fn test_fail_on_open() {
- // Set up a dummy channel and force close. This will produce a monitor
- // that we can then use to test persistence.
- let chanmon_cfgs = create_chanmon_cfgs(2);
- let mut node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
- let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
- let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
- let chan = create_announced_chan_between_nodes(&nodes, 0, 1);
- nodes[1].node.force_close_broadcasting_latest_txn(&chan.2, &nodes[0].node.get_our_node_id()).unwrap();
- check_closed_event!(nodes[1], 1, ClosureReason::HolderForceClosed);
- let mut added_monitors = nodes[1].chain_monitor.added_monitors.lock().unwrap();
- let update_map = nodes[1].chain_monitor.latest_monitor_update_id.lock().unwrap();
- let update_id = update_map.get(&added_monitors[0].0.to_channel_id()).unwrap();
-
- // Create the persister with an invalid directory name and test that the
- // channel fails to open because the directories fail to be created. There
- // don't seem to be invalid filename characters on Unix that Rust doesn't
- // handle, hence why the test is Windows-only.
- let persister = FilesystemPersister::new(":<>/".to_string());
-
- let test_txo = OutPoint {
- txid: Txid::from_hex("8984484a580b825b9972d7adb15050b3ab624ccd731946b3eeddb92f4e7ef6be").unwrap(),
- index: 0
- };
- match persister.persist_new_channel(test_txo, &added_monitors[0].1, update_id.2) {
- ChannelMonitorUpdateStatus::PermanentFailure => {},
- _ => panic!("unexpected result from persisting new channel")
- }
-
- nodes[1].node.get_and_clear_pending_msg_events();
- added_monitors.clear();
- }
-}
-
-#[cfg(ldk_bench)]
-/// Benches
-pub mod bench {
- use criterion::Criterion;
-
- /// Bench!
- pub fn bench_sends(bench: &mut Criterion) {
- let persister_a = super::FilesystemPersister::new("bench_filesystem_persister_a".to_string());
- let persister_b = super::FilesystemPersister::new("bench_filesystem_persister_b".to_string());
- lightning::ln::channelmanager::bench::bench_two_sends(
- bench, "bench_filesystem_persisted_sends", persister_a, persister_b);
- }
-}
+mod test_utils;
--- /dev/null
+use lightning::util::persist::{KVStore, KVSTORE_NAMESPACE_KEY_MAX_LEN, read_channel_monitors};
+use lightning::ln::functional_test_utils::{connect_block, create_announced_chan_between_nodes,
+ create_chanmon_cfgs, create_dummy_block, create_network, create_node_cfgs, create_node_chanmgrs,
+ send_payment};
+use lightning::chain::channelmonitor::CLOSED_CHANNEL_UPDATE_ID;
+use lightning::util::test_utils;
+use lightning::{check_closed_broadcast, check_closed_event, check_added_monitors};
+use lightning::events::ClosureReason;
+
+use std::panic::RefUnwindSafe;
+
+pub(crate) fn do_read_write_remove_list_persist<K: KVStore + RefUnwindSafe>(kv_store: &K) {
+ let data = [42u8; 32];
+
+ let namespace = "testspace";
+ let sub_namespace = "testsubspace";
+ let key = "testkey";
+
+ // Test the basic KVStore operations.
+ kv_store.write(namespace, sub_namespace, key, &data).unwrap();
+
+ // Test empty namespace/sub_namespace is allowed, but not empty namespace and non-empty
+ // sub-namespace, and not empty key.
+ kv_store.write("", "", key, &data).unwrap();
+ let res = std::panic::catch_unwind(|| kv_store.write("", sub_namespace, key, &data));
+ assert!(res.is_err());
+ let res = std::panic::catch_unwind(|| kv_store.write(namespace, sub_namespace, "", &data));
+ assert!(res.is_err());
+
+ let listed_keys = kv_store.list(namespace, sub_namespace).unwrap();
+ assert_eq!(listed_keys.len(), 1);
+ assert_eq!(listed_keys[0], key);
+
+ let read_data = kv_store.read(namespace, sub_namespace, key).unwrap();
+ assert_eq!(data, &*read_data);
+
+ kv_store.remove(namespace, sub_namespace, key, false).unwrap();
+
+ let listed_keys = kv_store.list(namespace, sub_namespace).unwrap();
+ assert_eq!(listed_keys.len(), 0);
+
+ // Ensure we have no issue operating with namespace/sub_namespace/key being KVSTORE_NAMESPACE_KEY_MAX_LEN
+ let max_chars: String = std::iter::repeat('A').take(KVSTORE_NAMESPACE_KEY_MAX_LEN).collect();
+ kv_store.write(&max_chars, &max_chars, &max_chars, &data).unwrap();
+
+ let listed_keys = kv_store.list(&max_chars, &max_chars).unwrap();
+ assert_eq!(listed_keys.len(), 1);
+ assert_eq!(listed_keys[0], max_chars);
+
+ let read_data = kv_store.read(&max_chars, &max_chars, &max_chars).unwrap();
+ assert_eq!(data, &*read_data);
+
+ kv_store.remove(&max_chars, &max_chars, &max_chars, false).unwrap();
+
+ let listed_keys = kv_store.list(&max_chars, &max_chars).unwrap();
+ assert_eq!(listed_keys.len(), 0);
+}
+
+// Integration-test the given KVStore implementation. Test relaying a few payments and check that
+// the persisted data is updated the appropriate number of times.
+pub(crate) fn do_test_store<K: KVStore>(store_0: &K, store_1: &K) {
+ let chanmon_cfgs = create_chanmon_cfgs(2);
+ let mut node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
+ let chain_mon_0 = test_utils::TestChainMonitor::new(Some(&chanmon_cfgs[0].chain_source), &chanmon_cfgs[0].tx_broadcaster, &chanmon_cfgs[0].logger, &chanmon_cfgs[0].fee_estimator, store_0, node_cfgs[0].keys_manager);
+ let chain_mon_1 = test_utils::TestChainMonitor::new(Some(&chanmon_cfgs[1].chain_source), &chanmon_cfgs[1].tx_broadcaster, &chanmon_cfgs[1].logger, &chanmon_cfgs[1].fee_estimator, store_1, node_cfgs[1].keys_manager);
+ node_cfgs[0].chain_monitor = chain_mon_0;
+ node_cfgs[1].chain_monitor = chain_mon_1;
+ let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
+ let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+
+ // Check that the persisted channel data is empty before any channels are
+ // open.
+ let mut persisted_chan_data_0 = read_channel_monitors(store_0, nodes[0].keys_manager, nodes[0].keys_manager).unwrap();
+ assert_eq!(persisted_chan_data_0.len(), 0);
+ let mut persisted_chan_data_1 = read_channel_monitors(store_1, nodes[1].keys_manager, nodes[1].keys_manager).unwrap();
+ assert_eq!(persisted_chan_data_1.len(), 0);
+
+ // Helper to make sure the channel is on the expected update ID.
+ macro_rules! check_persisted_data {
+ ($expected_update_id: expr) => {
+ persisted_chan_data_0 = read_channel_monitors(store_0, nodes[0].keys_manager, nodes[0].keys_manager).unwrap();
+ assert_eq!(persisted_chan_data_0.len(), 1);
+ for (_, mon) in persisted_chan_data_0.iter() {
+ assert_eq!(mon.get_latest_update_id(), $expected_update_id);
+ }
+ persisted_chan_data_1 = read_channel_monitors(store_1, nodes[1].keys_manager, nodes[1].keys_manager).unwrap();
+ assert_eq!(persisted_chan_data_1.len(), 1);
+ for (_, mon) in persisted_chan_data_1.iter() {
+ assert_eq!(mon.get_latest_update_id(), $expected_update_id);
+ }
+ }
+ }
+
+ // Create some initial channel and check that a channel was persisted.
+ let _ = create_announced_chan_between_nodes(&nodes, 0, 1);
+ check_persisted_data!(0);
+
+ // Send a few payments and make sure the monitors are updated to the latest.
+ send_payment(&nodes[0], &vec!(&nodes[1])[..], 8000000);
+ check_persisted_data!(5);
+ send_payment(&nodes[1], &vec!(&nodes[0])[..], 4000000);
+ check_persisted_data!(10);
+
+ // Force close because cooperative close doesn't result in any persisted
+ // updates.
+ nodes[0].node.force_close_broadcasting_latest_txn(&nodes[0].node.list_channels()[0].channel_id, &nodes[1].node.get_our_node_id()).unwrap();
+ check_closed_event!(nodes[0], 1, ClosureReason::HolderForceClosed, [nodes[1].node.get_our_node_id()], 100000);
+ check_closed_broadcast!(nodes[0], true);
+ check_added_monitors!(nodes[0], 1);
+
+ let node_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap();
+ assert_eq!(node_txn.len(), 1);
+
+ connect_block(&nodes[1], &create_dummy_block(nodes[0].best_block_hash(), 42, vec![node_txn[0].clone(), node_txn[0].clone()]));
+ check_closed_broadcast!(nodes[1], true);
+ check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [nodes[0].node.get_our_node_id()], 100000);
+ check_added_monitors!(nodes[1], 1);
+
+ // Make sure everything is persisted as expected after close.
+ check_persisted_data!(CLOSED_CHANNEL_UPDATE_ID);
+}
+++ /dev/null
-#[cfg(target_os = "windows")]
-extern crate winapi;
-
-use std::fs;
-use std::path::PathBuf;
-use std::io::BufWriter;
-
-#[cfg(not(target_os = "windows"))]
-use std::os::unix::io::AsRawFd;
-
-use lightning::util::ser::Writeable;
-
-#[cfg(target_os = "windows")]
-use {
- std::ffi::OsStr,
- std::os::windows::ffi::OsStrExt
-};
-
-#[cfg(target_os = "windows")]
-macro_rules! call {
- ($e: expr) => (
- if $e != 0 {
- return Ok(())
- } else {
- return Err(std::io::Error::last_os_error())
- }
- )
-}
-
-#[cfg(target_os = "windows")]
-fn path_to_windows_str<T: AsRef<OsStr>>(path: T) -> Vec<winapi::shared::ntdef::WCHAR> {
- path.as_ref().encode_wide().chain(Some(0)).collect()
-}
-
-#[allow(bare_trait_objects)]
-pub(crate) fn write_to_file<W: Writeable>(dest_file: PathBuf, data: &W) -> std::io::Result<()> {
- let mut tmp_file = dest_file.clone();
- tmp_file.set_extension("tmp");
-
- let parent_directory = dest_file.parent().unwrap();
- fs::create_dir_all(parent_directory)?;
- // Do a crazy dance with lots of fsync()s to be overly cautious here...
- // We never want to end up in a state where we've lost the old data, or end up using the
- // old data on power loss after we've returned.
- // The way to atomically write a file on Unix platforms is:
- // open(tmpname), write(tmpfile), fsync(tmpfile), close(tmpfile), rename(), fsync(dir)
- {
- // Note that going by rust-lang/rust@d602a6b, on MacOS it is only safe to use
- // rust stdlib 1.36 or higher.
- let mut buf = BufWriter::new(fs::File::create(&tmp_file)?);
- data.write(&mut buf)?;
- buf.into_inner()?.sync_all()?;
- }
- // Fsync the parent directory on Unix.
- #[cfg(not(target_os = "windows"))]
- {
- fs::rename(&tmp_file, &dest_file)?;
- let dir_file = fs::OpenOptions::new().read(true).open(parent_directory)?;
- unsafe { libc::fsync(dir_file.as_raw_fd()); }
- }
- #[cfg(target_os = "windows")]
- {
- if dest_file.exists() {
- unsafe {winapi::um::winbase::ReplaceFileW(
- path_to_windows_str(dest_file).as_ptr(), path_to_windows_str(tmp_file).as_ptr(), std::ptr::null(),
- winapi::um::winbase::REPLACEFILE_IGNORE_MERGE_ERRORS,
- std::ptr::null_mut() as *mut winapi::ctypes::c_void,
- std::ptr::null_mut() as *mut winapi::ctypes::c_void
- )};
- } else {
- call!(unsafe {winapi::um::winbase::MoveFileExW(
- path_to_windows_str(tmp_file).as_ptr(), path_to_windows_str(dest_file).as_ptr(),
- winapi::um::winbase::MOVEFILE_WRITE_THROUGH | winapi::um::winbase::MOVEFILE_REPLACE_EXISTING
- )});
- }
- }
- Ok(())
-}
-
-#[cfg(test)]
-mod tests {
- use lightning::util::ser::{Writer, Writeable};
-
- use super::{write_to_file};
- use std::fs;
- use std::io;
- use std::path::PathBuf;
-
- struct TestWriteable{}
- impl Writeable for TestWriteable {
- fn write<W: Writer>(&self, writer: &mut W) -> Result<(), std::io::Error> {
- writer.write_all(&[42; 1])
- }
- }
-
- // Test that if the persister's path to channel data is read-only, writing
- // data to it fails. Windows ignores the read-only flag for folders, so this
- // test is Unix-only.
- #[cfg(not(target_os = "windows"))]
- #[test]
- fn test_readonly_dir() {
- let test_writeable = TestWriteable{};
- let filename = "test_readonly_dir_persister_filename".to_string();
- let path = "test_readonly_dir_persister_dir";
- fs::create_dir_all(path).unwrap();
- let mut perms = fs::metadata(path).unwrap().permissions();
- perms.set_readonly(true);
- fs::set_permissions(path, perms).unwrap();
- let mut dest_file = PathBuf::from(path);
- dest_file.push(filename);
- match write_to_file(dest_file, &test_writeable) {
- Err(e) => assert_eq!(e.kind(), io::ErrorKind::PermissionDenied),
- _ => panic!("Unexpected error message")
- }
- }
-
- // Test failure to rename in the process of atomically creating a channel
- // monitor's file. We induce this failure by making the `tmp` file a
- // directory.
- // Explanation: given "from" = the file being renamed, "to" = the destination
- // file that already exists: Unix should fail because if "from" is a file,
- // then "to" is also required to be a file.
- // TODO: ideally try to make this work on Windows again
- #[cfg(not(target_os = "windows"))]
- #[test]
- fn test_rename_failure() {
- let test_writeable = TestWriteable{};
- let filename = "test_rename_failure_filename";
- let path = "test_rename_failure_dir";
- let mut dest_file = PathBuf::from(path);
- dest_file.push(filename);
- // Create the channel data file and make it a directory.
- fs::create_dir_all(dest_file.clone()).unwrap();
- match write_to_file(dest_file, &test_writeable) {
- Err(e) => assert_eq!(e.raw_os_error(), Some(libc::EISDIR)),
- _ => panic!("Unexpected Ok(())")
- }
- fs::remove_dir_all(path).unwrap();
- }
-
- #[test]
- fn test_diskwriteable_failure() {
- struct FailingWriteable {}
- impl Writeable for FailingWriteable {
- fn write<W: Writer>(&self, _writer: &mut W) -> Result<(), std::io::Error> {
- Err(std::io::Error::new(std::io::ErrorKind::Other, "expected failure"))
- }
- }
-
- let filename = "test_diskwriteable_failure";
- let path = "test_diskwriteable_failure_dir";
- let test_writeable = FailingWriteable{};
- let mut dest_file = PathBuf::from(path);
- dest_file.push(filename);
- match write_to_file(dest_file, &test_writeable) {
- Err(e) => {
- assert_eq!(e.kind(), std::io::ErrorKind::Other);
- assert_eq!(e.get_ref().unwrap().to_string(), "expected failure");
- },
- _ => panic!("unexpected result")
- }
- fs::remove_dir_all(path).unwrap();
- }
-
- // Test failure to create the temporary file in the persistence process.
- // We induce this failure by having the temp file already exist and be a
- // directory.
- #[test]
- fn test_tmp_file_creation_failure() {
- let test_writeable = TestWriteable{};
- let filename = "test_tmp_file_creation_failure_filename".to_string();
- let path = "test_tmp_file_creation_failure_dir";
- let mut dest_file = PathBuf::from(path);
- dest_file.push(filename);
- let mut tmp_file = dest_file.clone();
- tmp_file.set_extension("tmp");
- fs::create_dir_all(tmp_file).unwrap();
- match write_to_file(dest_file, &test_writeable) {
- Err(e) => {
- #[cfg(not(target_os = "windows"))]
- assert_eq!(e.raw_os_error(), Some(libc::EISDIR));
- #[cfg(target_os = "windows")]
- assert_eq!(e.kind(), io::ErrorKind::PermissionDenied);
- }
- _ => panic!("Unexpected error message")
- }
- }
-}
--- /dev/null
+use lightning::util::persist::{KVSTORE_NAMESPACE_KEY_ALPHABET, KVSTORE_NAMESPACE_KEY_MAX_LEN};
+use lightning::util::string::PrintableString;
+
+
+pub(crate) fn is_valid_kvstore_str(key: &str) -> bool {
+ key.len() <= KVSTORE_NAMESPACE_KEY_MAX_LEN && key.chars().all(|c| KVSTORE_NAMESPACE_KEY_ALPHABET.contains(c))
+}
+
+pub(crate) fn check_namespace_key_validity(namespace: &str, sub_namespace: &str, key: Option<&str>, operation: &str) -> Result<(), std::io::Error> {
+ if let Some(key) = key {
+ if key.is_empty() {
+ debug_assert!(false, "Failed to {} {}/{}/{}: key may not be empty.", operation,
+ PrintableString(namespace), PrintableString(sub_namespace), PrintableString(key));
+ let msg = format!("Failed to {} {}/{}/{}: key may not be empty.", operation,
+ PrintableString(namespace), PrintableString(sub_namespace), PrintableString(key));
+ return Err(std::io::Error::new(std::io::ErrorKind::Other, msg));
+ }
+
+ if namespace.is_empty() && !sub_namespace.is_empty() {
+ debug_assert!(false,
+ "Failed to {} {}/{}/{}: namespace may not be empty if a non-empty sub-namespace is given.",
+ operation,
+ PrintableString(namespace), PrintableString(sub_namespace), PrintableString(key));
+ let msg = format!(
+ "Failed to {} {}/{}/{}: namespace may not be empty if a non-empty sub-namespace is given.", operation,
+ PrintableString(namespace), PrintableString(sub_namespace), PrintableString(key));
+ return Err(std::io::Error::new(std::io::ErrorKind::Other, msg));
+ }
+
+ if !is_valid_kvstore_str(namespace) || !is_valid_kvstore_str(sub_namespace) || !is_valid_kvstore_str(key) {
+ debug_assert!(false, "Failed to {} {}/{}/{}: namespace, sub-namespace, and key must be valid.",
+ operation,
+ PrintableString(namespace), PrintableString(sub_namespace), PrintableString(key));
+ let msg = format!("Failed to {} {}/{}/{}: namespace, sub-namespace, and key must be valid.",
+ operation,
+ PrintableString(namespace), PrintableString(sub_namespace), PrintableString(key));
+ return Err(std::io::Error::new(std::io::ErrorKind::Other, msg));
+ }
+ } else {
+ if namespace.is_empty() && !sub_namespace.is_empty() {
+ debug_assert!(false,
+ "Failed to {} {}/{}: namespace may not be empty if a non-empty sub-namespace is given.",
+ operation, PrintableString(namespace), PrintableString(sub_namespace));
+ let msg = format!(
+ "Failed to {} {}/{}: namespace may not be empty if a non-empty sub-namespace is given.",
+ operation, PrintableString(namespace), PrintableString(sub_namespace));
+ return Err(std::io::Error::new(std::io::ErrorKind::Other, msg));
+ }
+ if !is_valid_kvstore_str(namespace) || !is_valid_kvstore_str(sub_namespace) {
+ debug_assert!(false, "Failed to {} {}/{}: namespace and sub-namespace must be valid.",
+ operation, PrintableString(namespace), PrintableString(sub_namespace));
+ let msg = format!("Failed to {} {}/{}: namespace and sub-namespace must be valid.",
+ operation, PrintableString(namespace), PrintableString(sub_namespace));
+ return Err(std::io::Error::new(std::io::ErrorKind::Other, msg));
+ }
+ }
+
+ Ok(())
+}
[package]
name = "lightning-rapid-gossip-sync"
-version = "0.0.116-alpha1"
+version = "0.0.117-alpha2"
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.116-alpha1", path = "../lightning", default-features = false }
+lightning = { version = "0.0.117-alpha2", 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.116-alpha1", path = "../lightning", features = ["_test_utils"] }
+lightning = { version = "0.0.117-alpha2", path = "../lightning", features = ["_test_utils"] }
[package]
name = "lightning-transaction-sync"
-version = "0.0.116-alpha1"
+version = "0.0.117-alpha2"
authors = ["Elias Rohrer"]
license = "MIT OR Apache-2.0"
-repository = "http://github.com/lightningdevkit/rust-lightning"
+repository = "https://github.com/lightningdevkit/rust-lightning"
description = """
Utilities for syncing LDK via the transaction-based `Confirm` interface.
"""
async-interface = []
[dependencies]
-lightning = { version = "0.0.116-alpha1", path = "../lightning", default-features = false }
+lightning = { version = "0.0.117-alpha2", 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.116-alpha1", path = "../lightning", features = ["std"] }
+lightning = { version = "0.0.117-alpha2", 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"] }
_ => panic!("Unexpected event"),
}
}
-
-#[tokio::test]
-#[cfg(any(feature = "esplora-async-https", feature = "esplora-blocking"))]
-async fn test_esplora_connects_to_public_server() {
- let mut logger = TestLogger {};
- let esplora_url = "https://blockstream.info/api".to_string();
- let tx_sync = EsploraSyncClient::new(esplora_url, &mut logger);
- let confirmable = TestConfirmable::new();
-
- // Check we connect and pick up on new best blocks
- assert_eq!(confirmable.best_block.lock().unwrap().1, 0);
- #[cfg(feature = "esplora-async-https")]
- tx_sync.sync(vec![&confirmable]).await.unwrap();
- #[cfg(feature = "esplora-blocking")]
- tx_sync.sync(vec![&confirmable]).unwrap();
- assert_ne!(confirmable.best_block.lock().unwrap().1, 0);
-}
[package]
name = "lightning"
-version = "0.0.116-alpha1"
+version = "0.0.117-alpha2"
authors = ["Matt Corallo"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning/"
--- /dev/null
+use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey};
+
+use crate::blinded_path::{BlindedHop, BlindedPath};
+use crate::blinded_path::utils;
+use crate::io;
+use crate::io::Cursor;
+use crate::ln::onion_utils;
+use crate::onion_message::ControlTlvs;
+use crate::prelude::*;
+use crate::sign::{NodeSigner, Recipient};
+use crate::util::chacha20poly1305rfc::ChaChaPolyReadAdapter;
+use crate::util::ser::{FixedLengthReader, LengthReadableArgs, Writeable, Writer};
+
+use core::mem;
+use core::ops::Deref;
+
+/// TLVs to encode in an intermediate onion message packet's hop data. When provided in a blinded
+/// route, they are encoded into [`BlindedHop::encrypted_payload`].
+pub(crate) struct ForwardTlvs {
+ /// The node id of the next hop in the onion message's path.
+ pub(crate) next_node_id: PublicKey,
+ /// Senders to a blinded path use this value to concatenate the route they find to the
+ /// introduction node with the blinded path.
+ pub(crate) next_blinding_override: Option<PublicKey>,
+}
+
+/// Similar to [`ForwardTlvs`], but these TLVs are for the final node.
+pub(crate) struct ReceiveTlvs {
+ /// If `path_id` is `Some`, it is used to identify the blinded path that this onion message is
+ /// sending to. This is useful for receivers to check that said blinded path is being used in
+ /// the right context.
+ pub(crate) path_id: Option<[u8; 32]>,
+}
+
+impl Writeable for ForwardTlvs {
+ fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
+ // TODO: write padding
+ encode_tlv_stream!(writer, {
+ (4, self.next_node_id, required),
+ (8, self.next_blinding_override, option)
+ });
+ Ok(())
+ }
+}
+
+impl Writeable for ReceiveTlvs {
+ fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
+ // TODO: write padding
+ encode_tlv_stream!(writer, {
+ (6, self.path_id, option),
+ });
+ Ok(())
+ }
+}
+
+/// Construct blinded onion message hops for the given `unblinded_path`.
+pub(super) fn blinded_hops<T: secp256k1::Signing + secp256k1::Verification>(
+ secp_ctx: &Secp256k1<T>, unblinded_path: &[PublicKey], session_priv: &SecretKey
+) -> Result<Vec<BlindedHop>, secp256k1::Error> {
+ let blinded_tlvs = unblinded_path.iter()
+ .skip(1) // The first node's TLVs contains the next node's pubkey
+ .map(|pk| {
+ ControlTlvs::Forward(ForwardTlvs { next_node_id: *pk, next_blinding_override: None })
+ })
+ .chain(core::iter::once(ControlTlvs::Receive(ReceiveTlvs { path_id: None })));
+
+ utils::construct_blinded_hops(secp_ctx, unblinded_path.iter(), blinded_tlvs, session_priv)
+}
+
+// Advance the blinded onion message path by one hop, so make the second hop into the new
+// introduction node.
+pub(crate) fn advance_path_by_one<NS: Deref, T: secp256k1::Signing + secp256k1::Verification>(
+ path: &mut BlindedPath, node_signer: &NS, secp_ctx: &Secp256k1<T>
+) -> Result<(), ()> where NS::Target: NodeSigner {
+ let control_tlvs_ss = node_signer.ecdh(Recipient::Node, &path.blinding_point, None)?;
+ let rho = onion_utils::gen_rho_from_shared_secret(&control_tlvs_ss.secret_bytes());
+ let encrypted_control_tlvs = path.blinded_hops.remove(0).encrypted_payload;
+ let mut s = Cursor::new(&encrypted_control_tlvs);
+ let mut reader = FixedLengthReader::new(&mut s, encrypted_control_tlvs.len() as u64);
+ match ChaChaPolyReadAdapter::read(&mut reader, rho) {
+ Ok(ChaChaPolyReadAdapter { readable: ControlTlvs::Forward(ForwardTlvs {
+ mut next_node_id, next_blinding_override,
+ })}) => {
+ let mut new_blinding_point = match next_blinding_override {
+ Some(blinding_point) => blinding_point,
+ None => {
+ onion_utils::next_hop_pubkey(secp_ctx, path.blinding_point,
+ control_tlvs_ss.as_ref()).map_err(|_| ())?
+ }
+ };
+ mem::swap(&mut path.blinding_point, &mut new_blinding_point);
+ mem::swap(&mut path.introduction_node_id, &mut next_node_id);
+ Ok(())
+ },
+ _ => Err(())
+ }
+}
//! Creating blinded paths and related utilities live here.
+pub mod payment;
+pub(crate) mod message;
pub(crate) mod utils;
-use bitcoin::hashes::{Hash, HashEngine};
-use bitcoin::hashes::sha256::Hash as Sha256;
-use bitcoin::secp256k1::{self, PublicKey, Scalar, Secp256k1, SecretKey};
+use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey};
-use crate::sign::{EntropySource, NodeSigner, Recipient};
-use crate::onion_message::ControlTlvs;
use crate::ln::msgs::DecodeError;
-use crate::ln::onion_utils;
-use crate::util::chacha20poly1305rfc::{ChaChaPolyReadAdapter, ChaChaPolyWriteAdapter};
-use crate::util::ser::{FixedLengthReader, LengthReadableArgs, Readable, VecWriter, Writeable, Writer};
+use crate::offers::invoice::BlindedPayInfo;
+use crate::sign::EntropySource;
+use crate::util::ser::{Readable, Writeable, Writer};
-use core::mem;
-use core::ops::Deref;
-use crate::io::{self, Cursor};
+use crate::io;
use crate::prelude::*;
/// Onion messages and payments can be sent and received to blinded paths, which serve to hide the
/// message or payment's next hop and forward it along.
///
/// [`encrypted_payload`]: BlindedHop::encrypted_payload
- pub(crate) introduction_node_id: PublicKey,
+ pub introduction_node_id: PublicKey,
/// Used by the introduction node to decrypt its [`encrypted_payload`] to forward the onion
/// message or payment.
///
/// [`encrypted_payload`]: BlindedHop::encrypted_payload
- pub(crate) blinding_point: PublicKey,
+ pub blinding_point: PublicKey,
/// The hops composing the blinded path.
- pub(crate) blinded_hops: Vec<BlindedHop>,
+ pub blinded_hops: Vec<BlindedHop>,
}
-/// Used to construct the blinded hops portion of a blinded path. These hops cannot be identified
-/// by outside observers and thus can be used to hide the identity of the recipient.
+/// An encrypted payload and node id corresponding to a hop in a payment or onion message path, to
+/// be encoded in the sender's onion packet. These hops cannot be identified by outside observers
+/// and thus can be used to hide the identity of the recipient.
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct BlindedHop {
- /// The blinded node id of this hop in a blinded path.
- pub(crate) blinded_node_id: PublicKey,
- /// The encrypted payload intended for this hop in a blinded path.
+ /// The blinded node id of this hop in a [`BlindedPath`].
+ pub blinded_node_id: PublicKey,
+ /// The encrypted payload intended for this hop in a [`BlindedPath`].
// The node sending to this blinded path will later encode this payload into the onion packet for
// this hop.
- pub(crate) encrypted_payload: Vec<u8>,
+ pub encrypted_payload: Vec<u8>,
}
impl BlindedPath {
Ok(BlindedPath {
introduction_node_id,
blinding_point: PublicKey::from_secret_key(secp_ctx, &blinding_secret),
- blinded_hops: blinded_message_hops(secp_ctx, node_pks, &blinding_secret).map_err(|_| ())?,
+ blinded_hops: message::blinded_hops(secp_ctx, node_pks, &blinding_secret).map_err(|_| ())?,
})
}
- // Advance the blinded onion message path by one hop, so make the second hop into the new
- // introduction node.
- pub(super) fn advance_message_path_by_one<NS: Deref, T: secp256k1::Signing + secp256k1::Verification>
- (&mut self, node_signer: &NS, secp_ctx: &Secp256k1<T>) -> Result<(), ()>
- where NS::Target: NodeSigner
- {
- let control_tlvs_ss = node_signer.ecdh(Recipient::Node, &self.blinding_point, None)?;
- let rho = onion_utils::gen_rho_from_shared_secret(&control_tlvs_ss.secret_bytes());
- let encrypted_control_tlvs = self.blinded_hops.remove(0).encrypted_payload;
- let mut s = Cursor::new(&encrypted_control_tlvs);
- let mut reader = FixedLengthReader::new(&mut s, encrypted_control_tlvs.len() as u64);
- match ChaChaPolyReadAdapter::read(&mut reader, rho) {
- Ok(ChaChaPolyReadAdapter { readable: ControlTlvs::Forward(ForwardTlvs {
- mut next_node_id, next_blinding_override,
- })}) => {
- let mut new_blinding_point = match next_blinding_override {
- Some(blinding_point) => blinding_point,
- None => {
- let blinding_factor = {
- let mut sha = Sha256::engine();
- sha.input(&self.blinding_point.serialize()[..]);
- sha.input(control_tlvs_ss.as_ref());
- Sha256::from_engine(sha).into_inner()
- };
- self.blinding_point.mul_tweak(secp_ctx, &Scalar::from_be_bytes(blinding_factor).unwrap())
- .map_err(|_| ())?
- }
- };
- mem::swap(&mut self.blinding_point, &mut new_blinding_point);
- mem::swap(&mut self.introduction_node_id, &mut next_node_id);
- Ok(())
- },
- _ => Err(())
- }
+ /// Create a one-hop blinded path for a payment.
+ pub fn one_hop_for_payment<ES: EntropySource, T: secp256k1::Signing + secp256k1::Verification>(
+ payee_node_id: PublicKey, payee_tlvs: payment::ReceiveTlvs, entropy_source: &ES,
+ secp_ctx: &Secp256k1<T>
+ ) -> Result<(BlindedPayInfo, Self), ()> {
+ // This value is not considered in pathfinding for 1-hop blinded paths, because it's intended to
+ // be in relation to a specific channel.
+ let htlc_maximum_msat = u64::max_value();
+ Self::new_for_payment(
+ &[], payee_node_id, payee_tlvs, htlc_maximum_msat, entropy_source, secp_ctx
+ )
}
-}
-/// Construct blinded onion message hops for the given `unblinded_path`.
-fn blinded_message_hops<T: secp256k1::Signing + secp256k1::Verification>(
- secp_ctx: &Secp256k1<T>, unblinded_path: &[PublicKey], session_priv: &SecretKey
-) -> Result<Vec<BlindedHop>, secp256k1::Error> {
- let mut blinded_hops = Vec::with_capacity(unblinded_path.len());
-
- let mut prev_ss_and_blinded_node_id = None;
- utils::construct_keys_callback(secp_ctx, unblinded_path, None, session_priv, |blinded_node_id, _, _, encrypted_payload_ss, unblinded_pk, _| {
- if let Some((prev_ss, prev_blinded_node_id)) = prev_ss_and_blinded_node_id {
- if let Some(pk) = unblinded_pk {
- let payload = ForwardTlvs {
- next_node_id: pk,
- next_blinding_override: None,
- };
- blinded_hops.push(BlindedHop {
- blinded_node_id: prev_blinded_node_id,
- encrypted_payload: encrypt_payload(payload, prev_ss),
- });
- } else { debug_assert!(false); }
- }
- prev_ss_and_blinded_node_id = Some((encrypted_payload_ss, blinded_node_id));
- })?;
-
- if let Some((final_ss, final_blinded_node_id)) = prev_ss_and_blinded_node_id {
- let final_payload = ReceiveTlvs { path_id: None };
- blinded_hops.push(BlindedHop {
- blinded_node_id: final_blinded_node_id,
- encrypted_payload: encrypt_payload(final_payload, final_ss),
- });
- } else { debug_assert!(false) }
-
- Ok(blinded_hops)
-}
+ /// Create a blinded path for a payment, to be forwarded along `intermediate_nodes`.
+ ///
+ /// Errors if:
+ /// * a provided node id is invalid
+ /// * [`BlindedPayInfo`] calculation results in an integer overflow
+ /// * any unknown features are required in the provided [`ForwardTlvs`]
+ ///
+ /// [`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>(
+ intermediate_nodes: &[payment::ForwardNode], payee_node_id: PublicKey,
+ payee_tlvs: payment::ReceiveTlvs, htlc_maximum_msat: u64, entropy_source: &ES,
+ secp_ctx: &Secp256k1<T>
+ ) -> Result<(BlindedPayInfo, Self), ()> {
+ let blinding_secret_bytes = entropy_source.get_secure_random_bytes();
+ let blinding_secret = SecretKey::from_slice(&blinding_secret_bytes[..]).expect("RNG is busted");
-/// Encrypt TLV payload to be used as a [`BlindedHop::encrypted_payload`].
-fn encrypt_payload<P: Writeable>(payload: P, encrypted_tlvs_ss: [u8; 32]) -> Vec<u8> {
- let mut writer = VecWriter(Vec::new());
- let write_adapter = ChaChaPolyWriteAdapter::new(encrypted_tlvs_ss, &payload);
- write_adapter.write(&mut writer).expect("In-memory writes cannot fail");
- writer.0
+ let blinded_payinfo = payment::compute_payinfo(intermediate_nodes, &payee_tlvs, htlc_maximum_msat)?;
+ Ok((blinded_payinfo, BlindedPath {
+ introduction_node_id: intermediate_nodes.first().map_or(payee_node_id, |n| n.node_id),
+ blinding_point: PublicKey::from_secret_key(secp_ctx, &blinding_secret),
+ blinded_hops: payment::blinded_hops(
+ secp_ctx, intermediate_nodes, payee_node_id, payee_tlvs, &blinding_secret
+ ).map_err(|_| ())?,
+ }))
+ }
}
impl Writeable for BlindedPath {
encrypted_payload
});
-/// TLVs to encode in an intermediate onion message packet's hop data. When provided in a blinded
-/// route, they are encoded into [`BlindedHop::encrypted_payload`].
-pub(crate) struct ForwardTlvs {
- /// The node id of the next hop in the onion message's path.
- pub(super) next_node_id: PublicKey,
- /// Senders to a blinded path use this value to concatenate the route they find to the
- /// introduction node with the blinded path.
- pub(super) next_blinding_override: Option<PublicKey>,
-}
-
-/// Similar to [`ForwardTlvs`], but these TLVs are for the final node.
-pub(crate) struct ReceiveTlvs {
- /// If `path_id` is `Some`, it is used to identify the blinded path that this onion message is
- /// sending to. This is useful for receivers to check that said blinded path is being used in
- /// the right context.
- pub(super) path_id: Option<[u8; 32]>,
-}
-
-impl Writeable for ForwardTlvs {
- fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
- // TODO: write padding
- encode_tlv_stream!(writer, {
- (4, self.next_node_id, required),
- (8, self.next_blinding_override, option)
- });
- Ok(())
- }
-}
-
-impl Writeable for ReceiveTlvs {
- fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
- // TODO: write padding
- encode_tlv_stream!(writer, {
- (6, self.path_id, option),
- });
- Ok(())
- }
-}
--- /dev/null
+//! Data structures and methods for constructing [`BlindedPath`]s to send a payment over.
+//!
+//! [`BlindedPath`]: crate::blinded_path::BlindedPath
+
+use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey};
+
+use crate::blinded_path::BlindedHop;
+use crate::blinded_path::utils;
+use crate::io;
+use crate::ln::PaymentSecret;
+use crate::ln::features::BlindedHopFeatures;
+use crate::ln::msgs::DecodeError;
+use crate::offers::invoice::BlindedPayInfo;
+use crate::prelude::*;
+use crate::util::ser::{Readable, Writeable, Writer};
+
+use core::convert::TryFrom;
+
+/// An intermediate node, its outbound channel, and relay parameters.
+#[derive(Clone, Debug)]
+pub struct ForwardNode {
+ /// The TLVs for this node's [`BlindedHop`], where the fee parameters contained within are also
+ /// used for [`BlindedPayInfo`] construction.
+ pub tlvs: ForwardTlvs,
+ /// This node's pubkey.
+ pub node_id: PublicKey,
+ /// The maximum value, in msat, that may be accepted by this node.
+ pub htlc_maximum_msat: u64,
+}
+
+/// Data to construct a [`BlindedHop`] for forwarding a payment.
+#[derive(Clone, Debug)]
+pub struct ForwardTlvs {
+ /// The short channel id this payment should be forwarded out over.
+ pub short_channel_id: u64,
+ /// Payment parameters for relaying over [`Self::short_channel_id`].
+ pub payment_relay: PaymentRelay,
+ /// Payment constraints for relaying over [`Self::short_channel_id`].
+ pub payment_constraints: PaymentConstraints,
+ /// Supported and required features when relaying a payment onion containing this object's
+ /// corresponding [`BlindedHop::encrypted_payload`].
+ ///
+ /// [`BlindedHop::encrypted_payload`]: crate::blinded_path::BlindedHop::encrypted_payload
+ pub features: BlindedHopFeatures,
+}
+
+/// Data to construct a [`BlindedHop`] for receiving a payment. This payload is custom to LDK and
+/// may not be valid if received by another lightning implementation.
+#[derive(Clone, Debug)]
+pub struct ReceiveTlvs {
+ /// Used to authenticate the sender of a payment to the receiver and tie MPP HTLCs together.
+ pub payment_secret: PaymentSecret,
+ /// Constraints for the receiver of this payment.
+ pub payment_constraints: PaymentConstraints,
+}
+
+/// Data to construct a [`BlindedHop`] for sending a payment over.
+///
+/// [`BlindedHop`]: crate::blinded_path::BlindedHop
+pub(crate) enum BlindedPaymentTlvs {
+ /// This blinded payment data is for a forwarding node.
+ Forward(ForwardTlvs),
+ /// This blinded payment data is for the receiving node.
+ Receive(ReceiveTlvs),
+}
+
+// Used to include forward and receive TLVs in the same iterator for encoding.
+enum BlindedPaymentTlvsRef<'a> {
+ Forward(&'a ForwardTlvs),
+ Receive(&'a ReceiveTlvs),
+}
+
+/// Parameters for relaying over a given [`BlindedHop`].
+///
+/// [`BlindedHop`]: crate::blinded_path::BlindedHop
+#[derive(Clone, Debug)]
+pub struct PaymentRelay {
+ /// Number of blocks subtracted from an incoming HTLC's `cltv_expiry` for this [`BlindedHop`].
+ pub cltv_expiry_delta: u16,
+ /// Liquidity fee charged (in millionths of the amount transferred) for relaying a payment over
+ /// this [`BlindedHop`], (i.e., 10,000 is 1%).
+ pub fee_proportional_millionths: u32,
+ /// Base fee charged (in millisatoshi) for relaying a payment over this [`BlindedHop`].
+ pub fee_base_msat: u32,
+}
+
+/// Constraints for relaying over a given [`BlindedHop`].
+///
+/// [`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`].
+ pub max_cltv_expiry: u32,
+ /// The minimum value, in msat, that may be accepted by the node corresponding to this
+ /// [`BlindedHop`].
+ pub htlc_minimum_msat: u64,
+}
+
+impl Writeable for ForwardTlvs {
+ fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
+ encode_tlv_stream!(w, {
+ (2, self.short_channel_id, required),
+ (10, self.payment_relay, required),
+ (12, self.payment_constraints, required),
+ (14, self.features, required)
+ });
+ Ok(())
+ }
+}
+
+impl Writeable for ReceiveTlvs {
+ fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
+ encode_tlv_stream!(w, {
+ (12, self.payment_constraints, required),
+ (65536, self.payment_secret, required)
+ });
+ Ok(())
+ }
+}
+
+// This will be removed once we support forwarding blinded HTLCs, because we'll always read a
+// `BlindedPaymentTlvs` instead.
+impl Readable for ReceiveTlvs {
+ fn read<R: io::Read>(r: &mut R) -> Result<Self, DecodeError> {
+ _init_and_read_tlv_stream!(r, {
+ (12, payment_constraints, required),
+ (65536, payment_secret, required),
+ });
+ Ok(Self {
+ payment_secret: payment_secret.0.unwrap(),
+ payment_constraints: payment_constraints.0.unwrap()
+ })
+ }
+}
+
+impl<'a> Writeable for BlindedPaymentTlvsRef<'a> {
+ fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
+ // TODO: write padding
+ match self {
+ Self::Forward(tlvs) => tlvs.write(w)?,
+ Self::Receive(tlvs) => tlvs.write(w)?,
+ }
+ Ok(())
+ }
+}
+
+impl Readable for BlindedPaymentTlvs {
+ fn read<R: io::Read>(r: &mut R) -> Result<Self, DecodeError> {
+ _init_and_read_tlv_stream!(r, {
+ (1, _padding, option),
+ (2, scid, option),
+ (10, payment_relay, option),
+ (12, payment_constraints, required),
+ (14, features, option),
+ (65536, payment_secret, option),
+ });
+ let _padding: Option<utils::Padding> = _padding;
+
+ if let Some(short_channel_id) = scid {
+ if payment_secret.is_some() { return Err(DecodeError::InvalidValue) }
+ Ok(BlindedPaymentTlvs::Forward(ForwardTlvs {
+ short_channel_id,
+ payment_relay: payment_relay.ok_or(DecodeError::InvalidValue)?,
+ payment_constraints: payment_constraints.0.unwrap(),
+ features: features.ok_or(DecodeError::InvalidValue)?,
+ }))
+ } else {
+ if payment_relay.is_some() || features.is_some() { return Err(DecodeError::InvalidValue) }
+ Ok(BlindedPaymentTlvs::Receive(ReceiveTlvs {
+ payment_secret: payment_secret.ok_or(DecodeError::InvalidValue)?,
+ payment_constraints: payment_constraints.0.unwrap(),
+ }))
+ }
+ }
+}
+
+/// Construct blinded payment hops for the given `intermediate_nodes` and payee info.
+pub(super) fn blinded_hops<T: secp256k1::Signing + secp256k1::Verification>(
+ secp_ctx: &Secp256k1<T>, intermediate_nodes: &[ForwardNode],
+ payee_node_id: PublicKey, payee_tlvs: ReceiveTlvs, session_priv: &SecretKey
+) -> Result<Vec<BlindedHop>, secp256k1::Error> {
+ let pks = intermediate_nodes.iter().map(|node| &node.node_id)
+ .chain(core::iter::once(&payee_node_id));
+ let tlvs = intermediate_nodes.iter().map(|node| BlindedPaymentTlvsRef::Forward(&node.tlvs))
+ .chain(core::iter::once(BlindedPaymentTlvsRef::Receive(&payee_tlvs)));
+ utils::construct_blinded_hops(secp_ctx, pks, tlvs, session_priv)
+}
+
+/// `None` if underflow occurs.
+fn amt_to_forward_msat(inbound_amt_msat: u64, payment_relay: &PaymentRelay) -> Option<u64> {
+ let inbound_amt = inbound_amt_msat as u128;
+ let base = payment_relay.fee_base_msat as u128;
+ let prop = payment_relay.fee_proportional_millionths as u128;
+
+ let post_base_fee_inbound_amt =
+ if let Some(amt) = inbound_amt.checked_sub(base) { amt } else { return None };
+ let mut amt_to_forward =
+ (post_base_fee_inbound_amt * 1_000_000 + 1_000_000 + prop - 1) / (prop + 1_000_000);
+
+ let fee = ((amt_to_forward * prop) / 1_000_000) + base;
+ if inbound_amt - fee < amt_to_forward {
+ // Rounding up the forwarded amount resulted in underpaying this node, so take an extra 1 msat
+ // in fee to compensate.
+ amt_to_forward -= 1;
+ }
+ debug_assert_eq!(amt_to_forward + fee, inbound_amt);
+ u64::try_from(amt_to_forward).ok()
+}
+
+pub(super) fn compute_payinfo(
+ intermediate_nodes: &[ForwardNode], payee_tlvs: &ReceiveTlvs, payee_htlc_maximum_msat: u64
+) -> Result<BlindedPayInfo, ()> {
+ let mut curr_base_fee: u64 = 0;
+ let mut curr_prop_mil: u64 = 0;
+ let mut cltv_expiry_delta: u16 = 0;
+ for tlvs in intermediate_nodes.iter().rev().map(|n| &n.tlvs) {
+ // In the future, we'll want to take the intersection of all supported features for the
+ // `BlindedPayInfo`, but there are no features in that context right now.
+ if tlvs.features.requires_unknown_bits_from(&BlindedHopFeatures::empty()) { return Err(()) }
+
+ let next_base_fee = tlvs.payment_relay.fee_base_msat as u64;
+ let next_prop_mil = tlvs.payment_relay.fee_proportional_millionths as u64;
+ // Use integer arithmetic to compute `ceil(a/b)` as `(a+b-1)/b`
+ // ((curr_base_fee * (1_000_000 + next_prop_mil)) / 1_000_000) + next_base_fee
+ curr_base_fee = curr_base_fee.checked_mul(1_000_000 + next_prop_mil)
+ .and_then(|f| f.checked_add(1_000_000 - 1))
+ .map(|f| f / 1_000_000)
+ .and_then(|f| f.checked_add(next_base_fee))
+ .ok_or(())?;
+ // ceil(((curr_prop_mil + 1_000_000) * (next_prop_mil + 1_000_000)) / 1_000_000) - 1_000_000
+ curr_prop_mil = curr_prop_mil.checked_add(1_000_000)
+ .and_then(|f1| next_prop_mil.checked_add(1_000_000).and_then(|f2| f2.checked_mul(f1)))
+ .and_then(|f| f.checked_add(1_000_000 - 1))
+ .map(|f| f / 1_000_000)
+ .and_then(|f| f.checked_sub(1_000_000))
+ .ok_or(())?;
+
+ cltv_expiry_delta = cltv_expiry_delta.checked_add(tlvs.payment_relay.cltv_expiry_delta).ok_or(())?;
+ }
+
+ let mut htlc_minimum_msat: u64 = 1;
+ let mut htlc_maximum_msat: u64 = 21_000_000 * 100_000_000 * 1_000; // Total bitcoin supply
+ for node in intermediate_nodes.iter() {
+ // The min htlc for an intermediate node is that node's min minus the fees charged by all of the
+ // following hops for forwarding that min, since that fee amount will automatically be included
+ // in the amount that this node receives and contribute towards reaching its min.
+ htlc_minimum_msat = amt_to_forward_msat(
+ core::cmp::max(node.tlvs.payment_constraints.htlc_minimum_msat, htlc_minimum_msat),
+ &node.tlvs.payment_relay
+ ).unwrap_or(1); // If underflow occurs, we definitely reached this node's min
+ htlc_maximum_msat = amt_to_forward_msat(
+ core::cmp::min(node.htlc_maximum_msat, htlc_maximum_msat), &node.tlvs.payment_relay
+ ).ok_or(())?; // If underflow occurs, we cannot send to this hop without exceeding their max
+ }
+ htlc_minimum_msat = core::cmp::max(
+ payee_tlvs.payment_constraints.htlc_minimum_msat, htlc_minimum_msat
+ );
+ htlc_maximum_msat = core::cmp::min(payee_htlc_maximum_msat, htlc_maximum_msat);
+
+ if htlc_maximum_msat < htlc_minimum_msat { return Err(()) }
+ Ok(BlindedPayInfo {
+ fee_base_msat: u32::try_from(curr_base_fee).map_err(|_| ())?,
+ fee_proportional_millionths: u32::try_from(curr_prop_mil).map_err(|_| ())?,
+ cltv_expiry_delta,
+ htlc_minimum_msat,
+ htlc_maximum_msat,
+ features: BlindedHopFeatures::empty(),
+ })
+}
+
+impl_writeable_msg!(PaymentRelay, {
+ cltv_expiry_delta,
+ fee_proportional_millionths,
+ fee_base_msat
+}, {});
+
+impl_writeable_msg!(PaymentConstraints, {
+ max_cltv_expiry,
+ htlc_minimum_msat
+}, {});
+
+#[cfg(test)]
+mod tests {
+ use bitcoin::secp256k1::PublicKey;
+ use crate::blinded_path::payment::{ForwardNode, ForwardTlvs, ReceiveTlvs, PaymentConstraints, PaymentRelay};
+ use crate::ln::PaymentSecret;
+ use crate::ln::features::BlindedHopFeatures;
+
+ #[test]
+ fn compute_payinfo() {
+ // Taken from the spec example for aggregating blinded payment info. See
+ // https://github.com/lightning/bolts/blob/master/proposals/route-blinding.md#blinded-payments
+ let dummy_pk = PublicKey::from_slice(&[2; 33]).unwrap();
+ let intermediate_nodes = vec![ForwardNode {
+ node_id: dummy_pk,
+ tlvs: ForwardTlvs {
+ short_channel_id: 0,
+ payment_relay: PaymentRelay {
+ cltv_expiry_delta: 144,
+ fee_proportional_millionths: 500,
+ fee_base_msat: 100,
+ },
+ payment_constraints: PaymentConstraints {
+ max_cltv_expiry: 0,
+ htlc_minimum_msat: 100,
+ },
+ features: BlindedHopFeatures::empty(),
+ },
+ htlc_maximum_msat: u64::max_value(),
+ }, ForwardNode {
+ node_id: dummy_pk,
+ tlvs: ForwardTlvs {
+ short_channel_id: 0,
+ payment_relay: PaymentRelay {
+ cltv_expiry_delta: 144,
+ fee_proportional_millionths: 500,
+ fee_base_msat: 100,
+ },
+ payment_constraints: PaymentConstraints {
+ max_cltv_expiry: 0,
+ htlc_minimum_msat: 1_000,
+ },
+ features: BlindedHopFeatures::empty(),
+ },
+ htlc_maximum_msat: u64::max_value(),
+ }];
+ let recv_tlvs = ReceiveTlvs {
+ payment_secret: PaymentSecret([0; 32]),
+ payment_constraints: PaymentConstraints {
+ max_cltv_expiry: 0,
+ htlc_minimum_msat: 1,
+ },
+ };
+ let htlc_maximum_msat = 100_000;
+ let blinded_payinfo = super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, htlc_maximum_msat).unwrap();
+ assert_eq!(blinded_payinfo.fee_base_msat, 201);
+ assert_eq!(blinded_payinfo.fee_proportional_millionths, 1001);
+ assert_eq!(blinded_payinfo.cltv_expiry_delta, 288);
+ assert_eq!(blinded_payinfo.htlc_minimum_msat, 900);
+ assert_eq!(blinded_payinfo.htlc_maximum_msat, htlc_maximum_msat);
+ }
+
+ #[test]
+ fn compute_payinfo_1_hop() {
+ let recv_tlvs = ReceiveTlvs {
+ payment_secret: PaymentSecret([0; 32]),
+ payment_constraints: PaymentConstraints {
+ max_cltv_expiry: 0,
+ htlc_minimum_msat: 1,
+ },
+ };
+ let blinded_payinfo = super::compute_payinfo(&[], &recv_tlvs, 4242).unwrap();
+ assert_eq!(blinded_payinfo.fee_base_msat, 0);
+ assert_eq!(blinded_payinfo.fee_proportional_millionths, 0);
+ assert_eq!(blinded_payinfo.cltv_expiry_delta, 0);
+ assert_eq!(blinded_payinfo.htlc_minimum_msat, 1);
+ assert_eq!(blinded_payinfo.htlc_maximum_msat, 4242);
+ }
+
+ #[test]
+ fn simple_aggregated_htlc_min() {
+ // If no hops charge fees, the htlc_minimum_msat should just be the maximum htlc_minimum_msat
+ // along the path.
+ let dummy_pk = PublicKey::from_slice(&[2; 33]).unwrap();
+ let intermediate_nodes = vec![ForwardNode {
+ node_id: dummy_pk,
+ tlvs: ForwardTlvs {
+ short_channel_id: 0,
+ payment_relay: PaymentRelay {
+ cltv_expiry_delta: 0,
+ fee_proportional_millionths: 0,
+ fee_base_msat: 0,
+ },
+ payment_constraints: PaymentConstraints {
+ max_cltv_expiry: 0,
+ htlc_minimum_msat: 1,
+ },
+ features: BlindedHopFeatures::empty(),
+ },
+ htlc_maximum_msat: u64::max_value()
+ }, ForwardNode {
+ node_id: dummy_pk,
+ tlvs: ForwardTlvs {
+ short_channel_id: 0,
+ payment_relay: PaymentRelay {
+ cltv_expiry_delta: 0,
+ fee_proportional_millionths: 0,
+ fee_base_msat: 0,
+ },
+ payment_constraints: PaymentConstraints {
+ max_cltv_expiry: 0,
+ htlc_minimum_msat: 2_000,
+ },
+ features: BlindedHopFeatures::empty(),
+ },
+ htlc_maximum_msat: u64::max_value()
+ }];
+ let recv_tlvs = ReceiveTlvs {
+ payment_secret: PaymentSecret([0; 32]),
+ payment_constraints: PaymentConstraints {
+ max_cltv_expiry: 0,
+ htlc_minimum_msat: 3,
+ },
+ };
+ let htlc_maximum_msat = 100_000;
+ let blinded_payinfo = super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, htlc_maximum_msat).unwrap();
+ assert_eq!(blinded_payinfo.htlc_minimum_msat, 2_000);
+ }
+
+ #[test]
+ fn aggregated_htlc_min() {
+ // Create a path with varying fees and htlc_mins, and make sure htlc_minimum_msat ends up as the
+ // max (htlc_min - following_fees) along the path.
+ let dummy_pk = PublicKey::from_slice(&[2; 33]).unwrap();
+ let intermediate_nodes = vec![ForwardNode {
+ node_id: dummy_pk,
+ tlvs: ForwardTlvs {
+ short_channel_id: 0,
+ payment_relay: PaymentRelay {
+ cltv_expiry_delta: 0,
+ fee_proportional_millionths: 500,
+ fee_base_msat: 1_000,
+ },
+ payment_constraints: PaymentConstraints {
+ max_cltv_expiry: 0,
+ htlc_minimum_msat: 5_000,
+ },
+ features: BlindedHopFeatures::empty(),
+ },
+ htlc_maximum_msat: u64::max_value()
+ }, ForwardNode {
+ node_id: dummy_pk,
+ tlvs: ForwardTlvs {
+ short_channel_id: 0,
+ payment_relay: PaymentRelay {
+ cltv_expiry_delta: 0,
+ fee_proportional_millionths: 500,
+ fee_base_msat: 200,
+ },
+ payment_constraints: PaymentConstraints {
+ max_cltv_expiry: 0,
+ htlc_minimum_msat: 2_000,
+ },
+ features: BlindedHopFeatures::empty(),
+ },
+ htlc_maximum_msat: u64::max_value()
+ }];
+ let recv_tlvs = ReceiveTlvs {
+ payment_secret: PaymentSecret([0; 32]),
+ payment_constraints: PaymentConstraints {
+ max_cltv_expiry: 0,
+ htlc_minimum_msat: 1,
+ },
+ };
+ let htlc_minimum_msat = 3798;
+ assert!(super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, htlc_minimum_msat - 1).is_err());
+
+ let htlc_maximum_msat = htlc_minimum_msat + 1;
+ let blinded_payinfo = super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, htlc_maximum_msat).unwrap();
+ assert_eq!(blinded_payinfo.htlc_minimum_msat, htlc_minimum_msat);
+ assert_eq!(blinded_payinfo.htlc_maximum_msat, htlc_maximum_msat);
+ }
+
+ #[test]
+ fn aggregated_htlc_max() {
+ // Create a path with varying fees and `htlc_maximum_msat`s, and make sure the aggregated max
+ // htlc ends up as the min (htlc_max - following_fees) along the path.
+ let dummy_pk = PublicKey::from_slice(&[2; 33]).unwrap();
+ let intermediate_nodes = vec![ForwardNode {
+ node_id: dummy_pk,
+ tlvs: ForwardTlvs {
+ short_channel_id: 0,
+ payment_relay: PaymentRelay {
+ cltv_expiry_delta: 0,
+ fee_proportional_millionths: 500,
+ fee_base_msat: 1_000,
+ },
+ payment_constraints: PaymentConstraints {
+ max_cltv_expiry: 0,
+ htlc_minimum_msat: 1,
+ },
+ features: BlindedHopFeatures::empty(),
+ },
+ htlc_maximum_msat: 5_000,
+ }, ForwardNode {
+ node_id: dummy_pk,
+ tlvs: ForwardTlvs {
+ short_channel_id: 0,
+ payment_relay: PaymentRelay {
+ cltv_expiry_delta: 0,
+ fee_proportional_millionths: 500,
+ fee_base_msat: 1,
+ },
+ payment_constraints: PaymentConstraints {
+ max_cltv_expiry: 0,
+ htlc_minimum_msat: 1,
+ },
+ features: BlindedHopFeatures::empty(),
+ },
+ htlc_maximum_msat: 10_000
+ }];
+ let recv_tlvs = ReceiveTlvs {
+ payment_secret: PaymentSecret([0; 32]),
+ payment_constraints: PaymentConstraints {
+ max_cltv_expiry: 0,
+ htlc_minimum_msat: 1,
+ },
+ };
+
+ let blinded_payinfo = super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, 10_000).unwrap();
+ assert_eq!(blinded_payinfo.htlc_maximum_msat, 3997);
+ }
+}
use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey, Scalar};
use bitcoin::secp256k1::ecdh::SharedSecret;
-use super::BlindedPath;
+use super::{BlindedHop, BlindedPath};
+use crate::ln::msgs::DecodeError;
use crate::ln::onion_utils;
use crate::onion_message::Destination;
+use crate::util::chacha20poly1305rfc::ChaChaPolyWriteAdapter;
+use crate::util::ser::{Readable, VecWriter, Writeable};
+use crate::io;
use crate::prelude::*;
// TODO: DRY with onion_utils::construct_onion_keys_callback
#[inline]
-pub(crate) fn construct_keys_callback<T: secp256k1::Signing + secp256k1::Verification,
- FType: FnMut(PublicKey, SharedSecret, PublicKey, [u8; 32], Option<PublicKey>, Option<Vec<u8>>)>(
- secp_ctx: &Secp256k1<T>, unblinded_path: &[PublicKey], destination: Option<Destination>,
- session_priv: &SecretKey, mut callback: FType
-) -> Result<(), secp256k1::Error> {
+pub(crate) fn construct_keys_callback<'a, T, I, F>(
+ secp_ctx: &Secp256k1<T>, unblinded_path: I, destination: Option<Destination>,
+ session_priv: &SecretKey, mut callback: F
+) -> Result<(), secp256k1::Error>
+where
+ T: secp256k1::Signing + secp256k1::Verification,
+ I: Iterator<Item=&'a PublicKey>,
+ F: FnMut(PublicKey, SharedSecret, PublicKey, [u8; 32], Option<PublicKey>, Option<Vec<u8>>),
+{
let mut msg_blinding_point_priv = session_priv.clone();
let mut msg_blinding_point = PublicKey::from_secret_key(secp_ctx, &msg_blinding_point_priv);
let mut onion_packet_pubkey_priv = msg_blinding_point_priv.clone();
}
Ok(())
}
+
+// Panics if `unblinded_tlvs` length is less than `unblinded_pks` length
+pub(super) fn construct_blinded_hops<'a, T, I1, I2>(
+ secp_ctx: &Secp256k1<T>, unblinded_pks: I1, mut unblinded_tlvs: I2, session_priv: &SecretKey
+) -> Result<Vec<BlindedHop>, secp256k1::Error>
+where
+ T: secp256k1::Signing + secp256k1::Verification,
+ I1: Iterator<Item=&'a PublicKey>,
+ I2: Iterator,
+ I2::Item: Writeable
+{
+ let mut blinded_hops = Vec::with_capacity(unblinded_pks.size_hint().0);
+ construct_keys_callback(
+ secp_ctx, unblinded_pks, None, session_priv,
+ |blinded_node_id, _, _, encrypted_payload_rho, _, _| {
+ blinded_hops.push(BlindedHop {
+ blinded_node_id,
+ encrypted_payload: encrypt_payload(unblinded_tlvs.next().unwrap(), encrypted_payload_rho),
+ });
+ })?;
+ Ok(blinded_hops)
+}
+
+/// Encrypt TLV payload to be used as a [`crate::blinded_path::BlindedHop::encrypted_payload`].
+fn encrypt_payload<P: Writeable>(payload: P, encrypted_tlvs_rho: [u8; 32]) -> Vec<u8> {
+ let mut writer = VecWriter(Vec::new());
+ let write_adapter = ChaChaPolyWriteAdapter::new(encrypted_tlvs_rho, &payload);
+ write_adapter.write(&mut writer).expect("In-memory writes cannot fail");
+ writer.0
+}
+
+/// Blinded path encrypted payloads may be padded to ensure they are equal length.
+///
+/// Reads padding to the end, ignoring what's read.
+pub(crate) struct Padding {}
+impl Readable for Padding {
+ #[inline]
+ fn read<R: io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
+ loop {
+ let mut buf = [0; 8192];
+ if reader.read(&mut buf[..])? == 0 { break; }
+ }
+ Ok(Self {})
+ }
+}
//! disconnections, transaction broadcasting, and feerate information requests.
use core::{cmp, ops::Deref};
+use core::convert::TryInto;
use bitcoin::blockdata::transaction::Transaction;
+// TODO: Define typed abstraction over feerates to handle their conversions.
+pub(crate) fn compute_feerate_sat_per_1000_weight(fee_sat: u64, weight: u64) -> u32 {
+ (fee_sat * 1000 / weight).try_into().unwrap_or(u32::max_value())
+}
+pub(crate) const fn fee_for_weight(feerate_sat_per_1000_weight: u32, weight: u64) -> u64 {
+ ((feerate_sat_per_1000_weight as u64 * weight) + 1000 - 1) / 1000
+}
+
/// An interface to send a transaction to the Bitcoin network.
pub trait BroadcasterInterface {
/// Sends a list of transactions out to (hopefully) be mined.
fn broadcast_transactions(&self, txs: &[&Transaction]);
}
-/// An enum that represents the speed at which we want a transaction to confirm used for feerate
+/// An enum that represents the priority at which we want a transaction to confirm used for feerate
/// estimation.
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub enum ConfirmationTarget {
- /// We are happy with this transaction confirming slowly when feerate drops some.
+ /// 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 this transaction to confirm without major delay, but 12-18 blocks is fine.
+ /// We'd like a transaction to confirm without major delayed, i.e., within the next 12-24 blocks.
Normal,
- /// We'd like this transaction to confirm in the next few blocks.
+ /// We'd like a transaction to confirm in the next few blocks.
HighPriority,
}
/// A trait which should be implemented to provide feerate information on a number of time
/// horizons.
///
+/// If access to a local mempool is not feasible, feerate estimates should be fetched from a set of
+/// third-parties hosting them. Note that this enables them to affect the propagation of your
+/// pre-signed transactions at any time and therefore endangers the safety of channels funds. It
+/// should be considered carefully as a deployment.
+///
/// Note that all of the functions implemented here *must* be reentrant-safe (obviously - they're
/// called from inside the library in response to chain events, P2P events, or timer events).
pub trait FeeEstimator {
use crate::prelude::*;
use crate::sync::{RwLock, RwLockReadGuard, Mutex, MutexGuard};
+use core::iter::FromIterator;
use core::ops::Deref;
-use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
+use core::sync::atomic::{AtomicUsize, Ordering};
use bitcoin::secp256k1::PublicKey;
-#[derive(Clone, Copy, Hash, PartialEq, Eq)]
+#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
/// A specific update's ID stored in a `MonitorUpdateId`, separated out to make the contents
/// entirely opaque.
enum UpdateOrigin {
}
/// An opaque identifier describing a specific [`Persist`] method call.
-#[derive(Clone, Copy, Hash, PartialEq, Eq)]
+#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub struct MonitorUpdateId {
contents: UpdateOrigin,
}
/// `Persist` defines behavior for persisting channel monitors: this could mean
/// writing once to disk, and/or uploading to one or more backup services.
///
-/// Each method can return three possible values:
-/// * If persistence (including any relevant `fsync()` calls) happens immediately, the
-/// implementation should return [`ChannelMonitorUpdateStatus::Completed`], indicating normal
-/// channel operation should continue.
-/// * If persistence happens asynchronously, implementations should first ensure the
-/// [`ChannelMonitor`] or [`ChannelMonitorUpdate`] are written durably to disk, and then return
-/// [`ChannelMonitorUpdateStatus::InProgress`] while the update continues in the background.
-/// Once the update completes, [`ChainMonitor::channel_monitor_updated`] should be called with
-/// the corresponding [`MonitorUpdateId`].
+/// Persistence can happen in one of two ways - synchronously completing before the trait method
+/// calls return or asynchronously in the background.
///
-/// Note that unlike the direct [`chain::Watch`] interface,
-/// [`ChainMonitor::channel_monitor_updated`] must be called once for *each* update which occurs.
+/// # For those implementing synchronous persistence
///
-/// * If persistence fails for some reason, implementations should return
-/// [`ChannelMonitorUpdateStatus::PermanentFailure`], in which case the channel will likely be
-/// closed without broadcasting the latest state. See
-/// [`ChannelMonitorUpdateStatus::PermanentFailure`] for more details.
+/// * If persistence completes fully (including any relevant `fsync()` calls), the implementation
+/// should return [`ChannelMonitorUpdateStatus::Completed`], indicating normal channel operation
+/// should continue.
+///
+/// * If persistence fails for some reason, implementations should consider returning
+/// [`ChannelMonitorUpdateStatus::InProgress`] and retry all pending persistence operations in
+/// the background with [`ChainMonitor::list_pending_monitor_updates`] and
+/// [`ChainMonitor::get_monitor`].
+///
+/// Once a full [`ChannelMonitor`] has been persisted, all pending updates for that channel can
+/// be marked as complete via [`ChainMonitor::channel_monitor_updated`].
+///
+/// If at some point no further progress can be made towards persisting the pending updates, the
+/// node should simply shut down.
+///
+/// * If the persistence has failed and cannot be retried further (e.g. because of some timeout),
+/// [`ChannelMonitorUpdateStatus::UnrecoverableError`] can be used, though this will result in
+/// an immediate panic and future operations in LDK generally failing.
+///
+/// # For those implementing asynchronous persistence
+///
+/// All calls should generally spawn a background task and immediately return
+/// [`ChannelMonitorUpdateStatus::InProgress`]. Once the update completes,
+/// [`ChainMonitor::channel_monitor_updated`] should be called with the corresponding
+/// [`MonitorUpdateId`].
+///
+/// Note that unlike the direct [`chain::Watch`] interface,
+/// [`ChainMonitor::channel_monitor_updated`] must be called once for *each* update which occurs.
+///
+/// If at some point no further progress can be made towards persisting a pending update, the node
+/// should simply shut down.
+///
+/// # Using remote watchtowers
+///
+/// Watchtowers may be updated as a part of an implementation of this trait, utilizing the async
+/// update process described above while the watchtower is being updated. The following methods are
+/// provided for bulding transactions for a watchtower:
+/// [`ChannelMonitor::initial_counterparty_commitment_tx`],
+/// [`ChannelMonitor::counterparty_commitment_txs_from_update`],
+/// [`ChannelMonitor::sign_to_local_justice_tx`], [`TrustedCommitmentTransaction::revokeable_output_index`],
+/// [`TrustedCommitmentTransaction::build_to_local_justice_tx`].
+///
+/// [`TrustedCommitmentTransaction::revokeable_output_index`]: crate::ln::chan_utils::TrustedCommitmentTransaction::revokeable_output_index
+/// [`TrustedCommitmentTransaction::build_to_local_justice_tx`]: crate::ln::chan_utils::TrustedCommitmentTransaction::build_to_local_justice_tx
pub trait Persist<ChannelSigner: WriteableEcdsaChannelSigner> {
/// Persist a new channel's data in response to a [`chain::Watch::watch_channel`] call. This is
/// called by [`ChannelManager`] for new channels, or may be called directly, e.g. on startup.
/// the ChannelManager re-adding the same payment entry, before the same block is replayed,
/// resulting in a duplicate PaymentSent event.
pending_monitor_updates: Mutex<Vec<MonitorUpdateId>>,
- /// When the user returns a PermanentFailure error from an update_persisted_channel call during
- /// block processing, we inform the ChannelManager that the channel should be closed
- /// asynchronously. In order to ensure no further changes happen before the ChannelManager has
- /// processed the closure event, we set this to true and return PermanentFailure for any other
- /// chain::Watch events.
- channel_perm_failed: AtomicBool,
/// The last block height at which no [`UpdateOrigin::ChainSync`] monitor updates were present
/// in `pending_monitor_updates`.
/// If it's been more than [`LATENCY_GRACE_PERIOD_BLOCKS`] since we started waiting on a chain
where
FN: Fn(&ChannelMonitor<ChannelSigner>, &TransactionData) -> Vec<TransactionOutputs>
{
+ let err_str = "ChannelMonitor[Update] persistence failed unrecoverably. This indicates we cannot continue normal operation and must shut down.";
+ let funding_outpoints: HashSet<OutPoint> = HashSet::from_iter(self.monitors.read().unwrap().keys().cloned());
+ for funding_outpoint in funding_outpoints.iter() {
+ let monitor_lock = self.monitors.read().unwrap();
+ if let Some(monitor_state) = monitor_lock.get(funding_outpoint) {
+ if self.update_monitor_with_chain_data(header, best_height, txdata, &process, funding_outpoint, &monitor_state).is_err() {
+ // Take the monitors lock for writing so that we poison it and any future
+ // operations going forward fail immediately.
+ core::mem::drop(monitor_state);
+ core::mem::drop(monitor_lock);
+ let _poison = self.monitors.write().unwrap();
+ log_error!(self.logger, "{}", err_str);
+ panic!("{}", err_str);
+ }
+ }
+ }
+
+ // do some followup cleanup if any funding outpoints were added in between iterations
let monitor_states = self.monitors.write().unwrap();
+ for (funding_outpoint, monitor_state) in monitor_states.iter() {
+ if !funding_outpoints.contains(funding_outpoint) {
+ if self.update_monitor_with_chain_data(header, best_height, txdata, &process, funding_outpoint, &monitor_state).is_err() {
+ log_error!(self.logger, "{}", err_str);
+ panic!("{}", err_str);
+ }
+ }
+ }
+
if let Some(height) = best_height {
// If the best block height is being updated, update highest_chain_height under the
// monitors write lock.
self.highest_chain_height.store(new_height, Ordering::Release);
}
}
+ }
- for (funding_outpoint, monitor_state) in monitor_states.iter() {
- let monitor = &monitor_state.monitor;
- let mut txn_outputs;
- {
- txn_outputs = process(monitor, txdata);
- let update_id = MonitorUpdateId {
- contents: UpdateOrigin::ChainSync(self.sync_persistence_id.get_increment()),
- };
- let mut pending_monitor_updates = monitor_state.pending_monitor_updates.lock().unwrap();
- if let Some(height) = best_height {
- if !monitor_state.has_pending_chainsync_updates(&pending_monitor_updates) {
- // If there are not ChainSync persists awaiting completion, go ahead and
- // set last_chain_persist_height here - we wouldn't want the first
- // InProgress to always immediately be considered "overly delayed".
- monitor_state.last_chain_persist_height.store(height as usize, Ordering::Release);
- }
+ fn update_monitor_with_chain_data<FN>(
+ &self, header: &BlockHeader, best_height: Option<u32>, txdata: &TransactionData,
+ process: FN, funding_outpoint: &OutPoint, monitor_state: &MonitorHolder<ChannelSigner>
+ ) -> Result<(), ()> where FN: Fn(&ChannelMonitor<ChannelSigner>, &TransactionData) -> Vec<TransactionOutputs> {
+ let monitor = &monitor_state.monitor;
+ let mut txn_outputs;
+ {
+ txn_outputs = process(monitor, txdata);
+ let update_id = MonitorUpdateId {
+ contents: UpdateOrigin::ChainSync(self.sync_persistence_id.get_increment()),
+ };
+ let mut pending_monitor_updates = monitor_state.pending_monitor_updates.lock().unwrap();
+ if let Some(height) = best_height {
+ if !monitor_state.has_pending_chainsync_updates(&pending_monitor_updates) {
+ // If there are not ChainSync persists awaiting completion, go ahead and
+ // set last_chain_persist_height here - we wouldn't want the first
+ // InProgress to always immediately be considered "overly delayed".
+ monitor_state.last_chain_persist_height.store(height as usize, Ordering::Release);
}
+ }
- log_trace!(self.logger, "Syncing Channel Monitor for channel {}", log_funding_info!(monitor));
- match self.persister.update_persisted_channel(*funding_outpoint, None, monitor, update_id) {
- ChannelMonitorUpdateStatus::Completed =>
- log_trace!(self.logger, "Finished syncing Channel Monitor for channel {}", log_funding_info!(monitor)),
- ChannelMonitorUpdateStatus::PermanentFailure => {
- monitor_state.channel_perm_failed.store(true, Ordering::Release);
- self.pending_monitor_events.lock().unwrap().push((*funding_outpoint, vec![MonitorEvent::UpdateFailed(*funding_outpoint)], monitor.get_counterparty_node_id()));
- self.event_notifier.notify();
- },
- ChannelMonitorUpdateStatus::InProgress => {
- log_debug!(self.logger, "Channel Monitor sync for channel {} in progress, holding events until completion!", log_funding_info!(monitor));
- pending_monitor_updates.push(update_id);
- },
- }
+ log_trace!(self.logger, "Syncing Channel Monitor for channel {}", log_funding_info!(monitor));
+ match self.persister.update_persisted_channel(*funding_outpoint, None, monitor, update_id) {
+ ChannelMonitorUpdateStatus::Completed =>
+ log_trace!(self.logger, "Finished syncing Channel Monitor for channel {}", log_funding_info!(monitor)),
+ ChannelMonitorUpdateStatus::InProgress => {
+ log_debug!(self.logger, "Channel Monitor sync for channel {} in progress, holding events until completion!", log_funding_info!(monitor));
+ pending_monitor_updates.push(update_id);
+ },
+ ChannelMonitorUpdateStatus::UnrecoverableError => {
+ return Err(());
+ },
}
+ }
- // Register any new outputs with the chain source for filtering, storing any dependent
- // transactions from within the block that previously had not been included in txdata.
- if let Some(ref chain_source) = self.chain_source {
- let block_hash = header.block_hash();
- for (txid, mut outputs) in txn_outputs.drain(..) {
- for (idx, output) in outputs.drain(..) {
- // Register any new outputs with the chain source for filtering
- let output = WatchedOutput {
- block_hash: Some(block_hash),
- outpoint: OutPoint { txid, index: idx as u16 },
- script_pubkey: output.script_pubkey,
- };
- chain_source.register_output(output)
- }
+ // Register any new outputs with the chain source for filtering, storing any dependent
+ // transactions from within the block that previously had not been included in txdata.
+ if let Some(ref chain_source) = self.chain_source {
+ let block_hash = header.block_hash();
+ for (txid, mut outputs) in txn_outputs.drain(..) {
+ for (idx, output) in outputs.drain(..) {
+ // Register any new outputs with the chain source for filtering
+ let output = WatchedOutput {
+ block_hash: Some(block_hash),
+ outpoint: OutPoint { txid, index: idx as u16 },
+ script_pubkey: output.script_pubkey,
+ };
+ chain_source.register_output(output)
}
}
}
+ Ok(())
}
/// Creates a new `ChainMonitor` used to watch on-chain activity pertaining to channels.
/// claims which are awaiting confirmation.
///
/// Includes the balances from each [`ChannelMonitor`] *except* those included in
- /// `ignored_channels`, allowing you to filter out balances from channels which are still open
- /// (and whose balance should likely be pulled from the [`ChannelDetails`]).
+ /// `ignored_channels`.
///
/// See [`ChannelMonitor::get_claimable_balances`] for more details on the exact criteria for
/// inclusion in the return value.
// `MonitorEvent`s from the monitor back to the `ChannelManager` until they
// complete.
let monitor_is_pending_updates = monitor_data.has_pending_offchain_updates(&pending_monitor_updates);
- if monitor_is_pending_updates || monitor_data.channel_perm_failed.load(Ordering::Acquire) {
- // If there are still monitor updates pending (or an old monitor update
- // finished after a later one perm-failed), we cannot yet construct an
+ if monitor_is_pending_updates {
+ // If there are still monitor updates pending, we cannot yet construct a
// Completed event.
return Ok(());
}
pub async fn process_pending_events_async<Future: core::future::Future, H: Fn(Event) -> Future>(
&self, handler: H
) {
- let mut pending_events = Vec::new();
- for monitor_state in self.monitors.read().unwrap().values() {
- pending_events.append(&mut monitor_state.monitor.get_and_clear_pending_events());
- }
- for event in pending_events {
- handler(event).await;
+ // Sadly we can't hold the monitors read lock through an async call. Thus we have to do a
+ // crazy dance to process a monitor's events then only remove them once we've done so.
+ let mons_to_process = self.monitors.read().unwrap().keys().cloned().collect::<Vec<_>>();
+ for funding_txo in mons_to_process {
+ let mut ev;
+ super::channelmonitor::process_events_body!(
+ self.monitors.read().unwrap().get(&funding_txo).map(|m| &m.monitor), ev, handler(ev).await);
}
}
L::Target: Logger,
P::Target: Persist<ChannelSigner>,
{
- /// Adds the monitor that watches the channel referred to by the given outpoint.
- ///
- /// Calls back to [`chain::Filter`] with the funding transaction and outputs to watch.
- ///
- /// Note that we persist the given `ChannelMonitor` while holding the `ChainMonitor`
- /// monitors lock.
- fn watch_channel(&self, funding_outpoint: OutPoint, monitor: ChannelMonitor<ChannelSigner>) -> ChannelMonitorUpdateStatus {
+ fn watch_channel(&self, funding_outpoint: OutPoint, monitor: ChannelMonitor<ChannelSigner>) -> Result<ChannelMonitorUpdateStatus, ()> {
let mut monitors = self.monitors.write().unwrap();
let entry = match monitors.entry(funding_outpoint) {
hash_map::Entry::Occupied(_) => {
log_error!(self.logger, "Failed to add new channel data: channel monitor for given outpoint is already present");
- return ChannelMonitorUpdateStatus::PermanentFailure
+ return Err(());
},
hash_map::Entry::Vacant(e) => e,
};
log_info!(self.logger, "Persistence of new ChannelMonitor for channel {} in progress", log_funding_info!(monitor));
pending_monitor_updates.push(update_id);
},
- ChannelMonitorUpdateStatus::PermanentFailure => {
- log_error!(self.logger, "Persistence of new ChannelMonitor for channel {} failed", log_funding_info!(monitor));
- return persist_res;
- },
ChannelMonitorUpdateStatus::Completed => {
log_info!(self.logger, "Persistence of new ChannelMonitor for channel {} completed", log_funding_info!(monitor));
- }
+ },
+ ChannelMonitorUpdateStatus::UnrecoverableError => {
+ let err_str = "ChannelMonitor[Update] persistence failed unrecoverably. This indicates we cannot continue normal operation and must shut down.";
+ log_error!(self.logger, "{}", err_str);
+ panic!("{}", err_str);
+ },
}
if let Some(ref chain_source) = self.chain_source {
monitor.load_outputs_to_watch(chain_source);
entry.insert(MonitorHolder {
monitor,
pending_monitor_updates: Mutex::new(pending_monitor_updates),
- channel_perm_failed: AtomicBool::new(false),
last_chain_persist_height: AtomicUsize::new(self.highest_chain_height.load(Ordering::Acquire)),
});
- persist_res
+ Ok(persist_res)
}
- /// Note that we persist the given `ChannelMonitor` update while holding the
- /// `ChainMonitor` monitors lock.
fn update_channel(&self, funding_txo: OutPoint, update: &ChannelMonitorUpdate) -> ChannelMonitorUpdateStatus {
// Update the monitor that watches the channel referred to by the given outpoint.
let monitors = self.monitors.read().unwrap();
- match monitors.get(&funding_txo) {
+ let ret = match monitors.get(&funding_txo) {
None => {
log_error!(self.logger, "Failed to update channel monitor: no such monitor registered");
// We should never ever trigger this from within ChannelManager. Technically a
// user could use this object with some proxying in between which makes this
// possible, but in tests and fuzzing, this should be a panic.
- #[cfg(any(test, fuzzing))]
+ #[cfg(debug_assertions)]
panic!("ChannelManager generated a channel update for a channel that was not yet registered!");
- #[cfg(not(any(test, fuzzing)))]
- ChannelMonitorUpdateStatus::PermanentFailure
+ #[cfg(not(debug_assertions))]
+ ChannelMonitorUpdateStatus::InProgress
},
Some(monitor_state) => {
let monitor = &monitor_state.monitor;
pending_monitor_updates.push(update_id);
log_debug!(self.logger, "Persistence of ChannelMonitorUpdate for channel {} in progress", log_funding_info!(monitor));
},
- ChannelMonitorUpdateStatus::PermanentFailure => {
- monitor_state.channel_perm_failed.store(true, Ordering::Release);
- log_error!(self.logger, "Persistence of ChannelMonitorUpdate for channel {} failed", log_funding_info!(monitor));
- },
ChannelMonitorUpdateStatus::Completed => {
log_debug!(self.logger, "Persistence of ChannelMonitorUpdate for channel {} completed", log_funding_info!(monitor));
},
+ ChannelMonitorUpdateStatus::UnrecoverableError => { /* we'll panic in a moment */ },
}
if update_res.is_err() {
- ChannelMonitorUpdateStatus::PermanentFailure
- } else if monitor_state.channel_perm_failed.load(Ordering::Acquire) {
- ChannelMonitorUpdateStatus::PermanentFailure
+ ChannelMonitorUpdateStatus::InProgress
} else {
persist_res
}
}
+ };
+ if let ChannelMonitorUpdateStatus::UnrecoverableError = ret {
+ // Take the monitors lock for writing so that we poison it and any future
+ // operations going forward fail immediately.
+ core::mem::drop(monitors);
+ let _poison = self.monitors.write().unwrap();
+ let err_str = "ChannelMonitor[Update] persistence failed unrecoverably. This indicates we cannot continue normal operation and must shut down.";
+ log_error!(self.logger, "{}", err_str);
+ panic!("{}", err_str);
}
+ ret
}
fn release_pending_monitor_events(&self) -> Vec<(OutPoint, Vec<MonitorEvent>, Option<PublicKey>)> {
monitor_state.last_chain_persist_height.load(Ordering::Acquire) + LATENCY_GRACE_PERIOD_BLOCKS as usize
> self.highest_chain_height.load(Ordering::Acquire)
{
- log_info!(self.logger, "A Channel Monitor sync is still in progress, refusing to provide monitor events!");
+ log_debug!(self.logger, "A Channel Monitor sync is still in progress, refusing to provide monitor events!");
} else {
- if monitor_state.channel_perm_failed.load(Ordering::Acquire) {
- // If a `UpdateOrigin::ChainSync` persistence failed with `PermanantFailure`,
- // we don't really know if the latest `ChannelMonitor` state is on disk or not.
- // We're supposed to hold monitor updates until the latest state is on disk to
- // avoid duplicate events, but the user told us persistence is screw-y and may
- // not complete. We can't hold events forever because we may learn some payment
- // preimage, so instead we just log and hope the user complied with the
- // `PermanentFailure` requirements of having at least the local-disk copy
- // updated.
- log_info!(self.logger, "A Channel Monitor sync returned PermanentFailure. Returning monitor events but duplicate events may appear after reload!");
- }
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.");
/// [`SpendableOutputs`]: events::Event::SpendableOutputs
/// [`BumpTransaction`]: events::Event::BumpTransaction
fn process_pending_events<H: Deref>(&self, handler: H) where H::Target: EventHandler {
- let mut pending_events = Vec::new();
for monitor_state in self.monitors.read().unwrap().values() {
- pending_events.append(&mut monitor_state.monitor.get_and_clear_pending_events());
- }
- for event in pending_events {
- handler.handle_event(event);
+ monitor_state.monitor.process_pending_events(&handler);
}
}
}
#[cfg(test)]
mod tests {
- use crate::{check_added_monitors, check_closed_broadcast, check_closed_event};
- use crate::{expect_payment_sent, expect_payment_claimed, expect_payment_sent_without_paths, expect_payment_path_successful, get_event_msg};
+ use crate::check_added_monitors;
+ use crate::{expect_payment_claimed, expect_payment_path_successful, get_event_msg};
use crate::{get_htlc_update_msgs, get_local_commitment_txn, get_revoke_commit_msgs, get_route_and_payment_hash, unwrap_send_err};
use crate::chain::{ChannelMonitorUpdateStatus, Confirm, Watch};
use crate::chain::channelmonitor::LATENCY_GRACE_PERIOD_BLOCKS;
- use crate::events::{Event, ClosureReason, MessageSendEvent, MessageSendEventsProvider};
+ use crate::events::{Event, MessageSendEvent, MessageSendEventsProvider};
use crate::ln::channelmanager::{PaymentSendFailure, PaymentId, RecipientOnionFields};
use crate::ln::functional_test_utils::*;
use crate::ln::msgs::ChannelMessageHandler;
create_announced_chan_between_nodes(&nodes, 0, 1);
// Route two payments to be claimed at the same time.
- let (payment_preimage_1, payment_hash_1, _) = route_payment(&nodes[0], &[&nodes[1]], 1_000_000);
- let (payment_preimage_2, payment_hash_2, _) = route_payment(&nodes[0], &[&nodes[1]], 1_000_000);
+ let (payment_preimage_1, payment_hash_1, ..) = route_payment(&nodes[0], &[&nodes[1]], 1_000_000);
+ let (payment_preimage_2, payment_hash_2, ..) = route_payment(&nodes[0], &[&nodes[1]], 1_000_000);
chanmon_cfgs[1].persister.offchain_monitor_updates.lock().unwrap().clear();
chanmon_cfgs[1].persister.set_update_ret(ChannelMonitorUpdateStatus::InProgress);
let 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(), &updates.update_fulfill_htlcs[0]);
- expect_payment_sent_without_paths!(nodes[0], payment_preimage_1);
+ expect_payment_sent(&nodes[0], payment_preimage_1, None, false, false);
nodes[0].node.handle_commitment_signed(&nodes[1].node.get_our_node_id(), &updates.commitment_signed);
check_added_monitors!(nodes[0], 1);
let (as_first_raa, as_first_update) = get_revoke_commit_msgs!(nodes[0], nodes[1].node.get_our_node_id());
let bs_first_raa = get_event_msg!(nodes[1], MessageSendEvent::SendRevokeAndACK, nodes[0].node.get_our_node_id());
nodes[0].node.handle_update_fulfill_htlc(&nodes[1].node.get_our_node_id(), &bs_second_updates.update_fulfill_htlcs[0]);
- expect_payment_sent_without_paths!(nodes[0], payment_preimage_2);
+ expect_payment_sent(&nodes[0], payment_preimage_2, None, false, false);
nodes[0].node.handle_commitment_signed(&nodes[1].node.get_our_node_id(), &bs_second_updates.commitment_signed);
check_added_monitors!(nodes[0], 1);
nodes[0].node.handle_revoke_and_ack(&nodes[1].node.get_our_node_id(), &bs_first_raa);
let (route, second_payment_hash, _, second_payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[1], 100_000);
// First route a payment that we will claim on chain and give the recipient the preimage.
- let (payment_preimage, payment_hash, _) = route_payment(&nodes[0], &[&nodes[1]], 1_000_000);
+ let (payment_preimage, payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1]], 1_000_000);
nodes[1].node.claim_funds(payment_preimage);
expect_payment_claimed!(nodes[1], payment_hash, 1_000_000);
nodes[1].node.get_and_clear_pending_msg_events();
chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::Completed);
unwrap_send_err!(nodes[0].node.send_payment_with_route(&route, second_payment_hash,
RecipientOnionFields::secret_only(second_payment_secret), PaymentId(second_payment_hash.0)
- ), true, APIError::ChannelUnavailable { ref err },
- assert!(err.contains("ChannelMonitor storage failure")));
- check_added_monitors!(nodes[0], 2); // After the failure we generate a close-channel monitor update
- check_closed_broadcast!(nodes[0], true);
- check_closed_event!(nodes[0], 1, ClosureReason::ProcessingError { err: "ChannelMonitor storage failure".to_string() });
+ ), false, APIError::MonitorUpdateInProgress, {});
+ check_added_monitors!(nodes[0], 1);
// However, as the ChainMonitor is still waiting for the original persistence to complete,
// it won't yet release the MonitorEvents.
}
}
- expect_payment_sent!(nodes[0], payment_preimage);
+ expect_payment_sent(&nodes[0], payment_preimage, None, true, false);
}
#[test]
}
#[test]
- fn update_during_chainsync_fails_channel() {
+ #[cfg(feature = "std")]
+ fn update_during_chainsync_poisons_channel() {
let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
create_announced_chan_between_nodes(&nodes, 0, 1);
chanmon_cfgs[0].persister.chain_sync_monitor_persistences.lock().unwrap().clear();
- chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::PermanentFailure);
-
- connect_blocks(&nodes[0], 1);
- // Before processing events, the ChannelManager will still think the Channel is open and
- // there won't be any ChannelMonitorUpdates
- assert_eq!(nodes[0].node.list_channels().len(), 1);
- check_added_monitors!(nodes[0], 0);
- // ... however once we get events once, the channel will close, creating a channel-closed
- // ChannelMonitorUpdate.
- check_closed_broadcast!(nodes[0], true);
- check_closed_event!(nodes[0], 1, ClosureReason::ProcessingError { err: "Failed to persist ChannelMonitor update during chain sync".to_string() });
- check_added_monitors!(nodes[0], 1);
+ chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::UnrecoverableError);
+
+ assert!(std::panic::catch_unwind(|| {
+ // Returning an UnrecoverableError should always panic immediately
+ connect_blocks(&nodes[0], 1);
+ }).is_err());
+ assert!(std::panic::catch_unwind(|| {
+ // ...and also poison our locks causing later use to panic as well
+ core::mem::drop(nodes);
+ }).is_err());
}
}
use bitcoin::secp256k1::{Secp256k1, ecdsa::Signature};
use bitcoin::secp256k1::{SecretKey, PublicKey};
-use bitcoin::secp256k1;
+use bitcoin::{secp256k1, EcdsaSighashType};
+use crate::ln::channel::INITIAL_COMMITMENT_NUMBER;
use crate::ln::{PaymentHash, PaymentPreimage};
use crate::ln::msgs::DecodeError;
use crate::ln::chan_utils;
-use crate::ln::chan_utils::{CounterpartyCommitmentSecrets, HTLCOutputInCommitment, HTLCClaim, ChannelTransactionParameters, HolderCommitmentTransaction};
+use crate::ln::chan_utils::{CommitmentTransaction, CounterpartyCommitmentSecrets, HTLCOutputInCommitment, HTLCClaim, ChannelTransactionParameters, HolderCommitmentTransaction, TxCreationKeys};
use crate::ln::channelmanager::{HTLCSource, SentHTLCId};
use crate::chain;
use crate::chain::{BestBlock, WatchedOutput};
use crate::util::logger::Logger;
use crate::util::ser::{Readable, ReadableArgs, RequiredWrapper, MaybeReadable, UpgradableRequired, Writer, Writeable, U48};
use crate::util::byte_utils;
-use crate::events::Event;
-use crate::events::bump_transaction::{AnchorDescriptor, HTLCDescriptor, BumpTransactionEvent};
+use crate::events::{Event, EventHandler};
+use crate::events::bump_transaction::{ChannelDerivationParameters, AnchorDescriptor, HTLCDescriptor, BumpTransactionEvent};
use crate::prelude::*;
use core::{cmp, mem};
/// much smaller than a full [`ChannelMonitor`]. However, for large single commitment transaction
/// updates (e.g. ones during which there are hundreds of HTLCs pending on the commitment
/// transaction), a single update may reach upwards of 1 MiB in serialized size.
-#[derive(Clone, PartialEq, Eq)]
+#[derive(Clone, Debug, PartialEq, Eq)]
#[must_use]
pub struct ChannelMonitorUpdate {
pub(crate) updates: Vec<ChannelMonitorUpdateStep>,
HTLCEvent(HTLCUpdate),
/// A monitor event that the Channel's commitment transaction was confirmed.
- CommitmentTxConfirmed(OutPoint),
+ HolderForceClosed(OutPoint),
/// Indicates a [`ChannelMonitor`] update has completed. See
/// [`ChannelMonitorUpdateStatus::InProgress`] for more information on how this is used.
/// same [`ChannelMonitor`] have been applied and persisted.
monitor_update_id: u64,
},
-
- /// Indicates a [`ChannelMonitor`] update has failed. See
- /// [`ChannelMonitorUpdateStatus::PermanentFailure`] for more information on how this is used.
- ///
- /// [`ChannelMonitorUpdateStatus::PermanentFailure`]: super::ChannelMonitorUpdateStatus::PermanentFailure
- UpdateFailed(OutPoint),
}
impl_writeable_tlv_based_enum_upgradable!(MonitorEvent,
- // Note that Completed and UpdateFailed are currently never serialized to disk as they are
- // generated only in ChainMonitor
+ // Note that Completed is currently never serialized to disk as it is generated only in
+ // ChainMonitor.
(0, Completed) => {
(0, funding_txo, required),
(2, monitor_update_id, required),
},
;
(2, HTLCEvent),
- (4, CommitmentTxConfirmed),
- (6, UpdateFailed),
+ (4, HolderForceClosed),
+ // 6 was `UpdateFailed` until LDK 0.0.117
);
/// Simple structure sent back by `chain::Watch` when an HTLC from a forward channel is detected on
(8, delayed_payment_key, required),
(10, per_commitment_point, required),
(12, feerate_per_kw, required),
- (14, htlc_outputs, vec_type)
+ (14, htlc_outputs, required_vec)
});
impl HolderSignedTx {
/// We use this to track static counterparty commitment transaction data and to generate any
/// justice or 2nd-stage preimage/timeout transactions.
-#[derive(PartialEq, Eq)]
+#[derive(Clone, PartialEq, Eq)]
struct CounterpartyCommitmentParameters {
counterparty_delayed_payment_base_key: PublicKey,
counterparty_htlc_base_key: PublicKey,
/// observed, as well as the transaction causing it.
///
/// Used to determine when the on-chain event can be considered safe from a chain reorganization.
-#[derive(PartialEq, Eq)]
+#[derive(Clone, PartialEq, Eq)]
struct OnchainEventEntry {
txid: Txid,
height: u32,
/// Upon discovering of some classes of onchain tx by ChannelMonitor, we may have to take actions on it
/// once they mature to enough confirmations (ANTI_REORG_DELAY)
-#[derive(PartialEq, Eq)]
+#[derive(Clone, PartialEq, Eq)]
enum OnchainEvent {
/// An outbound HTLC failing after a transaction is confirmed. Used
/// * when an outbound HTLC output is spent by us after the HTLC timed out
);
-#[derive(Clone, PartialEq, Eq)]
+#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum ChannelMonitorUpdateStep {
LatestHolderCommitmentTXInfo {
commitment_tx: HolderCommitmentTransaction,
htlc_outputs: Vec<(HTLCOutputInCommitment, Option<Box<HTLCSource>>)>,
commitment_number: u64,
their_per_commitment_point: PublicKey,
+ feerate_per_kw: Option<u32>,
+ to_broadcaster_value_sat: Option<u64>,
+ to_countersignatory_value_sat: Option<u64>,
},
PaymentPreimage {
payment_preimage: PaymentPreimage,
impl_writeable_tlv_based_enum_upgradable!(ChannelMonitorUpdateStep,
(0, LatestHolderCommitmentTXInfo) => {
(0, commitment_tx, required),
- (1, claimed_htlcs, vec_type),
- (2, htlc_outputs, vec_type),
+ (1, claimed_htlcs, optional_vec),
+ (2, htlc_outputs, required_vec),
(4, nondust_htlc_sources, optional_vec),
},
(1, LatestCounterpartyCommitmentTXInfo) => {
(0, commitment_txid, required),
+ (1, feerate_per_kw, option),
(2, commitment_number, required),
+ (3, to_broadcaster_value_sat, option),
(4, their_per_commitment_point, required),
- (6, htlc_outputs, vec_type),
+ (5, to_countersignatory_value_sat, option),
+ (6, htlc_outputs, required_vec),
},
(2, PaymentPreimage) => {
(0, payment_preimage, required),
ClaimableOnChannelClose {
/// The amount available to claim, in satoshis, excluding the on-chain fees which will be
/// required to do so.
- claimable_amount_satoshis: u64,
+ amount_satoshis: u64,
},
/// The channel has been closed, and the given balance is ours but awaiting confirmations until
/// we consider it spendable.
ClaimableAwaitingConfirmations {
/// The amount available to claim, in satoshis, possibly excluding the on-chain fees which
/// were spent in broadcasting the transaction.
- claimable_amount_satoshis: u64,
+ amount_satoshis: u64,
/// The height at which an [`Event::SpendableOutputs`] event will be generated for this
/// amount.
confirmation_height: u32,
ContentiousClaimable {
/// The amount available to claim, in satoshis, excluding the on-chain fees which will be
/// required to do so.
- claimable_amount_satoshis: u64,
+ amount_satoshis: u64,
/// The height at which the counterparty may be able to claim the balance if we have not
/// done so.
timeout_height: u32,
MaybeTimeoutClaimableHTLC {
/// The amount potentially available to claim, in satoshis, excluding the on-chain fees
/// which will be required to do so.
- claimable_amount_satoshis: u64,
+ amount_satoshis: u64,
/// The height at which we will be able to claim the balance if our counterparty has not
/// done so.
claimable_height: u32,
MaybePreimageClaimableHTLC {
/// The amount potentially available to claim, in satoshis, excluding the on-chain fees
/// which will be required to do so.
- claimable_amount_satoshis: u64,
+ amount_satoshis: u64,
/// The height at which our counterparty will be able to claim the balance if we have not
/// yet received the preimage and claimed it ourselves.
expiry_height: u32,
///
/// Note that for outputs from HTLC balances this may be excluding some on-chain fees that
/// were already spent.
- claimable_amount_satoshis: u64,
+ amount_satoshis: u64,
},
}
/// On-chain fees required to claim the balance are not included in this amount.
pub fn claimable_amount_satoshis(&self) -> u64 {
match self {
- Balance::ClaimableOnChannelClose {
- claimable_amount_satoshis,
- } => *claimable_amount_satoshis,
- Balance::ClaimableAwaitingConfirmations {
- claimable_amount_satoshis,
- ..
- } => *claimable_amount_satoshis,
- Balance::ContentiousClaimable {
- claimable_amount_satoshis,
- ..
- } => *claimable_amount_satoshis,
- Balance::MaybeTimeoutClaimableHTLC {
- ..
- } => 0,
- Balance::MaybePreimageClaimableHTLC {
- ..
- } => 0,
- Balance::CounterpartyRevokedOutputClaimable {
- claimable_amount_satoshis,
- ..
- } => *claimable_amount_satoshis,
+ Balance::ClaimableOnChannelClose { amount_satoshis, .. }|
+ Balance::ClaimableAwaitingConfirmations { amount_satoshis, .. }|
+ Balance::ContentiousClaimable { amount_satoshis, .. }|
+ Balance::CounterpartyRevokedOutputClaimable { amount_satoshis, .. }
+ => *amount_satoshis,
+ Balance::MaybeTimeoutClaimableHTLC { .. }|
+ Balance::MaybePreimageClaimableHTLC { .. }
+ => 0,
}
}
}
/// An HTLC which has been irrevocably resolved on-chain, and has reached ANTI_REORG_DELAY.
-#[derive(PartialEq, Eq)]
+#[derive(Clone, PartialEq, Eq)]
struct IrrevocablyResolvedHTLC {
commitment_tx_output_idx: Option<u32>,
/// The txid of the transaction which resolved the HTLC, this may be a commitment (if the HTLC
/// You MUST ensure that no ChannelMonitors for a given channel anywhere contain out-of-date
/// information and are actively monitoring the chain.
///
-/// Pending Events or updated HTLCs which have not yet been read out by
-/// get_and_clear_pending_monitor_events or get_and_clear_pending_events are serialized to disk and
-/// reloaded at deserialize-time. Thus, you must ensure that, when handling events, all events
-/// gotten are fully handled before re-serializing the new state.
-///
/// Note that the deserializer is only implemented for (BlockHash, ChannelMonitor), which
/// tells you the last block hash which was block_connect()ed. You MUST rescan any blocks along
/// the "reorg path" (ie disconnecting blocks until you find a common ancestor from both the
#[cfg(test)]
pub(crate) inner: Mutex<ChannelMonitorImpl<Signer>>,
#[cfg(not(test))]
- inner: Mutex<ChannelMonitorImpl<Signer>>,
+ pub(super) inner: Mutex<ChannelMonitorImpl<Signer>>,
}
-#[derive(PartialEq)]
+impl<Signer: WriteableEcdsaChannelSigner> Clone for ChannelMonitor<Signer> where Signer: Clone {
+ fn clone(&self) -> Self {
+ let inner = self.inner.lock().unwrap().clone();
+ ChannelMonitor::from_impl(inner)
+ }
+}
+
+#[derive(Clone, PartialEq)]
pub(crate) struct ChannelMonitorImpl<Signer: WriteableEcdsaChannelSigner> {
latest_update_id: u64,
commitment_transaction_number_obscure_factor: u64,
// we further MUST NOT generate events during block/transaction-disconnection.
pending_monitor_events: Vec<MonitorEvent>,
- pending_events: Vec<Event>,
+ pub(super) pending_events: Vec<Event>,
+ pub(super) is_processing_pending_events: bool,
// Used to track on-chain events (i.e., transactions part of channels confirmed on chain) on
// which to take actions once they reach enough confirmations. Each entry includes the
/// The node_id of our counterparty
counterparty_node_id: Option<PublicKey>,
+
+ /// Initial counterparty commmitment data needed to recreate the commitment tx
+ /// in the persistence pipeline for third-party watchtowers. This will only be present on
+ /// monitors created after 0.0.117.
+ ///
+ /// Ordering of tuple data: (their_per_commitment_point, feerate_per_kw, to_broadcaster_sats,
+ /// to_countersignatory_sats)
+ initial_counterparty_commitment_info: Option<(PublicKey, u32, u64, u64)>,
}
/// Transaction outputs to watch for on-chain spends.
writer.write_all(&(self.pending_monitor_events.iter().filter(|ev| match ev {
MonitorEvent::HTLCEvent(_) => true,
- MonitorEvent::CommitmentTxConfirmed(_) => true,
+ MonitorEvent::HolderForceClosed(_) => true,
_ => false,
}).count() as u64).to_be_bytes())?;
for event in self.pending_monitor_events.iter() {
0u8.write(writer)?;
upd.write(writer)?;
},
- MonitorEvent::CommitmentTxConfirmed(_) => 1u8.write(writer)?,
+ MonitorEvent::HolderForceClosed(_) => 1u8.write(writer)?,
_ => {}, // Covered in the TLV writes below
}
}
write_tlv_fields!(writer, {
(1, self.funding_spend_confirmed, option),
- (3, self.htlcs_resolved_on_chain, vec_type),
- (5, self.pending_monitor_events, vec_type),
+ (3, self.htlcs_resolved_on_chain, required_vec),
+ (5, self.pending_monitor_events, required_vec),
(7, self.funding_spend_seen, required),
(9, self.counterparty_node_id, option),
(11, self.confirmed_commitment_tx_counterparty_output, option),
- (13, self.spendable_txids_confirmed, vec_type),
+ (13, self.spendable_txids_confirmed, required_vec),
(15, self.counterparty_fulfilled_htlcs, required),
+ (17, self.initial_counterparty_commitment_info, option),
});
Ok(())
}
}
+macro_rules! _process_events_body {
+ ($self_opt: expr, $event_to_handle: expr, $handle_event: expr) => {
+ loop {
+ let (pending_events, repeated_events);
+ if let Some(us) = $self_opt {
+ let mut inner = us.inner.lock().unwrap();
+ if inner.is_processing_pending_events {
+ break;
+ }
+ inner.is_processing_pending_events = true;
+
+ pending_events = inner.pending_events.clone();
+ repeated_events = inner.get_repeated_events();
+ } else { break; }
+ let num_events = pending_events.len();
+
+ for event in pending_events.into_iter().chain(repeated_events.into_iter()) {
+ $event_to_handle = event;
+ $handle_event;
+ }
+
+ if let Some(us) = $self_opt {
+ let mut inner = us.inner.lock().unwrap();
+ inner.pending_events.drain(..num_events);
+ inner.is_processing_pending_events = false;
+ if !inner.pending_events.is_empty() {
+ // If there's more events to process, go ahead and do so.
+ continue;
+ }
+ }
+ break;
+ }
+ }
+}
+pub(super) use _process_events_body as process_events_body;
+
impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitor<Signer> {
/// For lockorder enforcement purposes, we need to have a single site which constructs the
/// `inner` mutex, otherwise cases where we lock two monitors at the same time (eg in our
payment_preimages: HashMap::new(),
pending_monitor_events: Vec::new(),
pending_events: Vec::new(),
+ is_processing_pending_events: false,
onchain_events_awaiting_threshold_conf: Vec::new(),
outputs_to_watch,
best_block,
counterparty_node_id: Some(counterparty_node_id),
+ initial_counterparty_commitment_info: None,
})
}
self.inner.lock().unwrap().provide_secret(idx, secret)
}
+ /// A variant of `Self::provide_latest_counterparty_commitment_tx` used to provide
+ /// additional information to the monitor to store in order to recreate the initial
+ /// counterparty commitment transaction during persistence (mainly for use in third-party
+ /// watchtowers).
+ ///
+ /// This is used to provide the counterparty commitment information directly to the monitor
+ /// before the initial persistence of a new channel.
+ pub(crate) fn provide_initial_counterparty_commitment_tx<L: Deref>(
+ &self, txid: Txid, htlc_outputs: Vec<(HTLCOutputInCommitment, Option<Box<HTLCSource>>)>,
+ commitment_number: u64, their_cur_per_commitment_point: PublicKey, feerate_per_kw: u32,
+ to_broadcaster_value_sat: u64, to_countersignatory_value_sat: u64, logger: &L,
+ )
+ where L::Target: Logger
+ {
+ self.inner.lock().unwrap().provide_initial_counterparty_commitment_tx(txid,
+ htlc_outputs, commitment_number, their_cur_per_commitment_point, feerate_per_kw,
+ to_broadcaster_value_sat, to_countersignatory_value_sat, logger);
+ }
+
/// Informs this monitor of the latest counterparty (ie non-broadcastable) commitment transaction.
/// The monitor watches for it to be broadcasted and then uses the HTLC information (and
/// possibly future revocation/preimage information) to claim outputs where possible.
/// We cache also the mapping hash:commitment number to lighten pruning of old preimages by watchtowers.
- pub(crate) fn provide_latest_counterparty_commitment_tx<L: Deref>(
+ #[cfg(test)]
+ fn provide_latest_counterparty_commitment_tx<L: Deref>(
&self,
txid: Txid,
htlc_outputs: Vec<(HTLCOutputInCommitment, Option<Box<HTLCSource>>)>,
self.inner.lock().unwrap().get_and_clear_pending_monitor_events()
}
- /// Gets the list of pending events which were generated by previous actions, clearing the list
- /// in the process.
+ /// Processes [`SpendableOutputs`] events produced from each [`ChannelMonitor`] upon maturity.
+ ///
+ /// For channels featuring anchor outputs, this method will also process [`BumpTransaction`]
+ /// events produced from each [`ChannelMonitor`] while there is a balance to claim onchain
+ /// within each channel. As the confirmation of a commitment transaction may be critical to the
+ /// safety of funds, we recommend invoking this every 30 seconds, or lower if running in an
+ /// environment with spotty connections, like on mobile.
+ ///
+ /// An [`EventHandler`] may safely call back to the provider, though this shouldn't be needed in
+ /// order to handle these events.
///
- /// This is called by the [`EventsProvider::process_pending_events`] implementation for
- /// [`ChainMonitor`].
+ /// [`SpendableOutputs`]: crate::events::Event::SpendableOutputs
+ /// [`BumpTransaction`]: crate::events::Event::BumpTransaction
+ pub fn process_pending_events<H: Deref>(&self, handler: &H) where H::Target: EventHandler {
+ let mut ev;
+ process_events_body!(Some(self), ev, handler.handle_event(ev));
+ }
+
+ /// Processes any events asynchronously.
///
- /// [`EventsProvider::process_pending_events`]: crate::events::EventsProvider::process_pending_events
- /// [`ChainMonitor`]: crate::chain::chainmonitor::ChainMonitor
+ /// See [`Self::process_pending_events`] for more information.
+ pub async fn process_pending_events_async<Future: core::future::Future, H: Fn(Event) -> Future>(
+ &self, handler: &H
+ ) {
+ let mut ev;
+ process_events_body!(Some(self), ev, { handler(ev).await });
+ }
+
+ #[cfg(test)]
pub fn get_and_clear_pending_events(&self) -> Vec<Event> {
- self.inner.lock().unwrap().get_and_clear_pending_events()
+ let mut ret = Vec::new();
+ let mut lck = self.inner.lock().unwrap();
+ mem::swap(&mut ret, &mut lck.pending_events);
+ ret.append(&mut lck.get_repeated_events());
+ ret
+ }
+
+ /// Gets the counterparty's initial commitment transaction. The returned commitment
+ /// transaction is unsigned. This is intended to be called during the initial persistence of
+ /// the monitor (inside an implementation of [`Persist::persist_new_channel`]), to allow for
+ /// watchtowers in the persistence pipeline to have enough data to form justice transactions.
+ ///
+ /// This is similar to [`Self::counterparty_commitment_txs_from_update`], except
+ /// that for the initial commitment transaction, we don't have a corresponding update.
+ ///
+ /// This will only return `Some` for channel monitors that have been created after upgrading
+ /// to LDK 0.0.117+.
+ ///
+ /// [`Persist::persist_new_channel`]: crate::chain::chainmonitor::Persist::persist_new_channel
+ pub fn initial_counterparty_commitment_tx(&self) -> Option<CommitmentTransaction> {
+ self.inner.lock().unwrap().initial_counterparty_commitment_tx()
+ }
+
+ /// Gets all of the counterparty commitment transactions provided by the given update. This
+ /// may be empty if the update doesn't include any new counterparty commitments. Returned
+ /// commitment transactions are unsigned.
+ ///
+ /// This is provided so that watchtower clients in the persistence pipeline are able to build
+ /// justice transactions for each counterparty commitment upon each update. It's intended to be
+ /// used within an implementation of [`Persist::update_persisted_channel`], which is provided
+ /// with a monitor and an update. Once revoked, signing a justice transaction can be done using
+ /// [`Self::sign_to_local_justice_tx`].
+ ///
+ /// It is expected that a watchtower client may use this method to retrieve the latest counterparty
+ /// commitment transaction(s), and then hold the necessary data until a later update in which
+ /// the monitor has been updated with the corresponding revocation data, at which point the
+ /// monitor can sign the justice transaction.
+ ///
+ /// This will only return a non-empty list for monitor updates that have been created after
+ /// upgrading to LDK 0.0.117+. Note that no restriction lies on the monitors themselves, which
+ /// may have been created prior to upgrading.
+ ///
+ /// [`Persist::update_persisted_channel`]: crate::chain::chainmonitor::Persist::update_persisted_channel
+ pub fn counterparty_commitment_txs_from_update(&self, update: &ChannelMonitorUpdate) -> Vec<CommitmentTransaction> {
+ self.inner.lock().unwrap().counterparty_commitment_txs_from_update(update)
+ }
+
+ /// Wrapper around [`EcdsaChannelSigner::sign_justice_revoked_output`] to make
+ /// signing the justice transaction easier for implementors of
+ /// [`chain::chainmonitor::Persist`]. On success this method returns the provided transaction
+ /// signing the input at `input_idx`. This method will only produce a valid signature for
+ /// a transaction spending the `to_local` output of a commitment transaction, i.e. this cannot
+ /// be used for revoked HTLC outputs.
+ ///
+ /// `Value` is the value of the output being spent by the input at `input_idx`, committed
+ /// in the BIP 143 signature.
+ ///
+ /// This method will only succeed if this monitor has received the revocation secret for the
+ /// provided `commitment_number`. If a commitment number is provided that does not correspond
+ /// to the commitment transaction being revoked, this will return a signed transaction, but
+ /// the signature will not be valid.
+ ///
+ /// [`EcdsaChannelSigner::sign_justice_revoked_output`]: crate::sign::EcdsaChannelSigner::sign_justice_revoked_output
+ /// [`Persist`]: crate::chain::chainmonitor::Persist
+ pub fn sign_to_local_justice_tx(&self, justice_tx: Transaction, input_idx: usize, value: u64, commitment_number: u64) -> Result<Transaction, ()> {
+ self.inner.lock().unwrap().sign_to_local_justice_tx(justice_tx, input_idx, value, commitment_number)
}
pub(crate) fn get_min_seen_secret(&self) -> u64 {
self.inner.lock().unwrap().counterparty_node_id
}
- /// Used by ChannelManager deserialization to broadcast the latest holder state if its copy of
- /// the Channel was out-of-date.
+ /// Used by [`ChannelManager`] deserialization to broadcast the latest holder state if its copy
+ /// of the channel state was out-of-date.
///
/// You may also use this to broadcast the latest local commitment transaction, either because
- /// a monitor update failed with [`ChannelMonitorUpdateStatus::PermanentFailure`] or because we've
- /// fallen behind (i.e. we've received proof that our counterparty side knows a revocation
- /// secret we gave them that they shouldn't know).
+ /// a monitor update failed or because we've fallen behind (i.e. we've received proof that our
+ /// counterparty side knows a revocation secret we gave them that they shouldn't know).
///
/// Broadcasting these transactions in the second case is UNSAFE, as they allow counterparty
/// side to punish you. Nevertheless you may want to broadcast them if counterparty doesn't
/// close channel with their commitment transaction after a substantial amount of time. Best
/// may be to contact the other node operator out-of-band to coordinate other options available
- /// to you. In any-case, the choice is up to you.
+ /// to you.
///
- /// [`ChannelMonitorUpdateStatus::PermanentFailure`]: super::ChannelMonitorUpdateStatus::PermanentFailure
+ /// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager
pub fn get_latest_holder_commitment_txn<L: Deref>(&self, logger: &L) -> Vec<Transaction>
where L::Target: Logger {
self.inner.lock().unwrap().get_latest_holder_commitment_txn(logger)
if let Some(conf_thresh) = holder_delayed_output_pending {
debug_assert!(holder_commitment);
return Some(Balance::ClaimableAwaitingConfirmations {
- claimable_amount_satoshis: htlc.amount_msat / 1000,
+ amount_satoshis: htlc.amount_msat / 1000,
confirmation_height: conf_thresh,
});
} else if htlc_resolved.is_some() && !htlc_output_spend_pending {
debug_assert!(!htlc.offered || htlc_spend_pending.is_none() || !htlc_spend_pending.unwrap().1,
"We don't (currently) generate preimage claims against revoked outputs, where did you get one?!");
return Some(Balance::CounterpartyRevokedOutputClaimable {
- claimable_amount_satoshis: htlc.amount_msat / 1000,
+ amount_satoshis: htlc.amount_msat / 1000,
});
}
} else if htlc.offered == holder_commitment {
// and awaiting confirmations on it.
if let Some(conf_thresh) = holder_timeout_spend_pending {
return Some(Balance::ClaimableAwaitingConfirmations {
- claimable_amount_satoshis: htlc.amount_msat / 1000,
+ amount_satoshis: htlc.amount_msat / 1000,
confirmation_height: conf_thresh,
});
} else {
return Some(Balance::MaybeTimeoutClaimableHTLC {
- claimable_amount_satoshis: htlc.amount_msat / 1000,
+ amount_satoshis: htlc.amount_msat / 1000,
claimable_height: htlc.cltv_expiry,
payment_hash: htlc.payment_hash,
});
debug_assert!(holder_timeout_spend_pending.is_none());
if let Some((conf_thresh, true)) = htlc_spend_pending {
return Some(Balance::ClaimableAwaitingConfirmations {
- claimable_amount_satoshis: htlc.amount_msat / 1000,
+ amount_satoshis: htlc.amount_msat / 1000,
confirmation_height: conf_thresh,
});
} else {
return Some(Balance::ContentiousClaimable {
- claimable_amount_satoshis: htlc.amount_msat / 1000,
+ amount_satoshis: htlc.amount_msat / 1000,
timeout_height: htlc.cltv_expiry,
payment_hash: htlc.payment_hash,
payment_preimage: *payment_preimage,
}
} else if htlc_resolved.is_none() {
return Some(Balance::MaybePreimageClaimableHTLC {
- claimable_amount_satoshis: htlc.amount_msat / 1000,
+ amount_satoshis: htlc.amount_msat / 1000,
expiry_height: htlc.cltv_expiry,
payment_hash: htlc.payment_hash,
});
} else { None }
}) {
res.push(Balance::ClaimableAwaitingConfirmations {
- claimable_amount_satoshis: value,
+ amount_satoshis: value,
confirmation_height: conf_thresh,
});
} else {
descriptor: SpendableOutputDescriptor::StaticOutput { output, .. }
} = &event.event {
res.push(Balance::ClaimableAwaitingConfirmations {
- claimable_amount_satoshis: output.value,
+ amount_satoshis: output.value,
confirmation_height: event.confirmation_threshold(),
});
if let Some(confirmed_to_self_idx) = confirmed_counterparty_output.map(|(idx, _)| idx) {
.is_output_spend_pending(&BitcoinOutPoint::new(txid, confirmed_to_self_idx));
if output_spendable {
res.push(Balance::CounterpartyRevokedOutputClaimable {
- claimable_amount_satoshis: amt,
+ amount_satoshis: amt,
});
}
} else {
walk_htlcs!(true, false, us.current_holder_commitment_tx.htlc_outputs.iter().map(|(a, _, _)| a));
if let Some(conf_thresh) = pending_commitment_tx_conf_thresh {
res.push(Balance::ClaimableAwaitingConfirmations {
- claimable_amount_satoshis: us.current_holder_commitment_tx.to_self_value_sat,
+ amount_satoshis: us.current_holder_commitment_tx.to_self_value_sat,
confirmation_height: conf_thresh,
});
}
walk_htlcs!(true, false, prev_commitment.htlc_outputs.iter().map(|(a, _, _)| a));
if let Some(conf_thresh) = pending_commitment_tx_conf_thresh {
res.push(Balance::ClaimableAwaitingConfirmations {
- claimable_amount_satoshis: prev_commitment.to_self_value_sat,
+ amount_satoshis: prev_commitment.to_self_value_sat,
confirmation_height: conf_thresh,
});
}
// neither us nor our counterparty misbehaved. At worst we've under-estimated
// the amount we can claim as we'll punish a misbehaving counterparty.
res.push(Balance::ClaimableAwaitingConfirmations {
- claimable_amount_satoshis: us.current_holder_commitment_tx.to_self_value_sat,
+ amount_satoshis: us.current_holder_commitment_tx.to_self_value_sat,
confirmation_height: conf_thresh,
});
}
if htlc.transaction_output_index.is_none() { continue; }
if htlc.offered {
res.push(Balance::MaybeTimeoutClaimableHTLC {
- claimable_amount_satoshis: htlc.amount_msat / 1000,
+ amount_satoshis: htlc.amount_msat / 1000,
claimable_height: htlc.cltv_expiry,
payment_hash: htlc.payment_hash,
});
// As long as the HTLC is still in our latest commitment state, treat
// it as potentially claimable, even if it has long-since expired.
res.push(Balance::MaybePreimageClaimableHTLC {
- claimable_amount_satoshis: htlc.amount_msat / 1000,
+ amount_satoshis: htlc.amount_msat / 1000,
expiry_height: htlc.cltv_expiry,
payment_hash: htlc.payment_hash,
});
}
}
res.push(Balance::ClaimableOnChannelClose {
- claimable_amount_satoshis: us.current_holder_commitment_tx.to_self_value_sat + claimable_inbound_htlc_value_sat,
+ amount_satoshis: us.current_holder_commitment_tx.to_self_value_sat + claimable_inbound_htlc_value_sat,
});
}
},
};
log_trace!($logger, "Failing HTLC with payment_hash {} from {} counterparty commitment tx due to broadcast of {} commitment transaction {}, waiting for confirmation (at height {})",
- log_bytes!(htlc.payment_hash.0), $commitment_tx, $commitment_tx_type,
+ &htlc.payment_hash, $commitment_tx, $commitment_tx_type,
$commitment_txid_confirmed, entry.confirmation_threshold());
$self.onchain_events_awaiting_threshold_conf.push(entry);
}
Ok(())
}
+ pub(crate) fn provide_initial_counterparty_commitment_tx<L: Deref>(
+ &mut self, txid: Txid, htlc_outputs: Vec<(HTLCOutputInCommitment, Option<Box<HTLCSource>>)>,
+ commitment_number: u64, their_per_commitment_point: PublicKey, feerate_per_kw: u32,
+ to_broadcaster_value: u64, to_countersignatory_value: u64, logger: &L
+ )
+ where L::Target: Logger
+ {
+ self.initial_counterparty_commitment_info = Some((their_per_commitment_point.clone(),
+ feerate_per_kw, to_broadcaster_value, to_countersignatory_value));
+
+ #[cfg(debug_assertions)] {
+ let rebuilt_commitment_tx = self.initial_counterparty_commitment_tx().unwrap();
+ debug_assert_eq!(rebuilt_commitment_tx.trust().txid(), txid);
+ }
+
+ self.provide_latest_counterparty_commitment_tx(txid, htlc_outputs, commitment_number,
+ their_per_commitment_point, logger);
+ }
+
pub(crate) fn provide_latest_counterparty_commitment_tx<L: Deref>(&mut self, txid: Txid, htlc_outputs: Vec<(HTLCOutputInCommitment, Option<Box<HTLCSource>>)>, commitment_number: u64, their_per_commitment_point: PublicKey, logger: &L) where L::Target: Logger {
// TODO: Encrypt the htlc_outputs data with the single-hash of the commitment transaction
// so that a remote monitor doesn't learn anything unless there is a malicious close.
txs.push(tx);
}
broadcaster.broadcast_transactions(&txs);
- self.pending_monitor_events.push(MonitorEvent::CommitmentTxConfirmed(self.funding_info.0));
+ self.pending_monitor_events.push(MonitorEvent::HolderForceClosed(self.funding_info.0));
}
pub fn update_monitor<B: Deref, F: Deref, L: Deref>(&mut self, updates: &ChannelMonitorUpdate, broadcaster: &B, fee_estimator: F, logger: &L) -> Result<(), ()>
ret = Err(());
}
}
- ChannelMonitorUpdateStep::LatestCounterpartyCommitmentTXInfo { commitment_txid, htlc_outputs, commitment_number, their_per_commitment_point } => {
+ ChannelMonitorUpdateStep::LatestCounterpartyCommitmentTXInfo { commitment_txid, htlc_outputs, commitment_number, their_per_commitment_point, .. } => {
log_trace!(logger, "Updating ChannelMonitor with latest counterparty commitment transaction info");
self.provide_latest_counterparty_commitment_tx(*commitment_txid, htlc_outputs.clone(), *commitment_number, *their_per_commitment_point, logger)
},
ChannelMonitorUpdateStep::CommitmentSecret { idx, secret } => {
log_trace!(logger, "Updating ChannelMonitor with commitment secret");
if let Err(e) = self.provide_secret(*idx, *secret) {
+ debug_assert!(false, "Latest counterparty commitment secret was invalid");
log_error!(logger, "Providing latest counterparty commitment secret failed/was refused:");
log_error!(logger, " {}", e);
ret = Err(());
}
} else if !self.holder_tx_signed {
log_error!(logger, "WARNING: You have a potentially-unsafe holder commitment transaction available to broadcast");
- log_error!(logger, " in channel monitor for channel {}!", log_bytes!(self.funding_info.0.to_channel_id()));
+ log_error!(logger, " in channel monitor for channel {}!", &self.funding_info.0.to_channel_id());
log_error!(logger, " Read the docs for ChannelMonitor::get_latest_holder_commitment_txn and take manual action!");
} else {
- // If we generated a MonitorEvent::CommitmentTxConfirmed, the ChannelManager
+ // If we generated a MonitorEvent::HolderForceClosed, the ChannelManager
// will still give us a ChannelForceClosed event with !should_broadcast, but we
// shouldn't print the scary warning above.
log_info!(logger, "Channel off-chain state closed after we broadcasted our latest commitment transaction.");
}
}
+ #[cfg(debug_assertions)] {
+ self.counterparty_commitment_txs_from_update(updates);
+ }
+
// If the updates succeeded and we were in an already closed channel state, then there's no
// need to refuse any updates we expect to receive afer seeing a confirmed commitment.
if ret.is_ok() && updates.update_id == CLOSED_CHANNEL_UPDATE_ID && self.latest_update_id == updates.update_id {
ret
}
- pub fn get_and_clear_pending_events(&mut self) -> Vec<Event> {
- let mut ret = Vec::new();
- mem::swap(&mut ret, &mut self.pending_events);
- for (claim_id, claim_event) in self.onchain_tx_handler.get_and_clear_pending_claim_events().drain(..) {
+ /// Gets the set of events that are repeated regularly (e.g. those which RBF bump
+ /// transactions). We're okay if we lose these on restart as they'll be regenerated for us at
+ /// some regular interval via [`ChannelMonitor::rebroadcast_pending_claims`].
+ pub(super) fn get_repeated_events(&mut self) -> Vec<Event> {
+ let pending_claim_events = self.onchain_tx_handler.get_and_clear_pending_claim_events();
+ let mut ret = Vec::with_capacity(pending_claim_events.len());
+ for (claim_id, claim_event) in pending_claim_events {
match claim_event {
ClaimEvent::BumpCommitment {
package_target_feerate_sat_per_1000_weight, commitment_tx, anchor_output_idx,
commitment_tx,
commitment_tx_fee_satoshis,
anchor_descriptor: AnchorDescriptor {
- channel_keys_id: self.channel_keys_id,
- channel_value_satoshis: self.channel_value_satoshis,
+ channel_derivation_parameters: ChannelDerivationParameters {
+ keys_id: self.channel_keys_id,
+ value_satoshis: self.channel_value_satoshis,
+ transaction_parameters: self.onchain_tx_handler.channel_transaction_parameters.clone(),
+ },
outpoint: BitcoinOutPoint {
txid: commitment_txid,
vout: anchor_output_idx,
let mut htlc_descriptors = Vec::with_capacity(htlcs.len());
for htlc in htlcs {
htlc_descriptors.push(HTLCDescriptor {
- channel_keys_id: self.channel_keys_id,
- channel_value_satoshis: self.channel_value_satoshis,
- channel_parameters: self.onchain_tx_handler.channel_transaction_parameters.clone(),
+ channel_derivation_parameters: ChannelDerivationParameters {
+ keys_id: self.channel_keys_id,
+ value_satoshis: self.channel_value_satoshis,
+ transaction_parameters: self.onchain_tx_handler.channel_transaction_parameters.clone(),
+ },
commitment_txid: htlc.commitment_txid,
per_commitment_number: htlc.per_commitment_number,
+ per_commitment_point: self.onchain_tx_handler.signer.get_per_commitment_point(
+ htlc.per_commitment_number, &self.onchain_tx_handler.secp_ctx,
+ ),
htlc: htlc.htlc,
preimage: htlc.preimage,
counterparty_sig: htlc.counterparty_sig,
ret
}
+ pub(crate) fn initial_counterparty_commitment_tx(&mut self) -> Option<CommitmentTransaction> {
+ let (their_per_commitment_point, feerate_per_kw, to_broadcaster_value,
+ to_countersignatory_value) = self.initial_counterparty_commitment_info?;
+ let htlc_outputs = vec![];
+
+ let commitment_tx = self.build_counterparty_commitment_tx(INITIAL_COMMITMENT_NUMBER,
+ &their_per_commitment_point, to_broadcaster_value, to_countersignatory_value,
+ feerate_per_kw, htlc_outputs);
+ Some(commitment_tx)
+ }
+
+ fn build_counterparty_commitment_tx(
+ &self, commitment_number: u64, their_per_commitment_point: &PublicKey,
+ to_broadcaster_value: u64, to_countersignatory_value: u64, feerate_per_kw: u32,
+ mut nondust_htlcs: Vec<(HTLCOutputInCommitment, Option<Box<HTLCSource>>)>
+ ) -> CommitmentTransaction {
+ let broadcaster_keys = &self.onchain_tx_handler.channel_transaction_parameters
+ .counterparty_parameters.as_ref().unwrap().pubkeys;
+ let countersignatory_keys =
+ &self.onchain_tx_handler.channel_transaction_parameters.holder_pubkeys;
+
+ let broadcaster_funding_key = broadcaster_keys.funding_pubkey;
+ let countersignatory_funding_key = countersignatory_keys.funding_pubkey;
+ let keys = TxCreationKeys::from_channel_static_keys(&their_per_commitment_point,
+ &broadcaster_keys, &countersignatory_keys, &self.onchain_tx_handler.secp_ctx);
+ let channel_parameters =
+ &self.onchain_tx_handler.channel_transaction_parameters.as_counterparty_broadcastable();
+
+ CommitmentTransaction::new_with_auxiliary_htlc_data(commitment_number,
+ to_broadcaster_value, to_countersignatory_value, broadcaster_funding_key,
+ countersignatory_funding_key, keys, feerate_per_kw, &mut nondust_htlcs,
+ channel_parameters)
+ }
+
+ pub(crate) fn counterparty_commitment_txs_from_update(&self, update: &ChannelMonitorUpdate) -> Vec<CommitmentTransaction> {
+ update.updates.iter().filter_map(|update| {
+ match update {
+ &ChannelMonitorUpdateStep::LatestCounterpartyCommitmentTXInfo { commitment_txid,
+ ref htlc_outputs, commitment_number, their_per_commitment_point,
+ feerate_per_kw: Some(feerate_per_kw),
+ to_broadcaster_value_sat: Some(to_broadcaster_value),
+ to_countersignatory_value_sat: Some(to_countersignatory_value) } => {
+
+ let nondust_htlcs = htlc_outputs.iter().filter_map(|(htlc, _)| {
+ htlc.transaction_output_index.map(|_| (htlc.clone(), None))
+ }).collect::<Vec<_>>();
+
+ let commitment_tx = self.build_counterparty_commitment_tx(commitment_number,
+ &their_per_commitment_point, to_broadcaster_value,
+ to_countersignatory_value, feerate_per_kw, nondust_htlcs);
+
+ debug_assert_eq!(commitment_tx.trust().txid(), commitment_txid);
+
+ Some(commitment_tx)
+ },
+ _ => None,
+ }
+ }).collect()
+ }
+
+ pub(crate) fn sign_to_local_justice_tx(
+ &self, mut justice_tx: Transaction, input_idx: usize, value: u64, commitment_number: u64
+ ) -> Result<Transaction, ()> {
+ let secret = self.get_secret(commitment_number).ok_or(())?;
+ let per_commitment_key = SecretKey::from_slice(&secret).map_err(|_| ())?;
+ let their_per_commitment_point = PublicKey::from_secret_key(
+ &self.onchain_tx_handler.secp_ctx, &per_commitment_key);
+
+ let revocation_pubkey = chan_utils::derive_public_revocation_key(
+ &self.onchain_tx_handler.secp_ctx, &their_per_commitment_point,
+ &self.holder_revocation_basepoint);
+ let delayed_key = chan_utils::derive_public_key(&self.onchain_tx_handler.secp_ctx,
+ &their_per_commitment_point,
+ &self.counterparty_commitment_params.counterparty_delayed_payment_base_key);
+ let revokeable_redeemscript = chan_utils::get_revokeable_redeemscript(&revocation_pubkey,
+ self.counterparty_commitment_params.on_counterparty_tx_csv, &delayed_key);
+
+ let sig = self.onchain_tx_handler.signer.sign_justice_revoked_output(
+ &justice_tx, input_idx, value, &per_commitment_key, &self.onchain_tx_handler.secp_ctx)?;
+ justice_tx.input[input_idx].witness.push_bitcoin_signature(&sig.serialize_der(), EcdsaSighashType::All);
+ justice_tx.input[input_idx].witness.push(&[1u8]);
+ justice_tx.input[input_idx].witness.push(revokeable_redeemscript.as_bytes());
+ Ok(justice_tx)
+ }
+
/// Can only fail if idx is < get_min_seen_secret
fn get_secret(&self, idx: u64) -> Option<[u8; 32]> {
self.commitment_secrets.get_secret(idx)
if prevout.txid == self.funding_info.0.txid && prevout.vout == self.funding_info.0.index as u32 {
let mut balance_spendable_csv = None;
log_info!(logger, "Channel {} closed by funding output spend in txid {}.",
- log_bytes!(self.funding_info.0.to_channel_id()), txid);
+ &self.funding_info.0.to_channel_id(), txid);
self.funding_spend_seen = true;
let mut commitment_tx_to_counterparty_output = None;
if (tx.input[0].sequence.0 >> 8*3) as u8 == 0x80 && (tx.lock_time.0 >> 8*3) as u8 == 0x20 {
let funding_outp = HolderFundingOutput::build(self.funding_redeemscript.clone(), self.channel_value_satoshis, self.onchain_tx_handler.channel_type_features().clone());
let commitment_package = PackageTemplate::build_package(self.funding_info.0.txid.clone(), self.funding_info.0.index as u32, PackageSolvingData::HolderFundingOutput(funding_outp), self.best_block.height(), self.best_block.height());
claimable_outpoints.push(commitment_package);
- self.pending_monitor_events.push(MonitorEvent::CommitmentTxConfirmed(self.funding_info.0));
- let commitment_tx = self.onchain_tx_handler.get_fully_signed_holder_tx(&self.funding_redeemscript);
+ self.pending_monitor_events.push(MonitorEvent::HolderForceClosed(self.funding_info.0));
+ // Although we aren't signing the transaction directly here, the transaction will be signed
+ // in the claim that is queued to OnchainTxHandler. We set holder_tx_signed here to reject
+ // new channel updates.
self.holder_tx_signed = true;
// We can't broadcast our HTLC transactions while the commitment transaction is
// unconfirmed. We'll delay doing so until we detect the confirmed commitment in
// assuming it gets confirmed in the next block. Sadly, we have code which considers
// "not yet confirmed" things as discardable, so we cannot do that here.
let (mut new_outpoints, _) = self.get_broadcasted_holder_claims(&self.current_holder_commitment_tx, self.best_block.height());
- let new_outputs = self.get_broadcasted_holder_watch_outputs(&self.current_holder_commitment_tx, &commitment_tx);
+ let unsigned_commitment_tx = self.onchain_tx_handler.get_unsigned_holder_commitment_tx();
+ let new_outputs = self.get_broadcasted_holder_watch_outputs(&self.current_holder_commitment_tx, &unsigned_commitment_tx);
if !new_outputs.is_empty() {
watch_outputs.push((self.current_holder_commitment_tx.txid.clone(), new_outputs));
}
}
log_debug!(logger, "HTLC {} failure update in {} has got enough confirmations to be passed upstream",
- log_bytes!(payment_hash.0), entry.txid);
+ &payment_hash, entry.txid);
self.pending_monitor_events.push(MonitorEvent::HTLCEvent(HTLCUpdate {
payment_hash,
payment_preimage: None,
OnchainEvent::MaturingOutput { descriptor } => {
log_debug!(logger, "Descriptor {} has got enough confirmations to be passed upstream", log_spendable!(descriptor));
self.pending_events.push(Event::SpendableOutputs {
- outputs: vec![descriptor]
+ outputs: vec![descriptor],
+ channel_id: Some(self.funding_info.0.to_channel_id()),
});
self.spendable_txids_confirmed.push(entry.txid);
},
(outbound_htlc && !$source_avail && (accepted_preimage_claim || offered_preimage_claim)) {
log_error!(logger, "Input spending {} ({}:{}) in {} resolves {} HTLC with payment hash {} with {}!",
$tx_info, input.previous_output.txid, input.previous_output.vout, tx.txid(),
- if outbound_htlc { "outbound" } else { "inbound" }, log_bytes!($htlc.payment_hash.0),
+ if outbound_htlc { "outbound" } else { "inbound" }, &$htlc.payment_hash,
if revocation_sig_claim { "revocation sig" } else { "preimage claim after we'd passed the HTLC resolution back. We can likely claim the HTLC output with a revocation claim" });
} else {
log_info!(logger, "Input spending {} ({}:{}) in {} resolves {} HTLC with payment hash {} with {}",
$tx_info, input.previous_output.txid, input.previous_output.vout, tx.txid(),
- if outbound_htlc { "outbound" } else { "inbound" }, log_bytes!($htlc.payment_hash.0),
+ if outbound_htlc { "outbound" } else { "inbound" }, &$htlc.payment_hash,
if revocation_sig_claim { "revocation sig" } else if accepted_preimage_claim || offered_preimage_claim { "preimage" } else { "timeout" });
}
}
commitment_tx_output_idx: Some(input.previous_output.vout),
},
};
- log_info!(logger, "Failing HTLC with payment_hash {} timeout by a spend tx, waiting for confirmation (at height {})", log_bytes!(payment_hash.0), entry.confirmation_threshold());
+ log_info!(logger, "Failing HTLC with payment_hash {} timeout by a spend tx, waiting for confirmation (at height {})", &payment_hash, entry.confirmation_threshold());
self.onchain_events_awaiting_threshold_conf.push(entry);
}
}
for _ in 0..pending_monitor_events_len {
let ev = match <u8 as Readable>::read(reader)? {
0 => MonitorEvent::HTLCEvent(Readable::read(reader)?),
- 1 => MonitorEvent::CommitmentTxConfirmed(funding_info.0),
+ 1 => MonitorEvent::HolderForceClosed(funding_info.0),
_ => return Err(DecodeError::InvalidValue)
};
pending_monitor_events.as_mut().unwrap().push(ev);
let mut confirmed_commitment_tx_counterparty_output = None;
let mut spendable_txids_confirmed = Some(Vec::new());
let mut counterparty_fulfilled_htlcs = Some(HashMap::new());
+ let mut initial_counterparty_commitment_info = None;
read_tlv_fields!(reader, {
(1, funding_spend_confirmed, option),
- (3, htlcs_resolved_on_chain, vec_type),
- (5, pending_monitor_events, vec_type),
+ (3, htlcs_resolved_on_chain, optional_vec),
+ (5, pending_monitor_events, optional_vec),
(7, funding_spend_seen, option),
(9, counterparty_node_id, option),
(11, confirmed_commitment_tx_counterparty_output, option),
- (13, spendable_txids_confirmed, vec_type),
+ (13, spendable_txids_confirmed, optional_vec),
(15, counterparty_fulfilled_htlcs, option),
+ (17, initial_counterparty_commitment_info, option),
});
Ok((best_block.block_hash(), ChannelMonitor::from_impl(ChannelMonitorImpl {
payment_preimages,
pending_monitor_events: pending_monitor_events.unwrap(),
pending_events,
+ is_processing_pending_events: false,
onchain_events_awaiting_threshold_conf,
outputs_to_watch,
best_block,
counterparty_node_id,
+ initial_counterparty_commitment_info,
})))
}
}
use crate::chain::chaininterface::LowerBoundedFeeEstimator;
use super::ChannelMonitorUpdateStep;
- use crate::{check_added_monitors, check_closed_broadcast, check_closed_event, check_spends, get_local_commitment_txn, get_monitor, get_route_and_payment_hash, unwrap_send_err};
+ use crate::{check_added_monitors, check_spends, get_local_commitment_txn, get_monitor, get_route_and_payment_hash, unwrap_send_err};
use crate::chain::{BestBlock, Confirm};
use crate::chain::channelmonitor::ChannelMonitor;
use crate::chain::package::{weight_offered_htlc, weight_received_htlc, weight_revoked_offered_htlc, weight_revoked_received_htlc, WEIGHT_REVOKED_OUTPUT};
use crate::chain::transaction::OutPoint;
use crate::sign::InMemorySigner;
- use crate::events::ClosureReason;
use crate::ln::{PaymentPreimage, PaymentHash};
use crate::ln::chan_utils;
use crate::ln::chan_utils::{HTLCOutputInCommitment, ChannelPublicKeys, ChannelTransactionParameters, HolderCommitmentTransaction, CounterpartyChannelTransactionParameters};
let (route, payment_hash, _, payment_secret) = get_route_and_payment_hash!(nodes[1], nodes[0], 100_000);
unwrap_send_err!(nodes[1].node.send_payment_with_route(&route, payment_hash,
RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_hash.0)
- ), true, APIError::ChannelUnavailable { ref err },
- assert!(err.contains("ChannelMonitor storage failure")));
- check_added_monitors!(nodes[1], 2); // After the failure we generate a close-channel monitor update
- check_closed_broadcast!(nodes[1], true);
- check_closed_event!(nodes[1], 1, ClosureReason::ProcessingError { err: "ChannelMonitor storage failure".to_string() });
+ ), false, APIError::MonitorUpdateInProgress, {});
+ check_added_monitors!(nodes[1], 1);
// Build a new ChannelMonitorUpdate which contains both the failing commitment tx update
// and provides the claim preimages for the two pending HTLCs. The first update generates
// an error, but the point of this test is to ensure the later updates are still applied.
let monitor_updates = nodes[1].chain_monitor.monitor_updates.lock().unwrap();
- let mut replay_update = monitor_updates.get(&channel.2).unwrap().iter().rev().skip(1).next().unwrap().clone();
+ let mut replay_update = monitor_updates.get(&channel.2).unwrap().iter().rev().next().unwrap().clone();
assert_eq!(replay_update.updates.len(), 1);
if let ChannelMonitorUpdateStep::LatestCounterpartyCommitmentTXInfo { .. } = replay_update.updates[0] {
} else { panic!(); }
}
/// An enum representing the status of a channel monitor update persistence.
+///
+/// These are generally used as the return value for an implementation of [`Persist`] which is used
+/// as the storage layer for a [`ChainMonitor`]. See the docs on [`Persist`] for a high-level
+/// explanation of how to handle different cases.
+///
+/// While `UnrecoverableError` is provided as a failure variant, it is not truly "handled" on the
+/// calling side, and generally results in an immediate panic. For those who prefer to avoid
+/// panics, `InProgress` can be used and you can retry the update operation in the background or
+/// shut down cleanly.
+///
+/// Note that channels should generally *not* be force-closed after a persistence failure.
+/// Force-closing with the latest [`ChannelMonitorUpdate`] applied may result in a transaction
+/// being broadcast which can only be spent by the latest [`ChannelMonitor`]! Thus, if the
+/// latest [`ChannelMonitor`] is not durably persisted anywhere and exists only in memory, naively
+/// calling [`ChannelManager::force_close_broadcasting_latest_txn`] *may result in loss of funds*!
+///
+/// [`Persist`]: chainmonitor::Persist
+/// [`ChainMonitor`]: chainmonitor::ChainMonitor
+/// [`ChannelManager::force_close_broadcasting_latest_txn`]: crate::ln::channelmanager::ChannelManager::force_close_broadcasting_latest_txn
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ChannelMonitorUpdateStatus {
/// The update has been durably persisted and all copies of the relevant [`ChannelMonitor`]
/// This includes performing any `fsync()` calls required to ensure the update is guaranteed to
/// be available on restart even if the application crashes.
Completed,
- /// Used to indicate a temporary failure (eg connection to a watchtower or remote backup of
- /// our state failed, but is expected to succeed at some point in the future).
- ///
- /// Such a failure will "freeze" a channel, preventing us from revoking old states or
- /// submitting new commitment transactions to the counterparty. Once the update(s) which failed
- /// have been successfully applied, a [`MonitorEvent::Completed`] can be used to restore the
- /// channel to an operational state.
+ /// Indicates that the update will happen asynchronously in the background or that a transient
+ /// failure occurred which is being retried in the background and will eventually complete.
///
- /// Note that a given [`ChannelManager`] will *never* re-generate a [`ChannelMonitorUpdate`].
- /// If you return this error you must ensure that it is written to disk safely before writing
- /// the latest [`ChannelManager`] state, or you should return [`PermanentFailure`] instead.
+ /// This will "freeze" a channel, preventing us from revoking old states or submitting a new
+ /// commitment transaction to the counterparty. Once the update(s) which are `InProgress` have
+ /// been completed, a [`MonitorEvent::Completed`] can be used to restore the channel to an
+ /// operational state.
///
/// Even when a channel has been "frozen", updates to the [`ChannelMonitor`] can continue to
/// occur (e.g. if an inbound HTLC which we forwarded was claimed upstream, resulting in us
/// until a [`MonitorEvent::Completed`] is provided, even if you return no error on a later
/// monitor update for the same channel.
///
- /// For deployments where a copy of ChannelMonitors and other local state are backed up in a
- /// remote location (with local copies persisted immediately), it is anticipated that all
+ /// For deployments where a copy of [`ChannelMonitor`]s and other local state are backed up in
+ /// a remote location (with local copies persisted immediately), it is anticipated that all
/// updates will return [`InProgress`] until the remote copies could be updated.
///
- /// [`PermanentFailure`]: ChannelMonitorUpdateStatus::PermanentFailure
+ /// Note that while fully asynchronous persistence of [`ChannelMonitor`] data is generally
+ /// reliable, this feature is considered beta, and a handful of edge-cases remain. Until the
+ /// remaining cases are fixed, in rare cases, *using this feature may lead to funds loss*.
+ ///
/// [`InProgress`]: ChannelMonitorUpdateStatus::InProgress
- /// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager
InProgress,
- /// Used to indicate no further channel monitor updates will be allowed (likely a disk failure
- /// or a remote copy of this [`ChannelMonitor`] is no longer reachable and thus not updatable).
- ///
- /// When this is returned, [`ChannelManager`] will force-close the channel but *not* broadcast
- /// our current commitment transaction. This avoids a dangerous case where a local disk failure
- /// (e.g. the Linux-default remounting of the disk as read-only) causes [`PermanentFailure`]s
- /// for all monitor updates. If we were to broadcast our latest commitment transaction and then
- /// restart, we could end up reading a previous [`ChannelMonitor`] and [`ChannelManager`],
- /// revoking our now-broadcasted state before seeing it confirm and losing all our funds.
- ///
- /// Note that this is somewhat of a tradeoff - if the disk is really gone and we may have lost
- /// the data permanently, we really should broadcast immediately. If the data can be recovered
- /// with manual intervention, we'd rather close the channel, rejecting future updates to it,
- /// and broadcast the latest state only if we have HTLCs to claim which are timing out (which
- /// we do as long as blocks are connected).
- ///
- /// In order to broadcast the latest local commitment transaction, you'll need to call
- /// [`ChannelMonitor::get_latest_holder_commitment_txn`] and broadcast the resulting
- /// transactions once you've safely ensured no further channel updates can be generated by your
- /// [`ChannelManager`].
+ /// Indicates that an update has failed and will not complete at any point in the future.
///
- /// Note that at least one final [`ChannelMonitorUpdate`] may still be provided, which must
- /// still be processed by a running [`ChannelMonitor`]. This final update will mark the
- /// [`ChannelMonitor`] as finalized, ensuring no further updates (e.g. revocation of the latest
- /// commitment transaction) are allowed.
+ /// Currently returning this variant will cause LDK to immediately panic to encourage immediate
+ /// shutdown. In the future this may be updated to disconnect peers and refuse to continue
+ /// normal operation without a panic.
///
- /// Note that even if you return a [`PermanentFailure`] due to unavailability of secondary
- /// [`ChannelMonitor`] copies, you should still make an attempt to store the update where
- /// possible to ensure you can claim HTLC outputs on the latest commitment transaction
- /// broadcasted later.
+ /// Applications which wish to perform an orderly shutdown after failure should consider
+ /// returning [`InProgress`] instead and simply shut down without ever marking the update
+ /// complete.
///
- /// In case of distributed watchtowers deployment, the new version must be written to disk, as
- /// state may have been stored but rejected due to a block forcing a commitment broadcast. This
- /// storage is used to claim outputs of rejected state confirmed onchain by another watchtower,
- /// lagging behind on block processing.
- ///
- /// [`PermanentFailure`]: ChannelMonitorUpdateStatus::PermanentFailure
- /// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager
- PermanentFailure,
+ /// [`InProgress`]: ChannelMonitorUpdateStatus::InProgress
+ UnrecoverableError,
}
/// The `Watch` trait defines behavior for watching on-chain activity pertaining to channels as
/// blocks are connected and disconnected.
///
/// Each channel is associated with a [`ChannelMonitor`]. Implementations of this trait are
-/// responsible for maintaining a set of monitors such that they can be updated accordingly as
-/// channel state changes and HTLCs are resolved. See method documentation for specific
-/// requirements.
-///
-/// Implementations **must** ensure that updates are successfully applied and persisted upon method
-/// completion. If an update fails with a [`PermanentFailure`], then it must immediately shut down
-/// without taking any further action such as persisting the current state.
-///
-/// If an implementation maintains multiple instances of a channel's monitor (e.g., by storing
-/// backup copies), then it must ensure that updates are applied across all instances. Otherwise, it
-/// could result in a revoked transaction being broadcast, allowing the counterparty to claim all
-/// funds in the channel. See [`ChannelMonitorUpdateStatus`] for more details about how to handle
-/// multiple instances.
+/// responsible for maintaining a set of monitors such that they can be updated as channel state
+/// changes. On each update, *all copies* of a [`ChannelMonitor`] must be updated and the update
+/// persisted to disk to ensure that the latest [`ChannelMonitor`] state can be reloaded if the
+/// application crashes.
///
-/// [`PermanentFailure`]: ChannelMonitorUpdateStatus::PermanentFailure
+/// See method documentation and [`ChannelMonitorUpdateStatus`] for specific requirements.
pub trait Watch<ChannelSigner: WriteableEcdsaChannelSigner> {
/// Watches a channel identified by `funding_txo` using `monitor`.
///
/// with any spends of outputs returned by [`get_outputs_to_watch`]. In practice, this means
/// calling [`block_connected`] and [`block_disconnected`] on the monitor.
///
- /// Note: this interface MUST error with [`ChannelMonitorUpdateStatus::PermanentFailure`] if
- /// the given `funding_txo` has previously been registered via `watch_channel`.
+ /// A return of `Err(())` indicates that the channel should immediately be force-closed without
+ /// broadcasting the funding transaction.
+ ///
+ /// If the given `funding_txo` has previously been registered via `watch_channel`, `Err(())`
+ /// must be returned.
///
/// [`get_outputs_to_watch`]: channelmonitor::ChannelMonitor::get_outputs_to_watch
/// [`block_connected`]: channelmonitor::ChannelMonitor::block_connected
/// [`block_disconnected`]: channelmonitor::ChannelMonitor::block_disconnected
- fn watch_channel(&self, funding_txo: OutPoint, monitor: ChannelMonitor<ChannelSigner>) -> ChannelMonitorUpdateStatus;
+ fn watch_channel(&self, funding_txo: OutPoint, monitor: ChannelMonitor<ChannelSigner>) -> Result<ChannelMonitorUpdateStatus, ()>;
/// Updates a channel identified by `funding_txo` by applying `update` to its monitor.
///
- /// Implementations must call [`update_monitor`] with the given update. See
- /// [`ChannelMonitorUpdateStatus`] for invariants around returning an error.
+ /// Implementations must call [`ChannelMonitor::update_monitor`] with the given update. This
+ /// may fail (returning an `Err(())`), in which case this should return
+ /// [`ChannelMonitorUpdateStatus::InProgress`] (and the update should never complete). This
+ /// generally implies the channel has been closed (either by the funding outpoint being spent
+ /// on-chain or the [`ChannelMonitor`] having decided to do so and broadcasted a transaction),
+ /// and the [`ChannelManager`] state will be updated once it sees the funding spend on-chain.
+ ///
+ /// In general, persistence failures should be retried after returning
+ /// [`ChannelMonitorUpdateStatus::InProgress`] and eventually complete. If a failure truly
+ /// cannot be retried, the node should shut down immediately after returning
+ /// [`ChannelMonitorUpdateStatus::UnrecoverableError`], see its documentation for more info.
///
- /// [`update_monitor`]: channelmonitor::ChannelMonitor::update_monitor
+ /// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager
fn update_channel(&self, funding_txo: OutPoint, update: &ChannelMonitorUpdate) -> ChannelMonitorUpdateStatus;
/// Returns any monitor events since the last call. Subsequent calls must only return new
}
/// A unique identifier to track each pending output claim within a [`ChannelMonitor`].
+///
+/// This is not exported to bindings users as we just use [u8; 32] directly.
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
pub struct ClaimId(pub [u8; 32]);
use bitcoin::secp256k1::{Secp256k1, ecdsa::Signature};
use bitcoin::secp256k1;
+use crate::chain::chaininterface::compute_feerate_sat_per_1000_weight;
use crate::sign::{ChannelSigner, EntropySource, SignerProvider};
use crate::ln::msgs::DecodeError;
use crate::ln::PaymentPreimage;
/// transaction causing it.
///
/// Used to determine when the on-chain event can be considered safe from a chain reorganization.
-#[derive(PartialEq, Eq)]
+#[derive(Clone, PartialEq, Eq)]
struct OnchainEventEntry {
txid: Txid,
height: u32,
/// Events for claims the [`OnchainTxHandler`] has generated. Once the events are considered safe
/// from a chain reorg, the [`OnchainTxHandler`] will act accordingly.
-#[derive(PartialEq, Eq)]
+#[derive(Clone, PartialEq, Eq)]
enum OnchainEvent {
/// A pending request has been claimed by a transaction spending the exact same set of outpoints
/// as the request. This claim can either be ours or from the counterparty. Once the claiming
}
/// The claim commonly referred to as the pre-signed second-stage HTLC transaction.
+#[derive(Clone, PartialEq, Eq)]
pub(crate) struct ExternalHTLCClaim {
pub(crate) commitment_txid: Txid,
pub(crate) per_commitment_number: u64,
// Represents the different types of claims for which events are yielded externally to satisfy said
// claims.
+#[derive(Clone, PartialEq, Eq)]
pub(crate) enum ClaimEvent {
/// Event yielded to signal that the commitment transaction fee must be bumped to claim any
/// encumbered funds and proceed to HTLC resolution, if any HTLCs exist.
/// OnchainTxHandler receives claiming requests, aggregates them if it's sound, broadcast and
/// do RBF bumping if possible.
+#[derive(Clone)]
pub struct OnchainTxHandler<ChannelSigner: WriteableEcdsaChannelSigner> {
destination_script: Script,
holder_commitment: HolderCommitmentTransaction,
return inputs.find_map(|input| match input {
// Commitment inputs with anchors support are the only untractable inputs supported
// thus far that require external funding.
- PackageSolvingData::HolderFundingOutput(..) => {
+ PackageSolvingData::HolderFundingOutput(output) => {
debug_assert_eq!(tx.txid(), self.holder_commitment.trust().txid(),
"Holder commitment transaction mismatch");
+
+ let conf_target = ConfirmationTarget::HighPriority;
+ 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 {
+ let fee_sat = input_amount_sat - tx.output.iter().map(|output| output.value).sum::<u64>();
+ let commitment_tx_feerate_sat_per_1000_weight =
+ compute_feerate_sat_per_1000_weight(fee_sat, tx.weight() as u64);
+ if commitment_tx_feerate_sat_per_1000_weight >= package_target_feerate_sat_per_1000_weight {
+ log_debug!(logger, "Pre-signed {} already has feerate {} sat/kW above required {} sat/kW",
+ log_tx!(tx), commitment_tx_feerate_sat_per_1000_weight,
+ package_target_feerate_sat_per_1000_weight);
+ return Some((new_timer, 0, OnchainClaim::Tx(tx.clone())));
+ }
+ }
+
// We'll locate an anchor output we can spend within the commitment transaction.
let funding_pubkey = &self.channel_transaction_parameters.holder_pubkeys.funding_pubkey;
match chan_utils::get_anchor_output(&tx, funding_pubkey) {
Some((idx, _)) => {
// TODO: Use a lower confirmation target when both our and the
// counterparty's latest commitment don't have any HTLCs present.
- let conf_target = ConfirmationTarget::HighPriority;
- let package_target_feerate_sat_per_1000_weight = cached_request
- .compute_package_feerate(fee_estimator, conf_target, force_feerate_bump);
Some((
new_timer,
package_target_feerate_sat_per_1000_weight as u64,
) {
req.set_timer(new_timer);
req.set_feerate(new_feerate);
+ // Once a pending claim has an id assigned, it remains fixed until the claim is
+ // satisfied, regardless of whether the claim switches between different variants of
+ // `OnchainClaim`.
let claim_id = match claim {
OnchainClaim::Tx(tx) => {
log_info!(logger, "Broadcasting onchain {}", log_tx!(tx));
ret
}
+ pub(crate) fn get_unsigned_holder_commitment_tx(&self) -> &Transaction {
+ &self.holder_commitment.trust().built_transaction().transaction
+ }
+
//TODO: getting lastest holder transactions should be infallible and result in us "force-closing the channel", but we may
- // have empty holder commitment transaction if a ChannelMonitor is asked to force-close just after Channel::get_outbound_funding_created,
+ // have empty holder commitment transaction if a ChannelMonitor is asked to force-close just after OutboundV1Channel::get_funding_created,
// 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 {
#[derive(Clone, PartialEq, Eq)]
pub(crate) struct HolderFundingOutput {
funding_redeemscript: Script,
- funding_amount: Option<u64>,
+ pub(crate) funding_amount: Option<u64>,
channel_type_features: ChannelTypeFeatures,
}
_ => { mem::discriminant(self) == mem::discriminant(&input) }
}
}
+ fn as_tx_input(&self, previous_output: BitcoinOutPoint) -> TxIn {
+ let sequence = match self {
+ PackageSolvingData::RevokedOutput(_) => Sequence::ENABLE_RBF_NO_LOCKTIME,
+ PackageSolvingData::RevokedHTLCOutput(_) => Sequence::ENABLE_RBF_NO_LOCKTIME,
+ PackageSolvingData::CounterpartyOfferedHTLCOutput(outp) => if outp.channel_type_features.supports_anchors_zero_fee_htlc_tx() {
+ Sequence::from_consensus(1)
+ } else {
+ Sequence::ENABLE_RBF_NO_LOCKTIME
+ },
+ PackageSolvingData::CounterpartyReceivedHTLCOutput(outp) => if outp.channel_type_features.supports_anchors_zero_fee_htlc_tx() {
+ Sequence::from_consensus(1)
+ } else {
+ Sequence::ENABLE_RBF_NO_LOCKTIME
+ },
+ _ => {
+ debug_assert!(false, "This should not be reachable by 'untractable' or 'malleable with external funding' packages");
+ Sequence::ENABLE_RBF_NO_LOCKTIME
+ },
+ };
+ TxIn {
+ previous_output,
+ script_sig: Script::new(),
+ sequence,
+ witness: Witness::new(),
+ }
+ }
fn finalize_input<Signer: WriteableEcdsaChannelSigner>(&self, bumped_tx: &mut Transaction, i: usize, onchain_handler: &mut OnchainTxHandler<Signer>) -> bool {
match self {
PackageSolvingData::RevokedOutput(ref outp) => {
value,
}],
};
- for (outpoint, _) in self.inputs.iter() {
- bumped_tx.input.push(TxIn {
- previous_output: *outpoint,
- script_sig: Script::new(),
- sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
- witness: Witness::new(),
- });
+ for (outpoint, outp) in self.inputs.iter() {
+ bumped_tx.input.push(outp.as_tx_input(*outpoint));
}
for (i, (outpoint, out)) in self.inputs.iter().enumerate() {
log_debug!(logger, "Adding claiming input for outpoint {}:{}", outpoint.txid, outpoint.vout);
) -> u32 where F::Target: FeeEstimator {
let feerate_estimate = fee_estimator.bounded_sat_per_1000_weight(conf_target);
if self.feerate_previous != 0 {
- // If old feerate inferior to actual one given back by Fee Estimator, use it to compute new fee...
+ // Use the new fee estimate if it's higher than the one previously used.
if feerate_estimate as u64 > self.feerate_previous {
feerate_estimate
} else if !force_feerate_bump {
self.feerate_previous.try_into().unwrap_or(u32::max_value())
} else {
- // ...else just increase the previous feerate by 25% (because that's a nice number)
- (self.feerate_previous + (self.feerate_previous / 4)).try_into().unwrap_or(u32::max_value())
+ // Our fee estimate has decreased, but our transaction remains unconfirmed after
+ // using our previous fee estimate. This may point to an unreliable fee estimator,
+ // so we choose to bump our previous feerate by 25%, making sure we don't use a
+ // lower feerate or overpay by a large margin by limiting it to 5x the new fee
+ // estimate.
+ let previous_feerate = self.feerate_previous.try_into().unwrap_or(u32::max_value());
+ let mut new_feerate = previous_feerate.saturating_add(previous_feerate / 4);
+ if new_feerate > feerate_estimate * 5 {
+ new_feerate = cmp::max(feerate_estimate * 5, previous_feerate);
+ }
+ new_feerate
}
} else {
feerate_estimate
//! Types describing on-chain transactions.
+use crate::ln::ChannelId;
use bitcoin::hash_types::Txid;
+use bitcoin::hashes::Hash;
use bitcoin::blockdata::transaction::OutPoint as BitcoinOutPoint;
use bitcoin::blockdata::transaction::Transaction;
impl OutPoint {
/// Convert an `OutPoint` to a lightning channel id.
- pub fn to_channel_id(&self) -> [u8; 32] {
- let mut res = [0; 32];
- res[..].copy_from_slice(&self.txid[..]);
- res[30] ^= ((self.index >> 8) & 0xff) as u8;
- res[31] ^= ((self.index >> 0) & 0xff) as u8;
- res
+ pub fn to_channel_id(&self) -> ChannelId {
+ ChannelId::v1_from_funding_txid(&self.txid.as_inner(), self.index)
}
/// Converts this OutPoint into the OutPoint field as used by rust-bitcoin
assert_eq!(&OutPoint {
txid: tx.txid(),
index: 0
- }.to_channel_id(), &hex::decode("3e88dd7165faf7be58b3c5bb2c9c452aebef682807ea57080f62e6f6e113c25e").unwrap()[..]);
+ }.to_channel_id().0[..], &hex::decode("3e88dd7165faf7be58b3c5bb2c9c452aebef682807ea57080f62e6f6e113c25e").unwrap()[..]);
assert_eq!(&OutPoint {
txid: tx.txid(),
index: 1
- }.to_channel_id(), &hex::decode("3e88dd7165faf7be58b3c5bb2c9c452aebef682807ea57080f62e6f6e113c25f").unwrap()[..]);
+ }.to_channel_id().0[..], &hex::decode("3e88dd7165faf7be58b3c5bb2c9c452aebef682807ea57080f62e6f6e113c25f").unwrap()[..]);
}
}
// You may not use this file except in accordance with one or both of these
// licenses.
-//! Utitilies for bumping transactions originating from [`super::Event`]s.
+//! Utilities for bumping transactions originating from [`Event`]s.
+//!
+//! [`Event`]: crate::events::Event
-use core::convert::TryInto;
+use alloc::collections::BTreeMap;
use core::ops::Deref;
-use crate::chain::chaininterface::BroadcasterInterface;
+use crate::chain::chaininterface::{BroadcasterInterface, fee_for_weight};
use crate::chain::ClaimId;
-use crate::events::Event;
use crate::io_extras::sink;
+use crate::ln::channel::ANCHOR_OUTPUT_VALUE_SATOSHI;
use crate::ln::chan_utils;
use crate::ln::chan_utils::{
ANCHOR_INPUT_WITNESS_WEIGHT, HTLC_SUCCESS_INPUT_ANCHOR_WITNESS_WEIGHT,
use crate::ln::features::ChannelTypeFeatures;
use crate::ln::PaymentPreimage;
use crate::prelude::*;
-use crate::sign::{ChannelSigner, EcdsaChannelSigner, SignerProvider};
+use crate::sign::{EcdsaChannelSigner, SignerProvider, WriteableEcdsaChannelSigner};
use crate::sync::Mutex;
use crate::util::logger::Logger;
const BASE_INPUT_WEIGHT: u64 = BASE_INPUT_SIZE * WITNESS_SCALE_FACTOR as u64;
-// TODO: Define typed abstraction over feerates to handle their conversions.
-fn compute_feerate_sat_per_1000_weight(fee_sat: u64, weight: u64) -> u32 {
- (fee_sat * 1000 / weight).try_into().unwrap_or(u32::max_value())
-}
-const fn fee_for_weight(feerate_sat_per_1000_weight: u32, weight: u64) -> u64 {
- ((feerate_sat_per_1000_weight as u64 * weight) + 1000 - 1) / 1000
+/// 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 unique identifier used along with `channel_value_satoshis` to re-derive the
- /// [`InMemorySigner`] required to sign `input`.
- ///
- /// [`InMemorySigner`]: crate::sign::InMemorySigner
- pub channel_keys_id: [u8; 32],
- /// The value in satoshis of the channel we're attempting to spend the anchor output of. This is
- /// used along with `channel_keys_id` to re-derive the [`InMemorySigner`] required to sign
- /// `input`.
- ///
- /// [`InMemorySigner`]: crate::sign::InMemorySigner
- pub channel_value_satoshis: u64,
+ /// The parameters required to derive the signer for the anchor input.
+ pub channel_derivation_parameters: ChannelDerivationParameters,
/// The transaction input's outpoint corresponding to the commitment transaction's anchor
/// output.
pub outpoint: OutPoint,
}
+impl AnchorDescriptor {
+ /// Returns the UTXO to be spent by the anchor input, which can be obtained via
+ /// [`Self::unsigned_tx_input`].
+ pub fn previous_utxo(&self) -> TxOut {
+ TxOut {
+ script_pubkey: self.witness_script().to_v0_p2wsh(),
+ value: ANCHOR_OUTPUT_VALUE_SATOSHI,
+ }
+ }
+
+ /// Returns the unsigned transaction input spending the anchor output in the commitment
+ /// transaction.
+ pub fn unsigned_tx_input(&self) -> TxIn {
+ TxIn {
+ previous_output: self.outpoint.clone(),
+ script_sig: Script::new(),
+ sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
+ witness: Witness::new(),
+ }
+ }
+
+ /// Returns the witness script of the anchor output in the commitment transaction.
+ pub fn witness_script(&self) -> Script {
+ let channel_params = self.channel_derivation_parameters.transaction_parameters.as_holder_broadcastable();
+ chan_utils::get_anchor_redeemscript(&channel_params.broadcaster_pubkeys().funding_pubkey)
+ }
+
+ /// Returns the fully signed witness required to spend the anchor output in the commitment
+ /// transaction.
+ pub fn tx_input_witness(&self, signature: &Signature) -> Witness {
+ let channel_params = self.channel_derivation_parameters.transaction_parameters.as_holder_broadcastable();
+ chan_utils::build_anchor_input_witness(&channel_params.broadcaster_pubkeys().funding_pubkey, signature)
+ }
+
+ /// Derives the channel signer required to sign the anchor 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 descriptor used to sign for a commitment transaction's HTLC output.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct HTLCDescriptor {
- /// A unique identifier used along with `channel_value_satoshis` to re-derive the
- /// [`InMemorySigner`] required to sign `input`.
- ///
- /// [`InMemorySigner`]: crate::sign::InMemorySigner
- pub channel_keys_id: [u8; 32],
- /// The value in satoshis of the channel we're attempting to spend the anchor output of. This is
- /// used along with `channel_keys_id` to re-derive the [`InMemorySigner`] required to sign
- /// `input`.
- ///
- /// [`InMemorySigner`]: crate::sign::InMemorySigner
- pub channel_value_satoshis: u64,
- /// The necessary channel parameters that need to be provided to the re-derived
- /// [`InMemorySigner`] through [`ChannelSigner::provide_channel_parameters`].
- ///
- /// [`InMemorySigner`]: crate::sign::InMemorySigner
- /// [`ChannelSigner::provide_channel_parameters`]: crate::sign::ChannelSigner::provide_channel_parameters
- pub channel_parameters: ChannelTransactionParameters,
+ /// 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
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 {
/// 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, per_commitment_point: &PublicKey, secp: &Secp256k1<C>
- ) -> TxOut {
- let channel_params = self.channel_parameters.as_holder_broadcastable();
+ 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, per_commitment_point, &broadcaster_keys.delayed_payment_basepoint
+ secp, &self.per_commitment_point, &broadcaster_keys.delayed_payment_basepoint
);
let counterparty_revocation_key = chan_utils::derive_public_revocation_key(
- secp, per_commitment_point, &counterparty_keys.revocation_basepoint
+ secp, &self.per_commitment_point, &counterparty_keys.revocation_basepoint
);
chan_utils::build_htlc_output(
0 /* feerate_per_kw */, channel_params.contest_delay(), &self.htlc,
}
/// Returns the witness script of the HTLC output in the commitment transaction.
- pub fn witness_script<C: secp256k1::Signing + secp256k1::Verification>(
- &self, per_commitment_point: &PublicKey, secp: &Secp256k1<C>
- ) -> Script {
- let channel_params = self.channel_parameters.as_holder_broadcastable();
+ 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, per_commitment_point, &broadcaster_keys.htlc_basepoint
+ secp, &self.per_commitment_point, &broadcaster_keys.htlc_basepoint
);
let counterparty_htlc_key = chan_utils::derive_public_key(
- secp, per_commitment_point, &counterparty_keys.htlc_basepoint
+ secp, &self.per_commitment_point, &counterparty_keys.htlc_basepoint
);
let counterparty_revocation_key = chan_utils::derive_public_revocation_key(
- secp, per_commitment_point, &counterparty_keys.revocation_basepoint
+ 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,
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.
/// broadcast first, as the child anchor transaction depends on it.
///
/// The consumer should be able to sign for any of the additional inputs included within the
- /// child anchor transaction. To sign its anchor input, an [`InMemorySigner`] should be
- /// re-derived through [`KeysManager::derive_channel_keys`] with the help of
- /// [`AnchorDescriptor::channel_keys_id`] and [`AnchorDescriptor::channel_value_satoshis`]. The
- /// anchor input signature can be computed with [`EcdsaChannelSigner::sign_holder_anchor_input`],
- /// which can then be provided to [`build_anchor_input_witness`] along with the `funding_pubkey`
- /// to obtain the full witness required to spend.
+ /// child anchor transaction. To sign its anchor input, an [`EcdsaChannelSigner`] should be
+ /// re-derived through [`AnchorDescriptor::derive_channel_signer`]. The anchor input signature
+ /// can be computed with [`EcdsaChannelSigner::sign_holder_anchor_input`], which can then be
+ /// provided to [`build_anchor_input_witness`] along with the `funding_pubkey` to obtain the
+ /// full witness required to spend.
///
/// It is possible to receive more than one instance of this event if a valid child anchor
/// transaction is never broadcast or is but not with a sufficient fee to be mined. Care should
/// an empty `pending_htlcs`), confirmation of the commitment transaction can be considered to
/// be not urgent.
///
- /// [`InMemorySigner`]: crate::sign::InMemorySigner
- /// [`KeysManager::derive_channel_keys`]: crate::sign::KeysManager::derive_channel_keys
+ /// [`EcdsaChannelSigner`]: crate::sign::EcdsaChannelSigner
/// [`EcdsaChannelSigner::sign_holder_anchor_input`]: crate::sign::EcdsaChannelSigner::sign_holder_anchor_input
/// [`build_anchor_input_witness`]: crate::ln::chan_utils::build_anchor_input_witness
ChannelClose {
/// broadcast by the consumer of the event.
///
/// The consumer should be able to sign for any of the non-HTLC inputs added to the resulting
- /// HTLC transaction. To sign HTLC inputs, an [`InMemorySigner`] should be re-derived through
- /// [`KeysManager::derive_channel_keys`] with the help of `channel_keys_id` and
- /// `channel_value_satoshis`. Each HTLC input's signature can be computed with
- /// [`EcdsaChannelSigner::sign_holder_htlc_transaction`], which can then be provided to
- /// [`HTLCDescriptor::tx_input_witness`] to obtain the fully signed witness required to spend.
+ /// HTLC transaction. To sign HTLC inputs, an [`EcdsaChannelSigner`] should be re-derived
+ /// through [`HTLCDescriptor::derive_channel_signer`]. Each HTLC input's signature can be
+ /// computed with [`EcdsaChannelSigner::sign_holder_htlc_transaction`], which can then be
+ /// provided to [`HTLCDescriptor::tx_input_witness`] to obtain the fully signed witness required
+ /// to spend.
///
/// It is possible to receive more than one instance of this event if a valid HTLC transaction
/// is never broadcast or is but not with a sufficient fee to be mined. Care should be taken by
/// longer able to commit external confirmed funds to the HTLC transaction or the fee committed
/// to the HTLC transaction is greater in value than the HTLCs being claimed.
///
- /// [`InMemorySigner`]: crate::sign::InMemorySigner
- /// [`KeysManager::derive_channel_keys`]: crate::sign::KeysManager::derive_channel_keys
+ /// [`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 {
/// An input that must be included in a transaction when performing coin selection through
/// [`CoinSelectionSource::select_confirmed_utxos`]. It is guaranteed to be a SegWit input, so it
/// must have an empty [`TxIn::script_sig`] when spent.
+#[derive(Clone, Debug, Hash, PartialOrd, Ord, PartialEq, Eq)]
pub struct Input {
/// The unique identifier of the input.
pub outpoint: OutPoint,
+ /// The UTXO being spent by the input.
+ pub previous_utxo: TxOut,
/// The upper-bound weight consumed by the input's full [`TxIn::script_sig`] and
/// [`TxIn::witness`], each with their lengths included, required to satisfy the output's
/// script.
/// An unspent transaction output that is available to spend resulting from a successful
/// [`CoinSelection`] attempt.
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, Hash, PartialOrd, Ord, PartialEq, Eq)]
pub struct Utxo {
/// The unique identifier of the output.
pub outpoint: OutPoint,
/// The result of a successful coin selection attempt for a transaction requiring additional UTXOs
/// to cover its fees.
+#[derive(Clone, Debug)]
pub struct CoinSelection {
/// The set of UTXOs (with at least 1 confirmation) to spend and use within a transaction
/// requiring additional fees.
- confirmed_utxos: Vec<Utxo>,
+ pub confirmed_utxos: Vec<Utxo>,
/// An additional output tracking whether any change remained after coin selection. This output
/// should always have a value above dust for its given `script_pubkey`. It should not be
/// spent until the transaction it belongs to confirms to ensure mempool descendant limits are
/// not met. This implies no other party should be able to spend it except us.
- change_output: Option<TxOut>,
+ pub change_output: Option<TxOut>,
}
/// An abstraction over a bitcoin wallet that can perform coin selection over a set of UTXOs and can
/// which UTXOs to double spend is left to the implementation, but it must strive to keep the
/// set of other claims being double spent to a minimum.
fn select_confirmed_utxos(
- &self, claim_id: ClaimId, must_spend: &[Input], must_pay_to: &[TxOut],
+ &self, claim_id: ClaimId, must_spend: Vec<Input>, must_pay_to: &[TxOut],
target_feerate_sat_per_1000_weight: u32,
) -> Result<CoinSelection, ()>;
/// Signs and provides the full witness for all inputs within the transaction known to the
/// trait (i.e., any provided via [`CoinSelectionSource::select_confirmed_utxos`]).
- fn sign_tx(&self, tx: &mut Transaction) -> Result<(), ()>;
+ fn sign_tx(&self, tx: Transaction) -> Result<Transaction, ()>;
}
/// An alternative to [`CoinSelectionSource`] that can be implemented and used along [`Wallet`] to
/// Signs and provides the full [`TxIn::script_sig`] and [`TxIn::witness`] for all inputs within
/// the transaction known to the wallet (i.e., any provided via
/// [`WalletSource::list_confirmed_utxos`]).
- fn sign_tx(&self, tx: &mut Transaction) -> Result<(), ()>;
+ fn sign_tx(&self, tx: Transaction) -> Result<Transaction, ()>;
}
/// A wrapper over [`WalletSource`] that implements [`CoinSelection`] by preferring UTXOs that would
/// avoid conflicting double spends. If not enough UTXOs are available to do so, conflicting double
/// spends may happen.
-pub struct Wallet<W: Deref> where W::Target: WalletSource {
+pub struct Wallet<W: Deref, L: Deref>
+where
+ W::Target: WalletSource,
+ L::Target: Logger
+{
source: W,
+ logger: L,
// TODO: Do we care about cleaning this up once the UTXOs have a confirmed spend? We can do so
// by checking whether any UTXOs that exist in the map are no longer returned in
// `list_confirmed_utxos`.
locked_utxos: Mutex<HashMap<OutPoint, ClaimId>>,
}
-impl<W: Deref> Wallet<W> where W::Target: WalletSource {
+impl<W: Deref, L: Deref> Wallet<W, L>
+where
+ W::Target: WalletSource,
+ L::Target: Logger
+{
/// Returns a new instance backed by the given [`WalletSource`] that serves as an implementation
/// of [`CoinSelectionSource`].
- pub fn new(source: W) -> Self {
- Self { source, locked_utxos: Mutex::new(HashMap::new()) }
+ pub fn new(source: W, logger: L) -> Self {
+ Self { source, logger, locked_utxos: Mutex::new(HashMap::new()) }
}
/// Performs coin selection on the set of UTXOs obtained from
fn select_confirmed_utxos_internal(
&self, utxos: &[Utxo], claim_id: ClaimId, force_conflicting_utxo_spend: bool,
tolerate_high_network_feerates: bool, target_feerate_sat_per_1000_weight: u32,
- preexisting_tx_weight: u64, target_amount_sat: u64,
+ preexisting_tx_weight: u64, input_amount_sat: u64, target_amount_sat: u64,
) -> Result<CoinSelection, ()> {
let mut locked_utxos = self.locked_utxos.lock().unwrap();
let mut eligible_utxos = utxos.iter().filter_map(|utxo| {
if let Some(utxo_claim_id) = locked_utxos.get(&utxo.outpoint) {
if *utxo_claim_id != claim_id && !force_conflicting_utxo_spend {
+ log_trace!(self.logger, "Skipping UTXO {} to prevent conflicting spend", utxo.outpoint);
return None;
}
}
if should_spend {
Some((utxo, fee_to_spend_utxo))
} else {
+ log_trace!(self.logger, "Skipping UTXO {} due to dust proximity after spend", utxo.outpoint);
None
}
}).collect::<Vec<_>>();
eligible_utxos.sort_unstable_by_key(|(utxo, _)| utxo.output.value);
- let mut selected_amount = 0;
+ let mut selected_amount = input_amount_sat;
let mut total_fees = fee_for_weight(target_feerate_sat_per_1000_weight, preexisting_tx_weight);
let mut selected_utxos = Vec::new();
for (utxo, fee_to_spend_utxo) in eligible_utxos {
selected_utxos.push(utxo.clone());
}
if selected_amount < target_amount_sat + total_fees {
+ log_debug!(self.logger, "Insufficient funds to meet target feerate {} sat/kW",
+ target_feerate_sat_per_1000_weight);
return Err(());
}
for utxo in &selected_utxos {
);
let change_output_amount = remaining_amount.saturating_sub(change_output_fee);
let change_output = if change_output_amount < change_script.dust_value().to_sat() {
+ log_debug!(self.logger, "Coin selection attempt did not yield change output");
None
} else {
Some(TxOut { script_pubkey: change_script, value: change_output_amount })
}
}
-impl<W: Deref> CoinSelectionSource for Wallet<W> where W::Target: WalletSource {
+impl<W: Deref, L: Deref> CoinSelectionSource for Wallet<W, L>
+where
+ W::Target: WalletSource,
+ L::Target: Logger
+{
fn select_confirmed_utxos(
- &self, claim_id: ClaimId, must_spend: &[Input], must_pay_to: &[TxOut],
+ &self, claim_id: ClaimId, must_spend: Vec<Input>, must_pay_to: &[TxOut],
target_feerate_sat_per_1000_weight: u32,
) -> Result<CoinSelection, ()> {
let utxos = self.source.list_confirmed_utxos()?;
let preexisting_tx_weight = 2 /* segwit marker & flag */ + total_input_weight +
((BASE_TX_SIZE + total_output_size) * WITNESS_SCALE_FACTOR as u64);
+ let input_amount_sat: u64 = must_spend.iter().map(|input| input.previous_utxo.value).sum();
let target_amount_sat = must_pay_to.iter().map(|output| output.value).sum();
let do_coin_selection = |force_conflicting_utxo_spend: bool, tolerate_high_network_feerates: bool| {
+ log_debug!(self.logger, "Attempting coin selection targeting {} sat/kW (force_conflicting_utxo_spend = {}, tolerate_high_network_feerates = {})",
+ target_feerate_sat_per_1000_weight, force_conflicting_utxo_spend, tolerate_high_network_feerates);
self.select_confirmed_utxos_internal(
&utxos, claim_id, force_conflicting_utxo_spend, tolerate_high_network_feerates,
- target_feerate_sat_per_1000_weight, preexisting_tx_weight, target_amount_sat,
+ target_feerate_sat_per_1000_weight, preexisting_tx_weight, input_amount_sat, target_amount_sat,
)
};
do_coin_selection(false, false)
.or_else(|_| do_coin_selection(true, true))
}
- fn sign_tx(&self, tx: &mut Transaction) -> Result<(), ()> {
+ fn sign_tx(&self, tx: Transaction) -> Result<Transaction, ()> {
self.source.sign_tx(tx)
}
}
/// A handler for [`Event::BumpTransaction`] events that sources confirmed UTXOs from a
/// [`CoinSelectionSource`] to fee bump transactions via Child-Pays-For-Parent (CPFP) or
/// Replace-By-Fee (RBF).
+///
+/// [`Event::BumpTransaction`]: crate::events::Event::BumpTransaction
pub struct BumpTransactionEventHandler<B: Deref, C: Deref, SP: Deref, L: Deref>
where
B::Target: BroadcasterInterface,
L::Target: Logger,
{
/// Returns a new instance capable of handling [`Event::BumpTransaction`] events.
+ ///
+ /// [`Event::BumpTransaction`]: crate::events::Event::BumpTransaction
pub fn new(broadcaster: B, utxo_source: C, signer_provider: SP, logger: L) -> Self {
Self {
broadcaster,
// match, but we still need to have at least one output in the transaction for it to be
// considered standard. We choose to go with an empty OP_RETURN as it is the cheapest
// way to include a dummy output.
+ log_debug!(self.logger, "Including dummy OP_RETURN output since an output is needed and a change output was not provided");
tx.output.push(TxOut {
value: 0,
script_pubkey: Script::new_op_return(&[]),
}
}
- /// Returns an unsigned transaction spending an anchor output of the commitment transaction, and
- /// any additional UTXOs sourced, to bump the commitment transaction's fee.
- fn build_anchor_tx(
- &self, claim_id: ClaimId, target_feerate_sat_per_1000_weight: u32,
- commitment_tx: &Transaction, anchor_descriptor: &AnchorDescriptor,
- ) -> Result<Transaction, ()> {
+ /// Handles a [`BumpTransactionEvent::ChannelClose`] event variant by producing a fully-signed
+ /// transaction spending an anchor output of the commitment transaction to bump its fee and
+ /// broadcasts them to the network as a package.
+ fn handle_channel_close(
+ &self, claim_id: ClaimId, package_target_feerate_sat_per_1000_weight: u32,
+ commitment_tx: &Transaction, commitment_tx_fee_sat: u64, anchor_descriptor: &AnchorDescriptor,
+ ) -> Result<(), ()> {
+ // Our commitment transaction already has fees allocated to it, so we should take them into
+ // account. We do so by pretending the commitment tranasction's fee and weight are part of
+ // the anchor input.
+ let mut anchor_utxo = anchor_descriptor.previous_utxo();
+ anchor_utxo.value += commitment_tx_fee_sat;
let must_spend = vec![Input {
outpoint: anchor_descriptor.outpoint,
+ previous_utxo: anchor_utxo,
satisfaction_weight: commitment_tx.weight() as u64 + ANCHOR_INPUT_WITNESS_WEIGHT + EMPTY_SCRIPT_SIG_WEIGHT,
}];
+ #[cfg(debug_assertions)]
+ let must_spend_amount = must_spend.iter().map(|input| input.previous_utxo.value).sum::<u64>();
+
+ log_debug!(self.logger, "Peforming coin selection for commitment package (commitment and anchor transaction) targeting {} sat/kW",
+ package_target_feerate_sat_per_1000_weight);
let coin_selection = self.utxo_source.select_confirmed_utxos(
- claim_id, &must_spend, &[], target_feerate_sat_per_1000_weight,
+ claim_id, must_spend, &[], package_target_feerate_sat_per_1000_weight,
)?;
- let mut tx = Transaction {
+ let mut anchor_tx = Transaction {
version: 2,
lock_time: PackedLockTime::ZERO, // TODO: Use next best height.
- input: vec![TxIn {
- previous_output: anchor_descriptor.outpoint,
- script_sig: Script::new(),
- sequence: Sequence::ZERO,
- witness: Witness::new(),
- }],
+ input: vec![anchor_descriptor.unsigned_tx_input()],
output: vec![],
};
- self.process_coin_selection(&mut tx, coin_selection);
- Ok(tx)
- }
- /// Handles a [`BumpTransactionEvent::ChannelClose`] event variant by producing a fully-signed
- /// transaction spending an anchor output of the commitment transaction to bump its fee and
- /// broadcasts them to the network as a package.
- fn handle_channel_close(
- &self, claim_id: ClaimId, package_target_feerate_sat_per_1000_weight: u32,
- commitment_tx: &Transaction, commitment_tx_fee_sat: u64, anchor_descriptor: &AnchorDescriptor,
- ) -> Result<(), ()> {
- // Compute the feerate the anchor transaction must meet to meet the overall feerate for the
- // package (commitment + anchor transactions).
- let commitment_tx_sat_per_1000_weight: u32 = compute_feerate_sat_per_1000_weight(
- commitment_tx_fee_sat, commitment_tx.weight() as u64,
- );
- if commitment_tx_sat_per_1000_weight >= package_target_feerate_sat_per_1000_weight {
- // If the commitment transaction already has a feerate high enough on its own, broadcast
- // it as is without a child.
- self.broadcaster.broadcast_transactions(&[&commitment_tx]);
- return Ok(());
- }
+ #[cfg(debug_assertions)]
+ let total_satisfaction_weight = ANCHOR_INPUT_WITNESS_WEIGHT + EMPTY_SCRIPT_SIG_WEIGHT +
+ coin_selection.confirmed_utxos.iter().map(|utxo| utxo.satisfaction_weight).sum::<u64>();
+ #[cfg(debug_assertions)]
+ let total_input_amount = must_spend_amount +
+ coin_selection.confirmed_utxos.iter().map(|utxo| utxo.output.value).sum::<u64>();
+
+ self.process_coin_selection(&mut anchor_tx, coin_selection);
+ let anchor_txid = anchor_tx.txid();
- let mut anchor_tx = self.build_anchor_tx(
- claim_id, package_target_feerate_sat_per_1000_weight, commitment_tx, anchor_descriptor,
- )?;
debug_assert_eq!(anchor_tx.output.len(), 1);
+ #[cfg(debug_assertions)]
+ let unsigned_tx_weight = anchor_tx.weight() as u64 - (anchor_tx.input.len() as u64 * EMPTY_SCRIPT_SIG_WEIGHT);
- self.utxo_source.sign_tx(&mut anchor_tx)?;
- let signer = self.signer_provider.derive_channel_signer(
- anchor_descriptor.channel_value_satoshis, anchor_descriptor.channel_keys_id,
- );
+ log_debug!(self.logger, "Signing anchor transaction {}", anchor_txid);
+ anchor_tx = self.utxo_source.sign_tx(anchor_tx)?;
+
+ let signer = anchor_descriptor.derive_channel_signer(&self.signer_provider);
let anchor_sig = signer.sign_holder_anchor_input(&anchor_tx, 0, &self.secp)?;
- anchor_tx.input[0].witness =
- chan_utils::build_anchor_input_witness(&signer.pubkeys().funding_pubkey, &anchor_sig);
+ anchor_tx.input[0].witness = anchor_descriptor.tx_input_witness(&anchor_sig);
+
+ #[cfg(debug_assertions)] {
+ let signed_tx_weight = anchor_tx.weight() as u64;
+ let expected_signed_tx_weight = unsigned_tx_weight + total_satisfaction_weight;
+ // Our estimate should be within a 1% error margin of the actual weight and we should
+ // never underestimate.
+ assert!(expected_signed_tx_weight >= signed_tx_weight &&
+ expected_signed_tx_weight - (expected_signed_tx_weight / 100) <= signed_tx_weight);
+
+ let expected_package_fee = fee_for_weight(package_target_feerate_sat_per_1000_weight,
+ signed_tx_weight + commitment_tx.weight() as u64);
+ let package_fee = total_input_amount -
+ anchor_tx.output.iter().map(|output| output.value).sum::<u64>();
+ // Our fee should be within a 5% error margin of the expected fee based on the
+ // feerate and transaction weight and we should never pay less than required.
+ let fee_error_margin = expected_package_fee * 5 / 100;
+ assert!(package_fee >= expected_package_fee &&
+ package_fee - fee_error_margin <= expected_package_fee);
+ }
+ log_info!(self.logger, "Broadcasting anchor transaction {} to bump channel close with txid {}",
+ anchor_txid, commitment_tx.txid());
self.broadcaster.broadcast_transactions(&[&commitment_tx, &anchor_tx]);
Ok(())
}
- /// Returns an unsigned, fee-bumped HTLC transaction, along with the set of signers required to
- /// fulfill the witness for each HTLC input within it.
- fn build_htlc_tx(
+ /// Handles a [`BumpTransactionEvent::HTLCResolution`] event variant by producing a
+ /// fully-signed, fee-bumped HTLC transaction that is broadcast to the network.
+ fn handle_htlc_resolution(
&self, claim_id: ClaimId, target_feerate_sat_per_1000_weight: u32,
htlc_descriptors: &[HTLCDescriptor], tx_lock_time: PackedLockTime,
- ) -> Result<(Transaction, HashMap<[u8; 32], <SP::Target as SignerProvider>::Signer>), ()> {
- let mut tx = Transaction {
+ ) -> Result<(), ()> {
+ let mut htlc_tx = Transaction {
version: 2,
lock_time: tx_lock_time,
input: vec![],
output: vec![],
};
- // Unfortunately, we need to derive the signer for each HTLC ahead of time to obtain its
- // input.
- let mut signers = HashMap::new();
let mut must_spend = Vec::with_capacity(htlc_descriptors.len());
for htlc_descriptor in htlc_descriptors {
- let signer = signers.entry(htlc_descriptor.channel_keys_id)
- .or_insert_with(||
- self.signer_provider.derive_channel_signer(
- htlc_descriptor.channel_value_satoshis, htlc_descriptor.channel_keys_id,
- )
- );
- let per_commitment_point = signer.get_per_commitment_point(
- htlc_descriptor.per_commitment_number, &self.secp
- );
-
let htlc_input = htlc_descriptor.unsigned_tx_input();
must_spend.push(Input {
outpoint: htlc_input.previous_output.clone(),
+ previous_utxo: htlc_descriptor.previous_utxo(&self.secp),
satisfaction_weight: EMPTY_SCRIPT_SIG_WEIGHT + if htlc_descriptor.preimage.is_some() {
HTLC_SUCCESS_INPUT_ANCHOR_WITNESS_WEIGHT
} else {
HTLC_TIMEOUT_INPUT_ANCHOR_WITNESS_WEIGHT
},
});
- tx.input.push(htlc_input);
- let htlc_output = htlc_descriptor.tx_output(&per_commitment_point, &self.secp);
- tx.output.push(htlc_output);
+ htlc_tx.input.push(htlc_input);
+ let htlc_output = htlc_descriptor.tx_output(&self.secp);
+ htlc_tx.output.push(htlc_output);
}
+ log_debug!(self.logger, "Peforming coin selection for HTLC transaction targeting {} sat/kW",
+ target_feerate_sat_per_1000_weight);
+
+ #[cfg(debug_assertions)]
+ let must_spend_satisfaction_weight =
+ must_spend.iter().map(|input| input.satisfaction_weight).sum::<u64>();
+ #[cfg(debug_assertions)]
+ let must_spend_amount = must_spend.iter().map(|input| input.previous_utxo.value).sum::<u64>();
+
let coin_selection = self.utxo_source.select_confirmed_utxos(
- claim_id, &must_spend, &tx.output, target_feerate_sat_per_1000_weight,
+ claim_id, must_spend, &htlc_tx.output, target_feerate_sat_per_1000_weight,
)?;
- self.process_coin_selection(&mut tx, coin_selection);
- Ok((tx, signers))
- }
- /// Handles a [`BumpTransactionEvent::HTLCResolution`] event variant by producing a
- /// fully-signed, fee-bumped HTLC transaction that is broadcast to the network.
- fn handle_htlc_resolution(
- &self, claim_id: ClaimId, target_feerate_sat_per_1000_weight: u32,
- htlc_descriptors: &[HTLCDescriptor], tx_lock_time: PackedLockTime,
- ) -> Result<(), ()> {
- let (mut htlc_tx, signers) = self.build_htlc_tx(
- claim_id, target_feerate_sat_per_1000_weight, htlc_descriptors, tx_lock_time,
- )?;
+ #[cfg(debug_assertions)]
+ let total_satisfaction_weight = must_spend_satisfaction_weight +
+ coin_selection.confirmed_utxos.iter().map(|utxo| utxo.satisfaction_weight).sum::<u64>();
+ #[cfg(debug_assertions)]
+ let total_input_amount = must_spend_amount +
+ coin_selection.confirmed_utxos.iter().map(|utxo| utxo.output.value).sum::<u64>();
+
+ self.process_coin_selection(&mut htlc_tx, coin_selection);
+
+ #[cfg(debug_assertions)]
+ let unsigned_tx_weight = htlc_tx.weight() as u64 - (htlc_tx.input.len() as u64 * EMPTY_SCRIPT_SIG_WEIGHT);
- self.utxo_source.sign_tx(&mut htlc_tx)?;
+ log_debug!(self.logger, "Signing HTLC transaction {}", htlc_tx.txid());
+ htlc_tx = self.utxo_source.sign_tx(htlc_tx)?;
+
+ let mut signers = BTreeMap::new();
for (idx, htlc_descriptor) in htlc_descriptors.iter().enumerate() {
- let signer = signers.get(&htlc_descriptor.channel_keys_id).unwrap();
- let htlc_sig = signer.sign_holder_htlc_transaction(
- &htlc_tx, idx, htlc_descriptor, &self.secp
- )?;
- let per_commitment_point = signer.get_per_commitment_point(
- htlc_descriptor.per_commitment_number, &self.secp
- );
- let witness_script = htlc_descriptor.witness_script(&per_commitment_point, &self.secp);
+ let signer = signers.entry(htlc_descriptor.channel_derivation_parameters.keys_id)
+ .or_insert_with(|| htlc_descriptor.derive_channel_signer(&self.signer_provider));
+ let htlc_sig = signer.sign_holder_htlc_transaction(&htlc_tx, idx, htlc_descriptor, &self.secp)?;
+ let witness_script = htlc_descriptor.witness_script(&self.secp);
htlc_tx.input[idx].witness = htlc_descriptor.tx_input_witness(&htlc_sig, &witness_script);
}
+ #[cfg(debug_assertions)] {
+ let signed_tx_weight = htlc_tx.weight() as u64;
+ let expected_signed_tx_weight = unsigned_tx_weight + total_satisfaction_weight;
+ // Our estimate should be within a 1% error margin of the actual weight and we should
+ // never underestimate.
+ assert!(expected_signed_tx_weight >= signed_tx_weight &&
+ expected_signed_tx_weight - (expected_signed_tx_weight / 100) <= signed_tx_weight);
+
+ let expected_signed_tx_fee = fee_for_weight(target_feerate_sat_per_1000_weight, signed_tx_weight);
+ let signed_tx_fee = total_input_amount -
+ htlc_tx.output.iter().map(|output| output.value).sum::<u64>();
+ // Our fee should be within a 5% error margin of the expected fee based on the
+ // feerate and transaction weight and we should never pay less than required.
+ let fee_error_margin = expected_signed_tx_fee * 5 / 100;
+ assert!(signed_tx_fee >= expected_signed_tx_fee &&
+ signed_tx_fee - fee_error_margin <= expected_signed_tx_fee);
+ }
+
+ log_info!(self.logger, "Broadcasting {}", log_tx!(htlc_tx));
self.broadcaster.broadcast_transactions(&[&htlc_tx]);
Ok(())
}
- /// Handles all variants of [`BumpTransactionEvent`], immediately returning otherwise.
- pub fn handle_event(&self, event: &Event) {
- let event = if let Event::BumpTransaction(event) = event {
- event
- } else {
- return;
- };
+ /// Handles all variants of [`BumpTransactionEvent`].
+ pub fn handle_event(&self, event: &BumpTransactionEvent) {
match event {
BumpTransactionEvent::ChannelClose {
claim_id, package_target_feerate_sat_per_1000_weight, commitment_tx,
- anchor_descriptor, commitment_tx_fee_satoshis, ..
+ commitment_tx_fee_satoshis, anchor_descriptor, ..
} => {
+ log_info!(self.logger, "Handling channel close bump (claim_id = {}, commitment_txid = {})",
+ log_bytes!(claim_id.0), commitment_tx.txid());
if let Err(_) = self.handle_channel_close(
*claim_id, *package_target_feerate_sat_per_1000_weight, commitment_tx,
*commitment_tx_fee_satoshis, anchor_descriptor,
BumpTransactionEvent::HTLCResolution {
claim_id, target_feerate_sat_per_1000_weight, htlc_descriptors, tx_lock_time,
} => {
+ log_info!(self.logger, "Handling HTLC bump (claim_id = {}, htlcs_to_claim = {})",
+ log_bytes!(claim_id.0), log_iter!(htlc_descriptors.iter().map(|d| d.outpoint())));
if let Err(_) = self.handle_htlc_resolution(
*claim_id, *target_feerate_sat_per_1000_weight, htlc_descriptors, *tx_lock_time,
) {
use crate::ln::channel::FUNDING_CONF_DEADLINE_BLOCKS;
use crate::ln::features::ChannelTypeFeatures;
use crate::ln::msgs;
-use crate::ln::{PaymentPreimage, PaymentHash, PaymentSecret};
+use crate::ln::{ChannelId, PaymentPreimage, PaymentHash, PaymentSecret};
use crate::routing::gossip::NetworkUpdate;
use crate::util::errors::APIError;
use crate::util::ser::{BigSize, FixedLengthReader, Writeable, Writer, MaybeReadable, Readable, RequiredWrapper, UpgradableRequired, WithoutLength};
(2, SpontaneousPayment)
);
+/// Information about an HTLC that is part of a payment that can be claimed.
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct ClaimedHTLC {
+ /// The `channel_id` of the channel over which the HTLC was received.
+ pub channel_id: ChannelId,
+ /// The `user_channel_id` of the channel over which the HTLC was received. This is the value
+ /// passed in to [`ChannelManager::create_channel`] for outbound channels, or to
+ /// [`ChannelManager::accept_inbound_channel`] for inbound channels if
+ /// [`UserConfig::manually_accept_inbound_channels`] config flag is set to true. Otherwise
+ /// `user_channel_id` will be randomized for an inbound channel.
+ ///
+ /// This field will be zero for a payment that was serialized prior to LDK version 0.0.117. (This
+ /// should only happen in the case that a payment was claimable prior to LDK version 0.0.117, but
+ /// was not actually claimed until after upgrading.)
+ ///
+ /// [`ChannelManager::create_channel`]: crate::ln::channelmanager::ChannelManager::create_channel
+ /// [`ChannelManager::accept_inbound_channel`]: crate::ln::channelmanager::ChannelManager::accept_inbound_channel
+ /// [`UserConfig::manually_accept_inbound_channels`]: crate::util::config::UserConfig::manually_accept_inbound_channels
+ pub user_channel_id: u128,
+ /// The block height at which this HTLC expires.
+ pub cltv_expiry: u32,
+ /// The amount (in msats) of this part of an MPP.
+ pub value_msat: u64,
+}
+impl_writeable_tlv_based!(ClaimedHTLC, {
+ (0, channel_id, required),
+ (2, user_channel_id, required),
+ (4, cltv_expiry, required),
+ (6, value_msat, required),
+});
+
/// When the payment path failure took place and extra details about it. [`PathFailure::OnPath`] may
/// contain a [`NetworkUpdate`] that needs to be applied to the [`NetworkGraph`].
///
);
#[derive(Clone, Debug, PartialEq, Eq)]
-/// The reason the channel was closed. See individual variants more details.
+/// The reason the channel was closed. See individual variants for more details.
pub enum ClosureReason {
/// Closure generated from receiving a peer error message.
///
///
/// [`ChannelMonitor`]: crate::chain::channelmonitor::ChannelMonitor
/// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager
- OutdatedChannelManager
+ OutdatedChannelManager,
+ /// The counterparty requested a cooperative close of a channel that had not been funded yet.
+ /// The channel has been immediately closed.
+ CounterpartyCoopClosedUnfundedChannel,
+ /// Another channel in the same funding batch closed before the funding transaction
+ /// was ready to be broadcast.
+ FundingBatchClosure,
}
impl core::fmt::Display for ClosureReason {
},
ClosureReason::DisconnectedPeer => f.write_str("the peer disconnected prior to the channel being funded"),
ClosureReason::OutdatedChannelManager => f.write_str("the ChannelManager read from disk was stale compared to ChannelMonitor(s)"),
+ ClosureReason::CounterpartyCoopClosedUnfundedChannel => f.write_str("the peer requested the unfunded channel be closed"),
+ ClosureReason::FundingBatchClosure => f.write_str("another channel in the same funding batch closed"),
}
}
}
(8, ProcessingError) => { (1, err, required) },
(10, DisconnectedPeer) => {},
(12, OutdatedChannelManager) => {},
+ (13, CounterpartyCoopClosedUnfundedChannel) => {},
+ (15, FundingBatchClosure) => {}
);
/// Intended destination of a failed HTLC as indicated in [`Event::HTLCHandlingFailed`].
/// counterparty node information.
node_id: Option<PublicKey>,
/// The outgoing `channel_id` between us and the next node.
- channel_id: [u8; 32],
+ channel_id: ChannelId,
},
/// Scenario where we are unsure of the next node to forward the HTLC to.
UnknownNextHop {
/// [`ChannelManager::funding_transaction_generated`].
///
/// [`ChannelManager::funding_transaction_generated`]: crate::ln::channelmanager::ChannelManager::funding_transaction_generated
- temporary_channel_id: [u8; 32],
+ temporary_channel_id: ChannelId,
/// The counterparty's node_id, which you'll need to pass back into
/// [`ChannelManager::funding_transaction_generated`].
///
channel_value_satoshis: u64,
/// The script which should be used in the transaction output.
output_script: Script,
- /// The `user_channel_id` value passed in to [`ChannelManager::create_channel`], or a
- /// random value for an inbound channel. This may be zero for objects serialized with LDK
- /// versions prior to 0.0.113.
+ /// The `user_channel_id` value passed in to [`ChannelManager::create_channel`] for outbound
+ /// channels, or to [`ChannelManager::accept_inbound_channel`] for inbound channels if
+ /// [`UserConfig::manually_accept_inbound_channels`] config flag is set to true. Otherwise
+ /// `user_channel_id` will be randomized for an inbound channel. This may be zero for objects
+ /// serialized with LDK versions prior to 0.0.113.
///
/// [`ChannelManager::create_channel`]: crate::ln::channelmanager::ChannelManager::create_channel
+ /// [`ChannelManager::accept_inbound_channel`]: crate::ln::channelmanager::ChannelManager::accept_inbound_channel
+ /// [`UserConfig::manually_accept_inbound_channels`]: crate::util::config::UserConfig::manually_accept_inbound_channels
user_channel_id: u128,
},
/// Indicates that we've been offered a payment and it needs to be claimed via calling
/// Note that if the preimage is not known, you should call
/// [`ChannelManager::fail_htlc_backwards`] or [`ChannelManager::fail_htlc_backwards_with_reason`]
/// to free up resources for this HTLC and avoid network congestion.
- /// If you fail to call either [`ChannelManager::claim_funds`], [`ChannelManager::fail_htlc_backwards`],
- /// or [`ChannelManager::fail_htlc_backwards_with_reason`] within the HTLC's timeout, the HTLC will be
- /// automatically failed.
+ ///
+ /// If [`Event::PaymentClaimable::onion_fields`] is `Some`, and includes custom TLVs with even type
+ /// numbers, you should use [`ChannelManager::fail_htlc_backwards_with_reason`] with
+ /// [`FailureCode::InvalidOnionPayload`] if you fail to understand and handle the contents, or
+ /// [`ChannelManager::claim_funds_with_known_custom_tlvs`] upon successful handling.
+ /// If you don't intend to check for custom TLVs, you can simply use
+ /// [`ChannelManager::claim_funds`], which will automatically fail back even custom TLVs.
+ ///
+ /// If you fail to call [`ChannelManager::claim_funds`],
+ /// [`ChannelManager::claim_funds_with_known_custom_tlvs`],
+ /// [`ChannelManager::fail_htlc_backwards`], or
+ /// [`ChannelManager::fail_htlc_backwards_with_reason`] within the HTLC's timeout, the HTLC will
+ /// be automatically failed.
///
/// # Note
/// LDK will not stop an inbound payment from being paid multiple times, so multiple
/// This event used to be called `PaymentReceived` in LDK versions 0.0.112 and earlier.
///
/// [`ChannelManager::claim_funds`]: crate::ln::channelmanager::ChannelManager::claim_funds
+ /// [`ChannelManager::claim_funds_with_known_custom_tlvs`]: crate::ln::channelmanager::ChannelManager::claim_funds_with_known_custom_tlvs
+ /// [`FailureCode::InvalidOnionPayload`]: crate::ln::channelmanager::FailureCode::InvalidOnionPayload
/// [`ChannelManager::fail_htlc_backwards`]: crate::ln::channelmanager::ChannelManager::fail_htlc_backwards
/// [`ChannelManager::fail_htlc_backwards_with_reason`]: crate::ln::channelmanager::ChannelManager::fail_htlc_backwards_with_reason
PaymentClaimable {
/// payment is to pay an invoice or to send a spontaneous payment.
purpose: PaymentPurpose,
/// The `channel_id` indicating over which channel we received the payment.
- via_channel_id: Option<[u8; 32]>,
+ via_channel_id: Option<ChannelId>,
/// The `user_channel_id` indicating over which channel we received the payment.
via_user_channel_id: Option<u128>,
/// The block height at which this payment will be failed back and will no longer be
/// The purpose of the claimed payment, i.e. whether the payment was for an invoice or a
/// spontaneous payment.
purpose: PaymentPurpose,
+ /// The HTLCs that comprise the claimed payment. This will be empty for events serialized prior
+ /// to LDK version 0.0.117.
+ htlcs: Vec<ClaimedHTLC>,
+ /// The sender-intended sum total of all the MPP parts. This will be `None` for events
+ /// serialized prior to LDK version 0.0.117.
+ 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`].
+ ///
+ /// [`ChannelManager::abandon_payment`]: crate::ln::channelmanager::ChannelManager::abandon_payment
+ InvoiceRequestFailed {
+ /// The `payment_id` to have been associated with payment for the requested invoice.
+ payment_id: PaymentId,
},
/// Indicates an outbound payment we made succeeded (i.e. it made it all the way to its target
/// and we got back the payment preimage for it).
/// payment is no longer retryable, due either to the [`Retry`] provided or
/// [`ChannelManager::abandon_payment`] having been called for the corresponding payment.
///
+ /// In exceedingly rare cases, it is possible that an [`Event::PaymentFailed`] is generated for
+ /// a payment after an [`Event::PaymentSent`] event for this same payment has already been
+ /// received and processed. In this case, the [`Event::PaymentFailed`] event MUST be ignored,
+ /// and the payment MUST be treated as having succeeded.
+ ///
/// [`Retry`]: crate::ln::channelmanager::Retry
/// [`ChannelManager::abandon_payment`]: crate::ln::channelmanager::ChannelManager::abandon_payment
PaymentFailed {
SpendableOutputs {
/// The outputs which you should store as spendable by you.
outputs: Vec<SpendableOutputDescriptor>,
+ /// The `channel_id` indicating which channel the spendable outputs belong to.
+ ///
+ /// This will always be `Some` for events generated by LDK versions 0.0.117 and above.
+ channel_id: Option<ChannelId>,
},
/// This event is generated when a payment has been successfully forwarded through us and a
/// forwarding fee earned.
PaymentForwarded {
/// The incoming channel between the previous node and us. This is only `None` for events
/// generated or serialized by versions prior to 0.0.107.
- prev_channel_id: Option<[u8; 32]>,
+ prev_channel_id: Option<ChannelId>,
/// The outgoing channel between the next node and us. This is only `None` for events
/// generated or serialized by versions prior to 0.0.107.
- next_channel_id: Option<[u8; 32]>,
+ next_channel_id: Option<ChannelId>,
/// The fee, in milli-satoshis, which was earned as a result of the payment.
///
/// Note that if we force-closed the channel over which we forwarded an HTLC while the HTLC
/// [`Event::ChannelReady`] event.
ChannelPending {
/// The `channel_id` of the channel that is pending confirmation.
- channel_id: [u8; 32],
+ channel_id: ChannelId,
/// The `user_channel_id` value passed in to [`ChannelManager::create_channel`] for outbound
/// channels, or to [`ChannelManager::accept_inbound_channel`] for inbound channels if
/// [`UserConfig::manually_accept_inbound_channels`] config flag is set to true. Otherwise
/// The `temporary_channel_id` this channel used to be known by during channel establishment.
///
/// Will be `None` for channels created prior to LDK version 0.0.115.
- former_temporary_channel_id: Option<[u8; 32]>,
+ former_temporary_channel_id: Option<ChannelId>,
/// The `node_id` of the channel counterparty.
counterparty_node_id: PublicKey,
/// The outpoint of the channel's funding transaction.
/// establishment.
ChannelReady {
/// The `channel_id` of the channel that is ready.
- channel_id: [u8; 32],
+ channel_id: ChannelId,
/// The `user_channel_id` value passed in to [`ChannelManager::create_channel`] for outbound
/// channels, or to [`ChannelManager::accept_inbound_channel`] for inbound channels if
/// [`UserConfig::manually_accept_inbound_channels`] config flag is set to true. Otherwise
},
/// Used to indicate that a previously opened channel with the given `channel_id` is in the
/// process of closure.
+ ///
+ /// Note that this event is only triggered for accepted channels: if the
+ /// [`UserConfig::manually_accept_inbound_channels`] config flag is set to true and the channel is
+ /// rejected, no `ChannelClosed` event will be sent.
+ ///
+ /// [`ChannelManager::accept_inbound_channel`]: crate::ln::channelmanager::ChannelManager::accept_inbound_channel
+ /// [`UserConfig::manually_accept_inbound_channels`]: crate::util::config::UserConfig::manually_accept_inbound_channels
ChannelClosed {
/// The `channel_id` of the channel which has been closed. Note that on-chain transactions
/// resolving the channel are likely still awaiting confirmation.
- channel_id: [u8; 32],
+ channel_id: ChannelId,
/// The `user_channel_id` value passed in to [`ChannelManager::create_channel`] for outbound
/// channels, or to [`ChannelManager::accept_inbound_channel`] for inbound channels if
/// [`UserConfig::manually_accept_inbound_channels`] config flag is set to true. Otherwise
/// [`UserConfig::manually_accept_inbound_channels`]: crate::util::config::UserConfig::manually_accept_inbound_channels
user_channel_id: u128,
/// The reason the channel was closed.
- reason: ClosureReason
+ reason: ClosureReason,
+ /// Counterparty in the closed channel.
+ ///
+ /// This field will be `None` for objects serialized prior to LDK 0.0.117.
+ counterparty_node_id: Option<PublicKey>,
+ /// Channel capacity of the closing channel (sats).
+ ///
+ /// This field will be `None` for objects serialized prior to LDK 0.0.117.
+ channel_capacity_sats: Option<u64>,
},
/// Used to indicate to the user that they can abandon the funding transaction and recycle the
/// inputs for another purpose.
+ ///
+ /// This event is not guaranteed to be generated for channels that are closed due to a restart.
DiscardFunding {
/// The channel_id of the channel which has been closed.
- channel_id: [u8; 32],
+ channel_id: ChannelId,
/// The full transaction received from the user
transaction: Transaction
},
/// Indicates a request to open a new channel by a peer.
///
- /// To accept the request, call [`ChannelManager::accept_inbound_channel`]. To reject the
- /// request, call [`ChannelManager::force_close_without_broadcasting_txn`].
+ /// To accept the request, call [`ChannelManager::accept_inbound_channel`]. To reject the request,
+ /// call [`ChannelManager::force_close_without_broadcasting_txn`]. Note that a ['ChannelClosed`]
+ /// event will _not_ be triggered if the channel is rejected.
///
/// The event is only triggered when a new open channel request is received and the
/// [`UserConfig::manually_accept_inbound_channels`] config flag is set to true.
///
/// [`ChannelManager::accept_inbound_channel`]: crate::ln::channelmanager::ChannelManager::accept_inbound_channel
/// [`ChannelManager::force_close_without_broadcasting_txn`]: crate::ln::channelmanager::ChannelManager::force_close_without_broadcasting_txn
- temporary_channel_id: [u8; 32],
+ temporary_channel_id: ChannelId,
/// The node_id of the counterparty requesting to open the channel.
///
/// When responding to the request, the `counterparty_node_id` should be passed
/// requirements (i.e. insufficient fees paid, or a CLTV that is too soon).
HTLCHandlingFailed {
/// The channel over which the HTLC was received.
- prev_channel_id: [u8; 32],
+ prev_channel_id: ChannelId,
/// Destination of the HTLC that failed to be processed.
failed_next_destination: HTLCDestination,
},
(2, payment_failed_permanently, required),
(3, false, required), // all_paths_failed in LDK versions prior to 0.0.114
(4, path.blinded_tail, option),
- (5, path.hops, vec_type),
+ (5, path.hops, required_vec),
(7, short_channel_id, option),
(9, None::<RouteParameters>, option), // retry in LDK versions prior to 0.0.115
(11, payment_id, option),
// Note that we now ignore these on the read end as we'll re-generate them in
// ChannelManager, we write them here only for backwards compatibility.
},
- &Event::SpendableOutputs { ref outputs } => {
+ &Event::SpendableOutputs { ref outputs, channel_id } => {
5u8.write(writer)?;
write_tlv_fields!(writer, {
(0, WithoutLength(outputs), required),
+ (1, channel_id, option),
});
},
&Event::HTLCIntercepted { requested_next_hop_scid, payment_hash, inbound_amount_msat, expected_outbound_amount_msat, intercept_id } => {
(5, outbound_amount_forwarded_msat, option),
});
},
- &Event::ChannelClosed { ref channel_id, ref user_channel_id, ref reason } => {
+ &Event::ChannelClosed { ref channel_id, ref user_channel_id, ref reason,
+ ref counterparty_node_id, ref channel_capacity_sats
+ } => {
9u8.write(writer)?;
// `user_channel_id` used to be a single u64 value. In order to remain backwards
// compatible with versions prior to 0.0.113, the u128 is serialized as two
(1, user_channel_id_low, required),
(2, reason, required),
(3, user_channel_id_high, required),
+ (5, counterparty_node_id, option),
+ (7, channel_capacity_sats, option),
});
},
&Event::DiscardFunding { ref channel_id, ref transaction } => {
write_tlv_fields!(writer, {
(0, payment_id, required),
(2, payment_hash, option),
- (4, path.hops, vec_type),
+ (4, path.hops, required_vec),
(6, path.blinded_tail, option),
})
},
// We never write the OpenChannelRequest events as, upon disconnection, peers
// drop any channels which have not yet exchanged funding_signed.
},
- &Event::PaymentClaimed { ref payment_hash, ref amount_msat, ref purpose, ref receiver_node_id } => {
+ &Event::PaymentClaimed { ref payment_hash, ref amount_msat, ref purpose, ref receiver_node_id, ref htlcs, ref sender_intended_total_msat } => {
19u8.write(writer)?;
write_tlv_fields!(writer, {
(0, payment_hash, required),
(1, receiver_node_id, option),
(2, purpose, required),
(4, amount_msat, required),
+ (5, *htlcs, optional_vec),
+ (7, sender_intended_total_msat, option),
});
},
&Event::ProbeSuccessful { ref payment_id, ref payment_hash, ref path } => {
write_tlv_fields!(writer, {
(0, payment_id, required),
(2, payment_hash, required),
- (4, path.hops, vec_type),
+ (4, path.hops, required_vec),
(6, path.blinded_tail, option),
})
},
write_tlv_fields!(writer, {
(0, payment_id, required),
(2, payment_hash, required),
- (4, path.hops, vec_type),
+ (4, path.hops, required_vec),
(6, short_channel_id, option),
(8, path.blinded_tail, option),
})
(8, funding_txo, required),
});
},
+ &Event::InvoiceRequestFailed { ref payment_id } => {
+ 33u8.write(writer)?;
+ write_tlv_fields!(writer, {
+ (0, payment_id, required),
+ })
+ },
// Note that, going forward, all new events must only write data inside of
// `write_tlv_fields`. Versions 0.0.101+ will ignore odd-numbered events that write
// data via `write_tlv_fields`.
(1, network_update, upgradable_option),
(2, payment_failed_permanently, required),
(4, blinded_tail, option),
- (5, path, vec_type),
+ // Added as a part of LDK 0.0.101 and always filled in since.
+ // Defaults to an empty Vec, though likely should have been `Option`al.
+ (5, path, optional_vec),
(7, short_channel_id, option),
(11, payment_id, option),
(13, failure_opt, upgradable_option),
5u8 => {
let f = || {
let mut outputs = WithoutLength(Vec::new());
+ let mut channel_id: Option<ChannelId> = None;
read_tlv_fields!(reader, {
(0, outputs, required),
+ (1, channel_id, option),
});
- Ok(Some(Event::SpendableOutputs { outputs: outputs.0 }))
+ Ok(Some(Event::SpendableOutputs { outputs: outputs.0, channel_id }))
};
f()
},
},
9u8 => {
let f = || {
- let mut channel_id = [0; 32];
+ let mut channel_id = ChannelId::new_zero();
let mut reason = UpgradableRequired(None);
let mut user_channel_id_low_opt: Option<u64> = None;
let mut user_channel_id_high_opt: Option<u64> = None;
+ let mut counterparty_node_id = None;
+ let mut channel_capacity_sats = None;
read_tlv_fields!(reader, {
(0, channel_id, required),
(1, user_channel_id_low_opt, option),
(2, reason, upgradable_required),
(3, user_channel_id_high_opt, option),
+ (5, counterparty_node_id, option),
+ (7, channel_capacity_sats, option),
});
// `user_channel_id` used to be a single u64 value. In order to remain
let user_channel_id = (user_channel_id_low_opt.unwrap_or(0) as u128) +
((user_channel_id_high_opt.unwrap_or(0) as u128) << 64);
- Ok(Some(Event::ChannelClosed { channel_id, user_channel_id, reason: _init_tlv_based_struct_field!(reason, upgradable_required) }))
+ Ok(Some(Event::ChannelClosed { channel_id, user_channel_id, reason: _init_tlv_based_struct_field!(reason, upgradable_required),
+ counterparty_node_id, channel_capacity_sats }))
};
f()
},
11u8 => {
let f = || {
- let mut channel_id = [0; 32];
+ let mut channel_id = ChannelId::new_zero();
let mut transaction = Transaction{ version: 2, lock_time: PackedLockTime::ZERO, input: Vec::new(), output: Vec::new() };
read_tlv_fields!(reader, {
(0, channel_id, required),
},
13u8 => {
let f = || {
- _init_and_read_tlv_fields!(reader, {
+ _init_and_read_len_prefixed_tlv_fields!(reader, {
(0, payment_id, required),
(2, payment_hash, option),
- (4, path, vec_type),
+ (4, path, required_vec),
(6, blinded_tail, option),
});
Ok(Some(Event::PaymentPathSuccessful {
payment_id: payment_id.0.unwrap(),
payment_hash,
- path: Path { hops: path.unwrap(), blinded_tail },
+ path: Path { hops: path, blinded_tail },
}))
};
f()
let mut purpose = UpgradableRequired(None);
let mut amount_msat = 0;
let mut receiver_node_id = None;
+ let mut htlcs: Option<Vec<ClaimedHTLC>> = Some(vec![]);
+ let mut sender_intended_total_msat: Option<u64> = None;
read_tlv_fields!(reader, {
(0, payment_hash, required),
(1, receiver_node_id, option),
(2, purpose, upgradable_required),
(4, amount_msat, required),
+ (5, htlcs, optional_vec),
+ (7, sender_intended_total_msat, option),
});
Ok(Some(Event::PaymentClaimed {
receiver_node_id,
payment_hash,
purpose: _init_tlv_based_struct_field!(purpose, upgradable_required),
amount_msat,
+ htlcs: htlcs.unwrap_or(vec![]),
+ sender_intended_total_msat,
}))
};
f()
},
21u8 => {
let f = || {
- _init_and_read_tlv_fields!(reader, {
+ _init_and_read_len_prefixed_tlv_fields!(reader, {
(0, payment_id, required),
(2, payment_hash, required),
- (4, path, vec_type),
+ (4, path, required_vec),
(6, blinded_tail, option),
});
Ok(Some(Event::ProbeSuccessful {
payment_id: payment_id.0.unwrap(),
payment_hash: payment_hash.0.unwrap(),
- path: Path { hops: path.unwrap(), blinded_tail },
+ path: Path { hops: path, blinded_tail },
}))
};
f()
},
23u8 => {
let f = || {
- _init_and_read_tlv_fields!(reader, {
+ _init_and_read_len_prefixed_tlv_fields!(reader, {
(0, payment_id, required),
(2, payment_hash, required),
- (4, path, vec_type),
+ (4, path, required_vec),
(6, short_channel_id, option),
(8, blinded_tail, option),
});
Ok(Some(Event::ProbeFailed {
payment_id: payment_id.0.unwrap(),
payment_hash: payment_hash.0.unwrap(),
- path: Path { hops: path.unwrap(), blinded_tail },
+ path: Path { hops: path, blinded_tail },
short_channel_id,
}))
};
},
25u8 => {
let f = || {
- let mut prev_channel_id = [0; 32];
+ let mut prev_channel_id = ChannelId::new_zero();
let mut failed_next_destination_opt = UpgradableRequired(None);
read_tlv_fields!(reader, {
(0, prev_channel_id, required),
27u8 => Ok(None),
29u8 => {
let f = || {
- let mut channel_id = [0; 32];
+ let mut channel_id = ChannelId::new_zero();
let mut user_channel_id: u128 = 0;
let mut counterparty_node_id = RequiredWrapper(None);
let mut channel_type = RequiredWrapper(None);
},
31u8 => {
let f = || {
- let mut channel_id = [0; 32];
+ let mut channel_id = ChannelId::new_zero();
let mut user_channel_id: u128 = 0;
let mut former_temporary_channel_id = None;
let mut counterparty_node_id = RequiredWrapper(None);
};
f()
},
+ 33u8 => {
+ let f = || {
+ _init_and_read_len_prefixed_tlv_fields!(reader, {
+ (0, payment_id, required),
+ });
+ Ok(Some(Event::InvoiceRequestFailed {
+ payment_id: payment_id.0.unwrap(),
+ }))
+ };
+ f()
+ },
// Versions prior to 0.0.100 did not ignore odd types, instead returning InvalidValue.
// Version 0.0.100 failed to properly ignore odd types, possibly resulting in corrupt
// reads.
/// broadcast to most peers).
/// These events are handled by PeerManager::process_events if you are using a PeerManager.
#[derive(Clone, Debug)]
+#[cfg_attr(test, derive(PartialEq))]
pub enum MessageSendEvent {
/// Used to indicate that we've accepted a channel open and should send the accept_channel
/// message provided to the given peer.
/// The node_id of the node which should receive this message
node_id: PublicKey,
/// The message which should be sent.
- msg: msgs::TxAddInput,
+ msg: msgs::TxAbort,
},
/// Used to indicate that a channel_ready message should be sent to the peer with the given node_id.
SendChannelReady {
--- /dev/null
+// This file is Copyright its original authors, visible in version control
+// history.
+//
+// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
+// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// You may not use this file except in accordance with one or both of these
+// licenses.
+
+use bitcoin::secp256k1::Secp256k1;
+use crate::blinded_path::BlindedPath;
+use crate::blinded_path::payment::{PaymentConstraints, ReceiveTlvs};
+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::*;
+use crate::routing::router::{PaymentParameters, RouteParameters};
+use crate::util::config::UserConfig;
+
+#[test]
+fn one_hop_blinded_path() {
+ do_one_hop_blinded_path(true);
+ do_one_hop_blinded_path(false);
+}
+
+fn do_one_hop_blinded_path(success: bool) {
+ let chanmon_cfgs = create_chanmon_cfgs(2);
+ let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
+ let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
+ let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+ let chan_upd = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0).0.contents;
+
+ let amt_msat = 5000;
+ let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[1], Some(amt_msat), None);
+ let payee_tlvs = ReceiveTlvs {
+ payment_secret,
+ payment_constraints: PaymentConstraints {
+ max_cltv_expiry: u32::max_value(),
+ htlc_minimum_msat: chan_upd.htlc_minimum_msat,
+ },
+ };
+ let mut secp_ctx = Secp256k1::new();
+ let blinded_path = BlindedPath::one_hop_for_payment(
+ nodes[1].node.get_our_node_id(), payee_tlvs, &chanmon_cfgs[1].keys_manager, &secp_ctx
+ ).unwrap();
+
+ let route_params = RouteParameters::from_payment_params_and_value(
+ PaymentParameters::blinded(vec![blinded_path]),
+ amt_msat,
+ );
+ nodes[0].node.send_payment(payment_hash, RecipientOnionFields::spontaneous_empty(),
+ PaymentId(payment_hash.0), route_params, Retry::Attempts(0)).unwrap();
+ check_added_monitors(&nodes[0], 1);
+ pass_along_route(&nodes[0], &[&[&nodes[1]]], amt_msat, payment_hash, payment_secret);
+ if success {
+ claim_payment(&nodes[0], &[&nodes[1]], payment_preimage);
+ } else {
+ fail_payment(&nodes[0], &[&nodes[1]], payment_hash);
+ }
+}
+
+#[test]
+fn mpp_to_one_hop_blinded_path() {
+ let chanmon_cfgs = create_chanmon_cfgs(4);
+ let node_cfgs = create_node_cfgs(4, &chanmon_cfgs);
+ let node_chanmgrs = create_node_chanmgrs(4, &node_cfgs, &[None, None, None, None]);
+ let nodes = create_network(4, &node_cfgs, &node_chanmgrs);
+ let mut secp_ctx = Secp256k1::new();
+
+ create_announced_chan_between_nodes(&nodes, 0, 1);
+ create_announced_chan_between_nodes(&nodes, 0, 2);
+ let chan_upd_1_3 = create_announced_chan_between_nodes(&nodes, 1, 3).0.contents;
+ create_announced_chan_between_nodes(&nodes, 2, 3).0.contents;
+
+ let amt_msat = 15_000_000;
+ let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[3], Some(amt_msat), None);
+ let payee_tlvs = ReceiveTlvs {
+ payment_secret,
+ payment_constraints: PaymentConstraints {
+ max_cltv_expiry: u32::max_value(),
+ htlc_minimum_msat: chan_upd_1_3.htlc_minimum_msat,
+ },
+ };
+ let blinded_path = BlindedPath::one_hop_for_payment(
+ 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 route_params = RouteParameters::from_payment_params_and_value(
+ 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();
+ check_added_monitors(&nodes[0], 2);
+
+ let expected_route: &[&[&Node]] = &[&[&nodes[1], &nodes[3]], &[&nodes[2], &nodes[3]]];
+ let mut events = nodes[0].node.get_and_clear_pending_msg_events();
+ assert_eq!(events.len(), 2);
+
+ let ev = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events);
+ pass_along_path(&nodes[0], expected_route[0], amt_msat, payment_hash.clone(),
+ Some(payment_secret), ev.clone(), false, None);
+
+ let ev = remove_first_msg_event_to_node(&nodes[2].node.get_our_node_id(), &mut events);
+ pass_along_path(&nodes[0], expected_route[1], amt_msat, payment_hash.clone(),
+ Some(payment_secret), ev.clone(), true, None);
+ claim_payment_along_route(&nodes[0], expected_route, false, payment_preimage);
+}
use bitcoin::hashes::ripemd160::Hash as Ripemd160;
use bitcoin::hash_types::{Txid, PubkeyHash};
+use crate::chain::chaininterface::fee_for_weight;
+use crate::chain::package::WEIGHT_REVOKED_OUTPUT;
use crate::sign::EntropySource;
use crate::ln::{PaymentHash, PaymentPreimage};
use crate::ln::msgs::DecodeError;
/// channel basepoints via the new function, or they were obtained via
/// CommitmentTransaction.trust().keys() because we trusted the source of the
/// pre-calculated keys.
-#[derive(PartialEq, Eq, Clone)]
+#[derive(PartialEq, Eq, Clone, Debug)]
pub struct TxCreationKeys {
/// The broadcaster's per-commitment public key which was used to derive the other keys.
pub per_commitment_point: PublicKey,
impl<'a> DirectedChannelTransactionParameters<'a> {
/// Get the channel pubkeys for the broadcaster
- pub fn broadcaster_pubkeys(&self) -> &ChannelPublicKeys {
+ pub fn broadcaster_pubkeys(&self) -> &'a ChannelPublicKeys {
if self.holder_is_broadcaster {
&self.inner.holder_pubkeys
} else {
}
/// Get the channel pubkeys for the countersignatory
- pub fn countersignatory_pubkeys(&self) -> &ChannelPublicKeys {
+ pub fn countersignatory_pubkeys(&self) -> &'a ChannelPublicKeys {
if self.holder_is_broadcaster {
&self.inner.counterparty_parameters.as_ref().unwrap().pubkeys
} else {
}
/// Whether to use anchors for this channel
- pub fn channel_type_features(&self) -> &ChannelTypeFeatures {
+ pub fn channel_type_features(&self) -> &'a ChannelTypeFeatures {
&self.inner.channel_type_features
}
}
/// Information needed to build and sign a holder's commitment transaction.
///
/// The transaction is only signed once we are ready to broadcast.
-#[derive(Clone)]
+#[derive(Clone, Debug)]
pub struct HolderCommitmentTransaction {
inner: CommitmentTransaction,
/// Our counterparty's signature for the transaction
(0, inner, required),
(2, counterparty_sig, required),
(4, holder_sig_first, required),
- (6, counterparty_htlc_sigs, vec_type),
+ (6, counterparty_htlc_sigs, required_vec),
});
impl HolderCommitmentTransaction {
}
/// A pre-built Bitcoin commitment transaction and its txid.
-#[derive(Clone)]
+#[derive(Clone, Debug)]
pub struct BuiltCommitmentTransaction {
/// The commitment transaction
pub transaction: Transaction,
impl<'a> TrustedClosingTransaction<'a> {
/// The pre-built Bitcoin commitment transaction
- pub fn built_transaction(&self) -> &Transaction {
+ pub fn built_transaction(&self) -> &'a Transaction {
&self.inner.built
}
///
/// This class can be used inside a signer implementation to generate a signature given the relevant
/// secret key.
-#[derive(Clone)]
+#[derive(Clone, Debug)]
pub struct CommitmentTransaction {
commitment_number: u64,
to_broadcaster_value_sat: u64,
to_countersignatory_value_sat: u64,
+ to_broadcaster_delay: Option<u16>, // Added in 0.0.117
feerate_per_kw: u32,
htlcs: Vec<HTLCOutputInCommitment>,
// Note that on upgrades, some features of existing outputs may be missed.
let legacy_deserialization_prevention_marker = legacy_deserialization_prevention_marker_for_channel_type_features(&self.channel_type_features);
write_tlv_fields!(writer, {
(0, self.commitment_number, required),
+ (1, self.to_broadcaster_delay, option),
(2, self.to_broadcaster_value_sat, required),
(4, self.to_countersignatory_value_sat, required),
(6, self.feerate_per_kw, required),
(8, self.keys, required),
(10, self.built, required),
- (12, self.htlcs, vec_type),
+ (12, self.htlcs, required_vec),
(14, legacy_deserialization_prevention_marker, option),
(15, self.channel_type_features, required),
});
impl Readable for CommitmentTransaction {
fn read<R: io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
- let mut commitment_number = RequiredWrapper(None);
- let mut to_broadcaster_value_sat = RequiredWrapper(None);
- let mut to_countersignatory_value_sat = RequiredWrapper(None);
- let mut feerate_per_kw = RequiredWrapper(None);
- let mut keys = RequiredWrapper(None);
- let mut built = RequiredWrapper(None);
- _init_tlv_field_var!(htlcs, vec_type);
- let mut _legacy_deserialization_prevention_marker: Option<()> = None;
- let mut channel_type_features = None;
-
- read_tlv_fields!(reader, {
+ _init_and_read_len_prefixed_tlv_fields!(reader, {
(0, commitment_number, required),
+ (1, to_broadcaster_delay, option),
(2, to_broadcaster_value_sat, required),
(4, to_countersignatory_value_sat, required),
(6, feerate_per_kw, required),
(8, keys, required),
(10, built, required),
- (12, htlcs, vec_type),
+ (12, htlcs, required_vec),
(14, _legacy_deserialization_prevention_marker, option),
(15, channel_type_features, option),
});
commitment_number: commitment_number.0.unwrap(),
to_broadcaster_value_sat: to_broadcaster_value_sat.0.unwrap(),
to_countersignatory_value_sat: to_countersignatory_value_sat.0.unwrap(),
+ to_broadcaster_delay,
feerate_per_kw: feerate_per_kw.0.unwrap(),
keys: keys.0.unwrap(),
built: built.0.unwrap(),
- htlcs: _init_tlv_based_struct_field!(htlcs, vec_type),
+ htlcs,
channel_type_features: channel_type_features.unwrap_or(ChannelTypeFeatures::only_static_remote_key())
})
}
commitment_number,
to_broadcaster_value_sat,
to_countersignatory_value_sat,
+ to_broadcaster_delay: Some(channel_parameters.contest_delay()),
feerate_per_kw,
htlcs,
channel_type_features: channel_parameters.channel_type_features().clone(),
}
/// The pre-built Bitcoin commitment transaction
- pub fn built_transaction(&self) -> &BuiltCommitmentTransaction {
+ pub fn built_transaction(&self) -> &'a BuiltCommitmentTransaction {
&self.inner.built
}
/// The pre-calculated transaction creation public keys.
- pub fn keys(&self) -> &TxCreationKeys {
+ pub fn keys(&self) -> &'a TxCreationKeys {
&self.inner.keys
}
/// Should anchors be used.
- pub fn channel_type_features(&self) -> &ChannelTypeFeatures {
+ pub fn channel_type_features(&self) -> &'a ChannelTypeFeatures {
&self.inner.channel_type_features
}
);
htlc_tx
}
+
+ /// Returns the index of the revokeable output, i.e. the `to_local` output sending funds to
+ /// the broadcaster, in the built transaction, if any exists.
+ ///
+ /// There are two cases where this may return `None`:
+ /// - The balance of the revokeable output is below the dust limit (only found on commitments
+ /// early in the channel's lifetime, i.e. before the channel reserve is met).
+ /// - This commitment was created before LDK 0.0.117. In this case, the
+ /// commitment transaction previously didn't contain enough information to locate the
+ /// revokeable output.
+ pub fn revokeable_output_index(&self) -> Option<usize> {
+ let revokeable_redeemscript = get_revokeable_redeemscript(
+ &self.keys.revocation_key,
+ self.to_broadcaster_delay?,
+ &self.keys.broadcaster_delayed_payment_key,
+ );
+ let revokeable_p2wsh = revokeable_redeemscript.to_v0_p2wsh();
+ let outputs = &self.inner.built.transaction.output;
+ outputs.iter().enumerate()
+ .find(|(_, out)| out.script_pubkey == revokeable_p2wsh)
+ .map(|(idx, _)| idx)
+ }
+
+ /// Helper method to build an unsigned justice transaction spending the revokeable
+ /// `to_local` output to a destination script. Fee estimation accounts for the expected
+ /// revocation witness data that will be added when signed.
+ ///
+ /// This method will error if the given fee rate results in a fee greater than the value
+ /// of the output being spent, or if there exists no revokeable `to_local` output on this
+ /// commitment transaction. See [`Self::revokeable_output_index`] for more details.
+ ///
+ /// The built transaction will allow fee bumping with RBF, and this method takes
+ /// `feerate_per_kw` as an input such that multiple copies of a justice transaction at different
+ /// fee rates may be built.
+ pub fn build_to_local_justice_tx(&self, feerate_per_kw: u64, destination_script: Script)
+ -> Result<Transaction, ()> {
+ let output_idx = self.revokeable_output_index().ok_or(())?;
+ let input = vec![TxIn {
+ previous_output: OutPoint {
+ txid: self.trust().txid(),
+ vout: output_idx as u32,
+ },
+ script_sig: Script::new(),
+ sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
+ witness: Witness::new(),
+ }];
+ let value = self.inner.built.transaction.output[output_idx].value;
+ let output = vec![TxOut {
+ script_pubkey: destination_script,
+ value,
+ }];
+ let mut justice_tx = Transaction {
+ version: 2,
+ lock_time: PackedLockTime::ZERO,
+ input,
+ output,
+ };
+ let weight = justice_tx.weight() as u64 + WEIGHT_REVOKED_OUTPUT;
+ let fee = fee_for_weight(feerate_per_kw as u32, weight);
+ justice_tx.output[0].value = value.checked_sub(fee).ok_or(())?;
+ Ok(justice_tx)
+ }
+
}
/// Commitment transaction numbers which appear in the transactions themselves are XOR'd with a
#[cfg(test)]
mod tests {
- use super::CounterpartyCommitmentSecrets;
+ use super::{CounterpartyCommitmentSecrets, ChannelPublicKeys};
use crate::{hex, chain};
use crate::prelude::*;
use crate::ln::chan_utils::{get_htlc_redeemscript, get_to_countersignatory_with_anchors_redeemscript, CommitmentTransaction, TxCreationKeys, ChannelTransactionParameters, CounterpartyChannelTransactionParameters, HTLCOutputInCommitment};
use bitcoin::secp256k1::{PublicKey, SecretKey, Secp256k1};
use crate::util::test_utils;
use crate::sign::{ChannelSigner, SignerProvider};
- use bitcoin::{Network, Txid};
+ use bitcoin::{Network, Txid, Script};
use bitcoin::hashes::Hash;
use crate::ln::PaymentHash;
use bitcoin::hashes::hex::ToHex;
use bitcoin::PublicKey as BitcoinPublicKey;
use crate::ln::features::ChannelTypeFeatures;
- #[test]
- fn test_anchors() {
- let secp_ctx = Secp256k1::new();
+ struct TestCommitmentTxBuilder {
+ commitment_number: u64,
+ holder_funding_pubkey: PublicKey,
+ counterparty_funding_pubkey: PublicKey,
+ keys: TxCreationKeys,
+ feerate_per_kw: u32,
+ htlcs_with_aux: Vec<(HTLCOutputInCommitment, ())>,
+ channel_parameters: ChannelTransactionParameters,
+ counterparty_pubkeys: ChannelPublicKeys,
+ }
+
+ impl TestCommitmentTxBuilder {
+ fn new() -> Self {
+ let secp_ctx = Secp256k1::new();
+ let seed = [42; 32];
+ let network = Network::Testnet;
+ let keys_provider = test_utils::TestKeysInterface::new(&seed, network);
+ let signer = keys_provider.derive_channel_signer(3000, keys_provider.generate_channel_keys_id(false, 1_000_000, 0));
+ let counterparty_signer = keys_provider.derive_channel_signer(3000, keys_provider.generate_channel_keys_id(true, 1_000_000, 1));
+ let delayed_payment_base = &signer.pubkeys().delayed_payment_basepoint;
+ let per_commitment_secret = SecretKey::from_slice(&hex::decode("1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100").unwrap()[..]).unwrap();
+ let per_commitment_point = PublicKey::from_secret_key(&secp_ctx, &per_commitment_secret);
+ let htlc_basepoint = &signer.pubkeys().htlc_basepoint;
+ let holder_pubkeys = signer.pubkeys();
+ let counterparty_pubkeys = counterparty_signer.pubkeys().clone();
+ let keys = TxCreationKeys::derive_new(&secp_ctx, &per_commitment_point, delayed_payment_base, htlc_basepoint, &counterparty_pubkeys.revocation_basepoint, &counterparty_pubkeys.htlc_basepoint);
+ let channel_parameters = ChannelTransactionParameters {
+ holder_pubkeys: holder_pubkeys.clone(),
+ holder_selected_contest_delay: 0,
+ is_outbound_from_holder: false,
+ counterparty_parameters: Some(CounterpartyChannelTransactionParameters { pubkeys: counterparty_pubkeys.clone(), selected_contest_delay: 0 }),
+ funding_outpoint: Some(chain::transaction::OutPoint { txid: Txid::all_zeros(), index: 0 }),
+ channel_type_features: ChannelTypeFeatures::only_static_remote_key(),
+ };
+ let htlcs_with_aux = Vec::new();
+
+ Self {
+ commitment_number: 0,
+ holder_funding_pubkey: holder_pubkeys.funding_pubkey,
+ counterparty_funding_pubkey: counterparty_pubkeys.funding_pubkey,
+ keys,
+ feerate_per_kw: 1,
+ htlcs_with_aux,
+ channel_parameters,
+ counterparty_pubkeys,
+ }
+ }
- let seed = [42; 32];
- let network = Network::Testnet;
- let keys_provider = test_utils::TestKeysInterface::new(&seed, network);
- let signer = keys_provider.derive_channel_signer(3000, keys_provider.generate_channel_keys_id(false, 1_000_000, 0));
- let counterparty_signer = keys_provider.derive_channel_signer(3000, keys_provider.generate_channel_keys_id(true, 1_000_000, 1));
- let delayed_payment_base = &signer.pubkeys().delayed_payment_basepoint;
- let per_commitment_secret = SecretKey::from_slice(&hex::decode("1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100").unwrap()[..]).unwrap();
- let per_commitment_point = PublicKey::from_secret_key(&secp_ctx, &per_commitment_secret);
- let htlc_basepoint = &signer.pubkeys().htlc_basepoint;
- let holder_pubkeys = signer.pubkeys();
- let counterparty_pubkeys = counterparty_signer.pubkeys();
- let keys = TxCreationKeys::derive_new(&secp_ctx, &per_commitment_point, delayed_payment_base, htlc_basepoint, &counterparty_pubkeys.revocation_basepoint, &counterparty_pubkeys.htlc_basepoint);
- let mut channel_parameters = ChannelTransactionParameters {
- holder_pubkeys: holder_pubkeys.clone(),
- holder_selected_contest_delay: 0,
- is_outbound_from_holder: false,
- counterparty_parameters: Some(CounterpartyChannelTransactionParameters { pubkeys: counterparty_pubkeys.clone(), selected_contest_delay: 0 }),
- funding_outpoint: Some(chain::transaction::OutPoint { txid: Txid::all_zeros(), index: 0 }),
- channel_type_features: ChannelTypeFeatures::only_static_remote_key(),
- };
+ fn build(&mut self, to_broadcaster_sats: u64, to_countersignatory_sats: u64) -> CommitmentTransaction {
+ CommitmentTransaction::new_with_auxiliary_htlc_data(
+ self.commitment_number, to_broadcaster_sats, to_countersignatory_sats,
+ self.holder_funding_pubkey.clone(),
+ self.counterparty_funding_pubkey.clone(),
+ self.keys.clone(), self.feerate_per_kw,
+ &mut self.htlcs_with_aux, &self.channel_parameters.as_holder_broadcastable()
+ )
+ }
+ }
- let mut htlcs_with_aux: Vec<(_, ())> = Vec::new();
+ #[test]
+ fn test_anchors() {
+ let mut builder = TestCommitmentTxBuilder::new();
// Generate broadcaster and counterparty outputs
- let tx = CommitmentTransaction::new_with_auxiliary_htlc_data(
- 0, 1000, 2000,
- holder_pubkeys.funding_pubkey,
- counterparty_pubkeys.funding_pubkey,
- keys.clone(), 1,
- &mut htlcs_with_aux, &channel_parameters.as_holder_broadcastable()
- );
+ let tx = builder.build(1000, 2000);
assert_eq!(tx.built.transaction.output.len(), 2);
- assert_eq!(tx.built.transaction.output[1].script_pubkey, Payload::p2wpkh(&BitcoinPublicKey::new(counterparty_pubkeys.payment_point)).unwrap().script_pubkey());
+ assert_eq!(tx.built.transaction.output[1].script_pubkey, Payload::p2wpkh(&BitcoinPublicKey::new(builder.counterparty_pubkeys.payment_point)).unwrap().script_pubkey());
// Generate broadcaster and counterparty outputs as well as two anchors
- channel_parameters.channel_type_features = ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies();
- let tx = CommitmentTransaction::new_with_auxiliary_htlc_data(
- 0, 1000, 2000,
- holder_pubkeys.funding_pubkey,
- counterparty_pubkeys.funding_pubkey,
- keys.clone(), 1,
- &mut htlcs_with_aux, &channel_parameters.as_holder_broadcastable()
- );
+ builder.channel_parameters.channel_type_features = ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies();
+ let tx = builder.build(1000, 2000);
assert_eq!(tx.built.transaction.output.len(), 4);
- assert_eq!(tx.built.transaction.output[3].script_pubkey, get_to_countersignatory_with_anchors_redeemscript(&counterparty_pubkeys.payment_point).to_v0_p2wsh());
+ assert_eq!(tx.built.transaction.output[3].script_pubkey, get_to_countersignatory_with_anchors_redeemscript(&builder.counterparty_pubkeys.payment_point).to_v0_p2wsh());
// Generate broadcaster output and anchor
- let tx = CommitmentTransaction::new_with_auxiliary_htlc_data(
- 0, 3000, 0,
- holder_pubkeys.funding_pubkey,
- counterparty_pubkeys.funding_pubkey,
- keys.clone(), 1,
- &mut htlcs_with_aux, &channel_parameters.as_holder_broadcastable()
- );
+ let tx = builder.build(3000, 0);
assert_eq!(tx.built.transaction.output.len(), 2);
// Generate counterparty output and anchor
- let tx = CommitmentTransaction::new_with_auxiliary_htlc_data(
- 0, 0, 3000,
- holder_pubkeys.funding_pubkey,
- counterparty_pubkeys.funding_pubkey,
- keys.clone(), 1,
- &mut htlcs_with_aux, &channel_parameters.as_holder_broadcastable()
- );
+ let tx = builder.build(0, 3000);
assert_eq!(tx.built.transaction.output.len(), 2);
let received_htlc = HTLCOutputInCommitment {
};
// Generate broadcaster output and received and offered HTLC outputs, w/o anchors
- channel_parameters.channel_type_features = ChannelTypeFeatures::only_static_remote_key();
- let tx = CommitmentTransaction::new_with_auxiliary_htlc_data(
- 0, 3000, 0,
- holder_pubkeys.funding_pubkey,
- counterparty_pubkeys.funding_pubkey,
- keys.clone(), 1,
- &mut vec![(received_htlc.clone(), ()), (offered_htlc.clone(), ())],
- &channel_parameters.as_holder_broadcastable()
- );
+ builder.channel_parameters.channel_type_features = ChannelTypeFeatures::only_static_remote_key();
+ builder.htlcs_with_aux = vec![(received_htlc.clone(), ()), (offered_htlc.clone(), ())];
+ let tx = builder.build(3000, 0);
+ let keys = &builder.keys.clone();
assert_eq!(tx.built.transaction.output.len(), 3);
assert_eq!(tx.built.transaction.output[0].script_pubkey, get_htlc_redeemscript(&received_htlc, &ChannelTypeFeatures::only_static_remote_key(), &keys).to_v0_p2wsh());
assert_eq!(tx.built.transaction.output[1].script_pubkey, get_htlc_redeemscript(&offered_htlc, &ChannelTypeFeatures::only_static_remote_key(), &keys).to_v0_p2wsh());
"0020215d61bba56b19e9eadb6107f5a85d7f99c40f65992443f69229c290165bc00d");
// Generate broadcaster output and received and offered HTLC outputs, with anchors
- channel_parameters.channel_type_features = ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies();
- let tx = CommitmentTransaction::new_with_auxiliary_htlc_data(
- 0, 3000, 0,
- holder_pubkeys.funding_pubkey,
- counterparty_pubkeys.funding_pubkey,
- keys.clone(), 1,
- &mut vec![(received_htlc.clone(), ()), (offered_htlc.clone(), ())],
- &channel_parameters.as_holder_broadcastable()
- );
+ builder.channel_parameters.channel_type_features = ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies();
+ builder.htlcs_with_aux = vec![(received_htlc.clone(), ()), (offered_htlc.clone(), ())];
+ let tx = builder.build(3000, 0);
assert_eq!(tx.built.transaction.output.len(), 5);
assert_eq!(tx.built.transaction.output[2].script_pubkey, get_htlc_redeemscript(&received_htlc, &ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies(), &keys).to_v0_p2wsh());
assert_eq!(tx.built.transaction.output[3].script_pubkey, get_htlc_redeemscript(&offered_htlc, &ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies(), &keys).to_v0_p2wsh());
"002087a3faeb1950a469c0e2db4a79b093a41b9526e5a6fc6ef5cb949bde3be379c7");
}
+ #[test]
+ fn test_finding_revokeable_output_index() {
+ let mut builder = TestCommitmentTxBuilder::new();
+
+ // Revokeable output present
+ let tx = builder.build(1000, 2000);
+ assert_eq!(tx.built.transaction.output.len(), 2);
+ assert_eq!(tx.trust().revokeable_output_index(), Some(0));
+
+ // Revokeable output present (but to_broadcaster_delay missing)
+ let tx = CommitmentTransaction { to_broadcaster_delay: None, ..tx };
+ assert_eq!(tx.built.transaction.output.len(), 2);
+ assert_eq!(tx.trust().revokeable_output_index(), None);
+
+ // Revokeable output not present (our balance is dust)
+ let tx = builder.build(0, 2000);
+ assert_eq!(tx.built.transaction.output.len(), 1);
+ assert_eq!(tx.trust().revokeable_output_index(), None);
+ }
+
+ #[test]
+ fn test_building_to_local_justice_tx() {
+ let mut builder = TestCommitmentTxBuilder::new();
+
+ // Revokeable output not present (our balance is dust)
+ let tx = builder.build(0, 2000);
+ assert_eq!(tx.built.transaction.output.len(), 1);
+ assert!(tx.trust().build_to_local_justice_tx(253, Script::new()).is_err());
+
+ // Revokeable output present
+ let tx = builder.build(1000, 2000);
+ assert_eq!(tx.built.transaction.output.len(), 2);
+
+ // Too high feerate
+ assert!(tx.trust().build_to_local_justice_tx(100_000, Script::new()).is_err());
+
+ // Generate a random public key for destination script
+ let secret_key = SecretKey::from_slice(
+ &hex::decode("1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100")
+ .unwrap()[..]).unwrap();
+ let pubkey_hash = BitcoinPublicKey::new(
+ PublicKey::from_secret_key(&Secp256k1::new(), &secret_key)).wpubkey_hash().unwrap();
+ let destination_script = Script::new_v0_p2wpkh(&pubkey_hash);
+
+ let justice_tx = tx.trust().build_to_local_justice_tx(253, destination_script.clone()).unwrap();
+ assert_eq!(justice_tx.input.len(), 1);
+ assert_eq!(justice_tx.input[0].previous_output.txid, tx.built.transaction.txid());
+ assert_eq!(justice_tx.input[0].previous_output.vout, tx.trust().revokeable_output_index().unwrap() as u32);
+ assert!(justice_tx.input[0].sequence.is_rbf());
+
+ assert_eq!(justice_tx.output.len(), 1);
+ assert!(justice_tx.output[0].value < 1000);
+ assert_eq!(justice_tx.output[0].script_pubkey, destination_script);
+ }
+
#[test]
fn test_per_commitment_storage() {
// Test vectors from BOLT 3:
use crate::chain::transaction::OutPoint;
use crate::chain::{ChannelMonitorUpdateStatus, Listen, Watch};
use crate::events::{Event, MessageSendEvent, MessageSendEventsProvider, PaymentPurpose, ClosureReason, HTLCDestination};
-use crate::ln::channelmanager::{ChannelManager, RAACommitmentOrder, PaymentSendFailure, PaymentId, RecipientOnionFields};
-use crate::ln::channel::AnnouncementSigsState;
+use crate::ln::channelmanager::{RAACommitmentOrder, PaymentSendFailure, PaymentId, RecipientOnionFields};
+use crate::ln::channel::{AnnouncementSigsState, ChannelPhase};
use crate::ln::msgs;
use crate::ln::msgs::{ChannelMessageHandler, RoutingMessageHandler};
-use crate::util::enforcing_trait_impls::EnforcingSigner;
+use crate::util::test_channel_signer::TestChannelSigner;
use crate::util::errors::APIError;
use crate::util::ser::{ReadableArgs, Writeable};
use crate::util::test_utils::TestBroadcaster;
use crate::prelude::*;
use crate::sync::{Arc, Mutex};
-#[test]
-fn test_simple_monitor_permanent_update_fail() {
- // Test that we handle a simple permanent monitor update failure
- let chanmon_cfgs = create_chanmon_cfgs(2);
- let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
- let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
- let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
- create_announced_chan_between_nodes(&nodes, 0, 1);
-
- let (route, payment_hash_1, _, payment_secret_1) = get_route_and_payment_hash!(&nodes[0], nodes[1], 1000000);
- chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::PermanentFailure);
- unwrap_send_err!(nodes[0].node.send_payment_with_route(&route, payment_hash_1,
- RecipientOnionFields::secret_only(payment_secret_1), PaymentId(payment_hash_1.0)
- ), true, APIError::ChannelUnavailable {..}, {});
- check_added_monitors!(nodes[0], 2);
-
- let events_1 = nodes[0].node.get_and_clear_pending_msg_events();
- assert_eq!(events_1.len(), 2);
- match events_1[0] {
- MessageSendEvent::BroadcastChannelUpdate { .. } => {},
- _ => panic!("Unexpected event"),
- };
- match events_1[1] {
- MessageSendEvent::HandleError { node_id, .. } => assert_eq!(node_id, nodes[1].node.get_our_node_id()),
- _ => panic!("Unexpected event"),
- };
-
- assert!(nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().is_empty());
-
- // TODO: Once we hit the chain with the failure transaction we should check that we get a
- // PaymentPathFailed event
-
- assert_eq!(nodes[0].node.list_channels().len(), 0);
- check_closed_event!(nodes[0], 1, ClosureReason::ProcessingError { err: "ChannelMonitor storage failure".to_string() });
-}
-
#[test]
fn test_monitor_and_persister_update_fail() {
// Test that if both updating the `ChannelMonitor` and persisting the updated
send_payment(&nodes[0], &vec!(&nodes[1])[..], 10_000_000);
// Route an HTLC from node 0 to node 1 (but don't settle)
- let (preimage, payment_hash, _) = route_payment(&nodes[0], &[&nodes[1]], 9_000_000);
+ let (preimage, payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1]], 9_000_000);
// Make a copy of the ChainMonitor so we can capture the error it returns on a
// bogus update. Note that if instead we updated the nodes[0]'s ChainMonitor
let chain_mon = {
let new_monitor = {
let monitor = nodes[0].chain_monitor.chain_monitor.get_monitor(outpoint).unwrap();
- let new_monitor = <(BlockHash, ChannelMonitor<EnforcingSigner>)>::read(
+ let new_monitor = <(BlockHash, ChannelMonitor<TestChannelSigner>)>::read(
&mut io::Cursor::new(&monitor.encode()), (nodes[0].keys_manager, nodes[0].keys_manager)).unwrap().1;
assert!(new_monitor == *monitor);
new_monitor
};
let chain_mon = test_utils::TestChainMonitor::new(Some(&chain_source), &tx_broadcaster, &logger, &chanmon_cfgs[0].fee_estimator, &persister, &node_cfgs[0].keys_manager);
- assert_eq!(chain_mon.watch_channel(outpoint, new_monitor), ChannelMonitorUpdateStatus::Completed);
+ assert_eq!(chain_mon.watch_channel(outpoint, new_monitor), Ok(ChannelMonitorUpdateStatus::Completed));
chain_mon
};
chain_mon.chain_monitor.block_connected(&create_dummy_block(BlockHash::all_zeros(), 42, Vec::new()), 200);
- // Set the persister's return value to be a InProgress.
- persister.set_update_ret(ChannelMonitorUpdateStatus::InProgress);
-
// Try to update ChannelMonitor
nodes[1].node.claim_funds(preimage);
expect_payment_claimed!(nodes[1], payment_hash, 9_000_000);
let updates = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
assert_eq!(updates.update_fulfill_htlcs.len(), 1);
nodes[0].node.handle_update_fulfill_htlc(&nodes[1].node.get_our_node_id(), &updates.update_fulfill_htlcs[0]);
+
{
let mut node_0_per_peer_lock;
let mut node_0_peer_state_lock;
- let mut channel = get_channel_ref!(nodes[0], nodes[1], node_0_per_peer_lock, node_0_peer_state_lock, chan.2);
- if let Ok(Some(update)) = channel.commitment_signed(&updates.commitment_signed, &node_cfgs[0].logger) {
- // Check that even though the persister is returning a InProgress,
- // because the update is bogus, ultimately the error that's returned
- // should be a PermanentFailure.
- if let ChannelMonitorUpdateStatus::PermanentFailure = chain_mon.chain_monitor.update_channel(outpoint, &update) {} else { panic!("Expected monitor error to be permanent"); }
- logger.assert_log_regex("lightning::chain::chainmonitor", regex::Regex::new("Persistence of ChannelMonitorUpdate for channel [0-9a-f]* in progress").unwrap(), 1);
- assert_eq!(nodes[0].chain_monitor.update_channel(outpoint, &update), ChannelMonitorUpdateStatus::Completed);
- } else { assert!(false); }
+ if let ChannelPhase::Funded(ref mut channel) = get_channel_ref!(nodes[0], nodes[1], node_0_per_peer_lock, node_0_peer_state_lock, chan.2) {
+ if let Ok(Some(update)) = channel.commitment_signed(&updates.commitment_signed, &node_cfgs[0].logger) {
+ // Check that the persister returns InProgress (and will never actually complete)
+ // as the monitor update errors.
+ if let ChannelMonitorUpdateStatus::InProgress = chain_mon.chain_monitor.update_channel(outpoint, &update) {} else { panic!("Expected monitor paused"); }
+ logger.assert_log_regex("lightning::chain::chainmonitor", regex::Regex::new("Failed to update ChannelMonitor for channel [0-9a-f]*.").unwrap(), 1);
+
+ // Apply the monitor update to the original ChainMonitor, ensuring the
+ // ChannelManager and ChannelMonitor aren't out of sync.
+ assert_eq!(nodes[0].chain_monitor.update_channel(outpoint, &update),
+ ChannelMonitorUpdateStatus::Completed);
+ } else { assert!(false); }
+ } else {
+ assert!(false);
+ }
}
check_added_monitors!(nodes[0], 1);
- let events = nodes[0].node.get_and_clear_pending_events();
- assert_eq!(events.len(), 1);
+ expect_payment_sent(&nodes[0], preimage, None, false, false);
}
fn do_test_simple_monitor_temporary_update_fail(disconnect: bool) {
if disconnect {
nodes[0].node.peer_disconnected(&nodes[1].node.get_our_node_id());
nodes[1].node.peer_disconnected(&nodes[0].node.get_our_node_id());
- reconnect_nodes(&nodes[0], &nodes[1], (true, true), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (false, false));
+ let mut reconnect_args = ReconnectArgs::new(&nodes[0], &nodes[1]);
+ reconnect_args.send_channel_ready = (true, true);
+ reconnect_nodes(reconnect_args);
}
chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::Completed);
if disconnect {
nodes[0].node.peer_disconnected(&nodes[1].node.get_our_node_id());
nodes[1].node.peer_disconnected(&nodes[0].node.get_our_node_id());
- reconnect_nodes(&nodes[0], &nodes[1], (false, false), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (false, false));
+ reconnect_nodes(ReconnectArgs::new(&nodes[0], &nodes[1]));
}
// ...and make sure we can force-close a frozen channel
// PaymentPathFailed event
assert_eq!(nodes[0].node.list_channels().len(), 0);
- check_closed_event!(nodes[0], 1, ClosureReason::HolderForceClosed);
+ check_closed_event!(nodes[0], 1, ClosureReason::HolderForceClosed, [nodes[1].node.get_our_node_id()], 100000);
}
#[test]
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
let channel_id = create_announced_chan_between_nodes(&nodes, 0, 1).2;
- let (payment_preimage_1, payment_hash_1, _) = route_payment(&nodes[0], &[&nodes[1]], 1_000_000);
+ let (payment_preimage_1, payment_hash_1, ..) = route_payment(&nodes[0], &[&nodes[1]], 1_000_000);
// Now try to send a second payment which will fail to send
let (route, payment_hash_2, payment_preimage_2, payment_secret_2) = get_route_and_payment_hash!(nodes[0], nodes[1], 1000000);
send_payment(&nodes[0], &[&nodes[1], &nodes[2]], 5000000);
// Route a first payment that we'll fail backwards
- let (_, payment_hash_1, _) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 1000000);
+ let (_, payment_hash_1, ..) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 1000000);
// Fail the payment backwards, failing the monitor update on nodes[1]'s receipt of the RAA
nodes[2].node.fail_htlc_backwards(&payment_hash_1);
let chan_1 = create_announced_chan_between_nodes(&nodes, 0, 1);
create_announced_chan_between_nodes(&nodes, 1, 2);
- let (payment_preimage, payment_hash, _) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 1_000_000);
+ let (payment_preimage, payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 1_000_000);
nodes[1].node.peer_disconnected(&nodes[0].node.get_our_node_id());
nodes[0].node.peer_disconnected(&nodes[1].node.get_our_node_id());
let channel_id = create_announced_chan_between_nodes(&nodes, 0, 1).2;
// Forward a payment for B to claim
- let (payment_preimage_1, payment_hash_1, _) = route_payment(&nodes[0], &[&nodes[1]], 1_000_000);
+ let (payment_preimage_1, payment_hash_1, ..) = route_payment(&nodes[0], &[&nodes[1]], 1_000_000);
nodes[0].node.peer_disconnected(&nodes[1].node.get_our_node_id());
nodes[1].node.peer_disconnected(&nodes[0].node.get_our_node_id());
MessageSendEvent::UpdateHTLCs { ref node_id, ref updates } => {
assert_eq!(*node_id, nodes[0].node.get_our_node_id());
nodes[0].node.handle_update_fulfill_htlc(&nodes[1].node.get_our_node_id(), &updates.update_fulfill_htlcs[0]);
+ expect_payment_sent(&nodes[0], payment_preimage_1, None, false, false);
nodes[0].node.handle_commitment_signed(&nodes[1].node.get_our_node_id(), &updates.commitment_signed);
check_added_monitors!(nodes[0], 1);
nodes[0].node.handle_revoke_and_ack(&nodes[1].node.get_our_node_id(), &bs_raa);
check_added_monitors!(nodes[0], 1);
- expect_payment_sent!(nodes[0], payment_preimage_1);
+ expect_payment_path_successful!(nodes[0]);
claim_payment(&nodes[0], &[&nodes[1]], payment_preimage_2);
}
{
let mut node_0_per_peer_lock;
let mut node_0_peer_state_lock;
- get_channel_ref!(nodes[0], nodes[1], node_0_per_peer_lock, node_0_peer_state_lock, channel_id).context.announcement_sigs_state = AnnouncementSigsState::PeerReceived;
+ get_channel_ref!(nodes[0], nodes[1], node_0_per_peer_lock, node_0_peer_state_lock, channel_id).context_mut().announcement_sigs_state = AnnouncementSigsState::PeerReceived;
}
{
let mut node_1_per_peer_lock;
let mut node_1_peer_state_lock;
- get_channel_ref!(nodes[1], nodes[0], node_1_per_peer_lock, node_1_peer_state_lock, channel_id).context.announcement_sigs_state = AnnouncementSigsState::PeerReceived;
+ get_channel_ref!(nodes[1], nodes[0], node_1_per_peer_lock, node_1_peer_state_lock, channel_id).context_mut().announcement_sigs_state = AnnouncementSigsState::PeerReceived;
}
// Route the payment and deliver the initial commitment_signed (with a monitor update failure
// Rebalance a bit so that we can send backwards from 3 to 2.
send_payment(&nodes[0], &[&nodes[1], &nodes[2]], 5000000);
- let (payment_preimage_1, payment_hash_1, _) = route_payment(&nodes[0], &[&nodes[1]], 1_000_000);
+ let (payment_preimage_1, payment_hash_1, ..) = route_payment(&nodes[0], &[&nodes[1]], 1_000_000);
chanmon_cfgs[1].persister.set_update_ret(ChannelMonitorUpdateStatus::InProgress);
nodes[1].node.claim_funds(payment_preimage_1);
// Rebalance a bit so that we can send backwards from 3 to 1.
send_payment(&nodes[0], &[&nodes[1], &nodes[2]], 5000000);
- let (_, payment_hash_1, _) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 1000000);
+ let (_, payment_hash_1, ..) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 1000000);
nodes[2].node.fail_htlc_backwards(&payment_hash_1);
expect_pending_htlcs_forwardable_and_htlc_handling_failed!(nodes[2], vec![HTLCDestination::FailedPayment { payment_hash: payment_hash_1 }]);
check_added_monitors!(nodes[2], 1);
let channel_id = create_announced_chan_between_nodes(&nodes, 0, 1).2;
// Forward a payment for B to claim
- let (payment_preimage_1, payment_hash_1, _) = route_payment(&nodes[0], &[&nodes[1]], 1_000_000);
+ let (payment_preimage_1, payment_hash_1, ..) = route_payment(&nodes[0], &[&nodes[1]], 1_000_000);
// Now start forwarding a second payment, skipping the last RAA so B is in AwaitingRAA
let (route, payment_hash_2, payment_preimage_2, payment_secret_2) = get_route_and_payment_hash!(nodes[0], nodes[1], 1000000);
// Make sure nodes[1] isn't stupid enough to re-send the ChannelReady on reconnect
nodes[0].node.peer_disconnected(&nodes[1].node.get_our_node_id());
nodes[1].node.peer_disconnected(&nodes[0].node.get_our_node_id());
- reconnect_nodes(&nodes[0], &nodes[1], (false, confirm_a_first), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (false, false));
+ let mut reconnect_args = ReconnectArgs::new(&nodes[0], &nodes[1]);
+ reconnect_args.send_channel_ready.1 = confirm_a_first;
+ reconnect_nodes(reconnect_args);
// But we want to re-emit ChannelPending
expect_channel_pending_event(&nodes[1], &nodes[0].node.get_our_node_id());
send_payment(&nodes[0], &[&nodes[1]], 8000000);
close_channel(&nodes[0], &nodes[1], &channel_id, funding_tx, true);
- check_closed_event!(nodes[0], 1, ClosureReason::CooperativeClosure);
- check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure);
+ check_closed_event!(nodes[0], 1, ClosureReason::CooperativeClosure, [nodes[1].node.get_our_node_id()], 100000);
+ check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure, [nodes[0].node.get_our_node_id()], 100000);
}
#[test]
create_announced_chan_between_nodes(&nodes, 0, 1);
let chan_id_2 = create_announced_chan_between_nodes(&nodes, 1, 2).2;
- let (payment_preimage, payment_hash, _) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 2000);
+ let (payment_preimage, payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 2000);
let bs_txn = get_local_commitment_txn!(nodes[2], chan_id_2);
assert_eq!(bs_txn.len(), 1);
expect_payment_forwarded!(nodes[1], nodes[0], nodes[2], Some(1000), false, false);
mine_transaction(&nodes[1], &bs_txn[0]);
- check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [nodes[2].node.get_our_node_id()], 100000);
check_closed_broadcast!(nodes[1], true);
connect_blocks(&nodes[1], ANTI_REORG_DELAY - 1);
check_added_monitors!(nodes[1], 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 }]);
nodes[0].node.handle_update_fulfill_htlc(&nodes[1].node.get_our_node_id(), &bs_updates.update_fulfill_htlcs[0]);
- expect_payment_sent_without_paths!(nodes[0], payment_preimage);
+ expect_payment_sent(&nodes[0], payment_preimage, None, false, false);
commitment_signed_dance!(nodes[0], nodes[1], bs_updates.commitment_signed, true, true);
expect_payment_path_successful!(nodes[0]);
}
// which failed in such a case).
let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
+ let persister;
+ let new_chain_monitor;
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
- let persister: test_utils::TestPersister;
- let new_chain_monitor: test_utils::TestChainMonitor;
- let nodes_0_deserialized: ChannelManager<&test_utils::TestChainMonitor, &test_utils::TestBroadcaster, &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestFeeEstimator, &test_utils::TestRouter, &test_utils::TestLogger>;
+ let nodes_0_deserialized;
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
let chan_id = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 15_000_000, 7_000_000_000).2;
//
// Note that because, at the end, MonitorUpdateInProgress is still set, the HTLC generated in
// (c) will not be freed from the holding cell.
- let (payment_preimage_0, payment_hash_0, _) = route_payment(&nodes[1], &[&nodes[0]], 100_000);
+ let (payment_preimage_0, payment_hash_0, ..) = route_payment(&nodes[1], &[&nodes[0]], 100_000);
nodes[0].node.send_payment_with_route(&route, payment_hash_1,
RecipientOnionFields::secret_only(payment_secret_1), PaymentId(payment_hash_1.0)).unwrap();
RecipientOnionFields::secret_only(payment_secret_2), PaymentId(payment_hash_2.0)).unwrap();
check_added_monitors!(nodes[0], 0);
+ let chan_0_monitor_serialized = get_monitor!(nodes[0], chan_id).encode();
chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::InProgress);
chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::InProgress);
nodes[0].node.claim_funds(payment_preimage_0);
// disconnect the peers. Note that the fuzzer originally found this issue because
// deserializing a ChannelManager in this state causes an assertion failure.
if reload_a {
- let chan_0_monitor_serialized = get_monitor!(nodes[0], chan_id).encode();
reload_node!(nodes[0], &nodes[0].node.encode(), &[&chan_0_monitor_serialized], persister, new_chain_monitor, nodes_0_deserialized);
+ persister.set_update_ret(ChannelMonitorUpdateStatus::InProgress);
+ persister.set_update_ret(ChannelMonitorUpdateStatus::InProgress);
} else {
nodes[0].node.peer_disconnected(&nodes[1].node.get_our_node_id());
}
assert_eq!(pending_cs.commitment_signed, cs);
} else { panic!(); }
- // There should be no monitor updates as we are still pending awaiting a failed one.
- check_added_monitors!(nodes[0], 0);
- check_added_monitors!(nodes[1], 0);
+ if reload_a {
+ // The two pending monitor updates were replayed (but are still pending).
+ check_added_monitors(&nodes[0], 2);
+ } else {
+ // There should be no monitor updates as we are still pending awaiting a failed one.
+ check_added_monitors(&nodes[0], 0);
+ }
+ check_added_monitors(&nodes[1], 0);
}
// If we finish updating the monitor, we should free the holding cell right away (this did
assert!(updates.update_fee.is_none());
assert_eq!(updates.update_fulfill_htlcs.len(), 1);
nodes[1].node.handle_update_fulfill_htlc(&nodes[0].node.get_our_node_id(), &updates.update_fulfill_htlcs[0]);
- expect_payment_sent_without_paths!(nodes[1], payment_preimage_0);
+ expect_payment_sent(&nodes[1], payment_preimage_0, None, false, false);
assert_eq!(updates.update_add_htlcs.len(), 1);
nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &updates.update_add_htlcs[0]);
updates.commitment_signed
expect_payment_claimable!(nodes[1], payment_hash_1, payment_secret_1, 100000);
check_added_monitors!(nodes[1], 1);
- commitment_signed_dance!(nodes[1], nodes[0], (), false, true, false);
+ commitment_signed_dance!(nodes[1], nodes[0], (), false, true, false, false);
let events = nodes[1].node.get_and_clear_pending_events();
assert_eq!(events.len(), 2);
create_announced_chan_between_nodes(&nodes, 0, 1);
let chan_id_2 = create_announced_chan_between_nodes(&nodes, 1, 2).2;
- let (payment_preimage, payment_hash, _) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 100_000);
+ let (payment_preimage, payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 100_000);
let mut as_raa = None;
if htlc_status == HTLCStatusAtDupClaim::HoldingCell {
bs_updates = Some(get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()));
assert_eq!(bs_updates.as_ref().unwrap().update_fulfill_htlcs.len(), 1);
nodes[0].node.handle_update_fulfill_htlc(&nodes[1].node.get_our_node_id(), &bs_updates.as_ref().unwrap().update_fulfill_htlcs[0]);
- expect_payment_sent_without_paths!(nodes[0], payment_preimage);
+ expect_payment_sent(&nodes[0], payment_preimage, None, false, false);
if htlc_status == HTLCStatusAtDupClaim::Cleared {
commitment_signed_dance!(nodes[0], nodes[1], &bs_updates.as_ref().unwrap().commitment_signed, false);
expect_payment_path_successful!(nodes[0]);
nodes[2].node.peer_disconnected(&nodes[1].node.get_our_node_id());
if second_fails {
- reconnect_nodes(&nodes[1], &nodes[2], (false, false), (0, 0), (0, 0), (1, 0), (0, 0), (0, 0), (false, false));
+ let mut reconnect_args = ReconnectArgs::new(&nodes[1], &nodes[2]);
+ reconnect_args.pending_htlc_fails.0 = 1;
+ reconnect_nodes(reconnect_args);
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 }]);
} else {
- reconnect_nodes(&nodes[1], &nodes[2], (false, false), (0, 0), (1, 0), (0, 0), (0, 0), (0, 0), (false, false));
+ let mut reconnect_args = ReconnectArgs::new(&nodes[1], &nodes[2]);
+ reconnect_args.pending_htlc_claims.0 = 1;
+ reconnect_nodes(reconnect_args);
}
if htlc_status == HTLCStatusAtDupClaim::HoldingCell {
bs_updates = Some(get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()));
assert_eq!(bs_updates.as_ref().unwrap().update_fulfill_htlcs.len(), 1);
nodes[0].node.handle_update_fulfill_htlc(&nodes[1].node.get_our_node_id(), &bs_updates.as_ref().unwrap().update_fulfill_htlcs[0]);
- expect_payment_sent_without_paths!(nodes[0], payment_preimage);
+ expect_payment_sent(&nodes[0], payment_preimage, None, false, false);
}
if htlc_status != HTLCStatusAtDupClaim::Cleared {
commitment_signed_dance!(nodes[0], nodes[1], &bs_updates.as_ref().unwrap().commitment_signed, false);
assert_eq!(txn_a, txn_b);
assert_eq!(txn_a.len(), 1);
check_spends!(txn_a[0], funding_tx);
- check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure);
- check_closed_event!(nodes[0], 1, ClosureReason::CooperativeClosure);
-}
-
-#[test]
-fn test_permanent_error_during_sending_shutdown() {
- // Test that permanent failures when updating the monitor's shutdown script result in a force
- // close when initiating a cooperative close.
- let mut config = test_default_channel_config();
- config.channel_handshake_config.commit_upfront_shutdown_pubkey = false;
-
- let chanmon_cfgs = create_chanmon_cfgs(2);
- let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
- let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config), None]);
- let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
-
- let channel_id = create_announced_chan_between_nodes(&nodes, 0, 1).2;
- chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::PermanentFailure);
-
- assert!(nodes[0].node.close_channel(&channel_id, &nodes[1].node.get_our_node_id()).is_ok());
-
- // We always send the `shutdown` response when initiating a shutdown, even if we immediately
- // close the channel thereafter.
- let msg_events = nodes[0].node.get_and_clear_pending_msg_events();
- assert_eq!(msg_events.len(), 3);
- if let MessageSendEvent::SendShutdown { .. } = msg_events[0] {} else { panic!(); }
- if let MessageSendEvent::BroadcastChannelUpdate { .. } = msg_events[1] {} else { panic!(); }
- if let MessageSendEvent::HandleError { .. } = msg_events[2] {} else { panic!(); }
-
- check_added_monitors!(nodes[0], 2);
- check_closed_event!(nodes[0], 1, ClosureReason::ProcessingError { err: "ChannelMonitor storage failure".to_string() });
-}
-
-#[test]
-fn test_permanent_error_during_handling_shutdown() {
- // Test that permanent failures when updating the monitor's shutdown script result in a force
- // close when handling a cooperative close.
- let mut config = test_default_channel_config();
- config.channel_handshake_config.commit_upfront_shutdown_pubkey = false;
-
- let chanmon_cfgs = create_chanmon_cfgs(2);
- let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
- let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, Some(config)]);
- let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
-
- let channel_id = create_announced_chan_between_nodes(&nodes, 0, 1).2;
- chanmon_cfgs[1].persister.set_update_ret(ChannelMonitorUpdateStatus::PermanentFailure);
-
- assert!(nodes[0].node.close_channel(&channel_id, &nodes[1].node.get_our_node_id()).is_ok());
- let shutdown = get_event_msg!(nodes[0], MessageSendEvent::SendShutdown, nodes[1].node.get_our_node_id());
- nodes[1].node.handle_shutdown(&nodes[0].node.get_our_node_id(), &shutdown);
-
- // We always send the `shutdown` response when receiving a shutdown, even if we immediately
- // close the channel thereafter.
- let msg_events = nodes[1].node.get_and_clear_pending_msg_events();
- assert_eq!(msg_events.len(), 3);
- if let MessageSendEvent::SendShutdown { .. } = msg_events[0] {} else { panic!(); }
- if let MessageSendEvent::BroadcastChannelUpdate { .. } = msg_events[1] {} else { panic!(); }
- if let MessageSendEvent::HandleError { .. } = msg_events[2] {} else { panic!(); }
-
- check_added_monitors!(nodes[1], 2);
- check_closed_event!(nodes[1], 1, ClosureReason::ProcessingError { err: "ChannelMonitor storage failure".to_string() });
+ check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure, [nodes[0].node.get_our_node_id()], 100000);
+ check_closed_event!(nodes[0], 1, ClosureReason::CooperativeClosure, [nodes[1].node.get_our_node_id()], 100000);
}
#[test]
let (_, _, channel_id, _) = create_announced_chan_between_nodes(&nodes, 0, 1);
- let (payment_preimage_1, payment_hash_1, _) = route_payment(&nodes[0], &[&nodes[1]], 1_000_000);
- let (payment_preimage_2, payment_hash_2, _) = route_payment(&nodes[0], &[&nodes[1]], 1_000_000);
+ let (payment_preimage_1, payment_hash_1, ..) = route_payment(&nodes[0], &[&nodes[1]], 1_000_000);
+ let (payment_preimage_2, payment_hash_2, ..) = route_payment(&nodes[0], &[&nodes[1]], 1_000_000);
chanmon_cfgs[1].persister.set_update_ret(ChannelMonitorUpdateStatus::InProgress);
// `claim_funds` results in a ChannelMonitorUpdate.
assert_eq!(node_id, nodes[0].node.get_our_node_id());
nodes[0].node.handle_update_fulfill_htlc(&nodes[1].node.get_our_node_id(), &update_fulfill_1);
check_added_monitors!(nodes[0], 0);
- expect_payment_sent_without_paths!(nodes[0], payment_preimage_1);
+ expect_payment_sent(&nodes[0], payment_preimage_1, None, false, false);
nodes[0].node.handle_commitment_signed(&nodes[1].node.get_our_node_id(), &commitment_signed_b1);
check_added_monitors!(nodes[0], 1);
nodes[0].node.process_pending_htlc_forwards();
let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
- let persister: test_utils::TestPersister;
- let new_chain_monitor: test_utils::TestChainMonitor;
- let nodes_0_deserialized: ChannelManager<&test_utils::TestChainMonitor, &test_utils::TestBroadcaster, &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestFeeEstimator, &test_utils::TestRouter, &test_utils::TestLogger>;
+ let persister;
+ let new_chain_monitor;
let mut chan_config = test_default_channel_config();
chan_config.manually_accept_inbound_channels = true;
chan_config.channel_handshake_limits.trust_own_funding_0conf = true;
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(chan_config), Some(chan_config)]);
+ let nodes_0_deserialized;
+
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 100000, 10001, 43, None).unwrap();
nodes[0].chain_source.watched_outputs.lock().unwrap().clear();
reload_node!(nodes[0], &nodes[0].node.encode(), &[], persister, new_chain_monitor, nodes_0_deserialized);
- check_closed_event!(nodes[0], 1, ClosureReason::DisconnectedPeer);
+ check_closed_event!(nodes[0], 1, ClosureReason::DisconnectedPeer, [nodes[1].node.get_our_node_id()], 100000);
assert!(nodes[0].node.list_channels().is_empty());
}
let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
- let persister: test_utils::TestPersister;
- let new_chain_monitor: test_utils::TestChainMonitor;
- let nodes_1_deserialized: ChannelManager<&test_utils::TestChainMonitor, &test_utils::TestBroadcaster, &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestFeeEstimator, &test_utils::TestRouter, &test_utils::TestLogger>;
+ let persister;
+ let new_chain_monitor;
let mut chan_config = test_default_channel_config();
chan_config.manually_accept_inbound_channels = true;
chan_config.channel_handshake_limits.trust_own_funding_0conf = true;
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(chan_config), Some(chan_config)]);
+ let nodes_1_deserialized;
+
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 100000, 10001, 43, None).unwrap();
reload_node!(nodes[1], &nodes[1].node.encode(), &[], persister, new_chain_monitor, nodes_1_deserialized);
- check_closed_event!(nodes[1], 1, ClosureReason::DisconnectedPeer);
+ check_closed_event!(nodes[1], 1, ClosureReason::DisconnectedPeer, [nodes[0].node.get_our_node_id()], 100000);
assert!(nodes[1].node.list_channels().is_empty());
}
do_test_inbound_reload_without_init_mon(false, true);
do_test_inbound_reload_without_init_mon(false, false);
}
+
+#[test]
+fn test_blocked_chan_preimage_release() {
+ // Test that even if a channel's `ChannelMonitorUpdate` flow is blocked waiting on an event to
+ // be handled HTLC preimage `ChannelMonitorUpdate`s will still go out.
+ 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);
+ let chan_id_2 = create_announced_chan_between_nodes(&nodes, 1, 2).2;
+
+ send_payment(&nodes[0], &[&nodes[1], &nodes[2]], 5_000_000);
+
+ // Tee up two payments in opposite directions across nodes[1], one it sent to generate a
+ // PaymentSent event and one it forwards.
+ let (payment_preimage_1, payment_hash_1, ..) = route_payment(&nodes[1], &[&nodes[2]], 1_000_000);
+ let (payment_preimage_2, payment_hash_2, ..) = route_payment(&nodes[2], &[&nodes[1], &nodes[0]], 1_000_000);
+
+ // Claim the first payment to get a `PaymentSent` event (but don't handle it yet).
+ nodes[2].node.claim_funds(payment_preimage_1);
+ check_added_monitors(&nodes[2], 1);
+ expect_payment_claimed!(nodes[2], payment_hash_1, 1_000_000);
+
+ let cs_htlc_fulfill_updates = get_htlc_update_msgs!(nodes[2], nodes[1].node.get_our_node_id());
+ nodes[1].node.handle_update_fulfill_htlc(&nodes[2].node.get_our_node_id(), &cs_htlc_fulfill_updates.update_fulfill_htlcs[0]);
+ do_commitment_signed_dance(&nodes[1], &nodes[2], &cs_htlc_fulfill_updates.commitment_signed, false, false);
+ check_added_monitors(&nodes[1], 0);
+
+ // Now claim the second payment on nodes[0], which will ultimately result in nodes[1] trying to
+ // claim an HTLC on its channel with nodes[2], but that channel is blocked on the above
+ // `PaymentSent` event.
+ nodes[0].node.claim_funds(payment_preimage_2);
+ check_added_monitors(&nodes[0], 1);
+ expect_payment_claimed!(nodes[0], payment_hash_2, 1_000_000);
+
+ let as_htlc_fulfill_updates = get_htlc_update_msgs!(nodes[0], nodes[1].node.get_our_node_id());
+ nodes[1].node.handle_update_fulfill_htlc(&nodes[0].node.get_our_node_id(), &as_htlc_fulfill_updates.update_fulfill_htlcs[0]);
+ check_added_monitors(&nodes[1], 1); // We generate only a preimage monitor update
+ assert!(get_monitor!(nodes[1], chan_id_2).get_stored_preimages().contains_key(&payment_hash_2));
+ assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
+
+ // Finish the CS dance between nodes[0] and nodes[1]. Note that until the event handling, the
+ // update_fulfill_htlc + CS is held, even though the preimage is already on disk for the
+ // channel.
+ nodes[1].node.handle_commitment_signed(&nodes[0].node.get_our_node_id(), &as_htlc_fulfill_updates.commitment_signed);
+ check_added_monitors(&nodes[1], 1);
+ let (a, raa) = do_main_commitment_signed_dance(&nodes[1], &nodes[0], false);
+ assert!(a.is_none());
+
+ nodes[1].node.handle_revoke_and_ack(&nodes[0].node.get_our_node_id(), &raa);
+ check_added_monitors(&nodes[1], 0);
+ assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
+
+ let events = nodes[1].node.get_and_clear_pending_events();
+ assert_eq!(events.len(), 3);
+ if let Event::PaymentSent { .. } = events[0] {} else { panic!(); }
+ if let Event::PaymentPathSuccessful { .. } = events[2] {} else { panic!(); }
+ if let Event::PaymentForwarded { .. } = events[1] {} else { panic!(); }
+
+ // The event processing should release the last RAA updates on both channels.
+ check_added_monitors(&nodes[1], 2);
+
+ // When we fetch the next update the message getter will generate the next update for nodes[2],
+ // generating a further monitor update.
+ let bs_htlc_fulfill_updates = get_htlc_update_msgs!(nodes[1], nodes[2].node.get_our_node_id());
+ check_added_monitors(&nodes[1], 1);
+
+ nodes[2].node.handle_update_fulfill_htlc(&nodes[1].node.get_our_node_id(), &bs_htlc_fulfill_updates.update_fulfill_htlcs[0]);
+ do_commitment_signed_dance(&nodes[2], &nodes[1], &bs_htlc_fulfill_updates.commitment_signed, false, false);
+ expect_payment_sent(&nodes[2], payment_preimage_2, None, true, true);
+}
+
+fn do_test_inverted_mon_completion_order(with_latest_manager: bool, complete_bc_commitment_dance: bool) {
+ // When we forward a payment and receive `update_fulfill_htlc`+`commitment_signed` messages
+ // from the downstream channel, we immediately claim the HTLC on the upstream channel, before
+ // even doing a `commitment_signed` dance on the downstream channel. This implies that our
+ // `ChannelMonitorUpdate`s are generated in the right order - first we ensure we'll get our
+ // money, then we write the update that resolves the downstream node claiming their money. This
+ // is safe as long as `ChannelMonitorUpdate`s complete in the order in which they are
+ // generated, but of course this may not be the case. For asynchronous update writes, we have
+ // to ensure monitor updates can block each other, preventing the inversion all together.
+ let chanmon_cfgs = create_chanmon_cfgs(3);
+ let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
+
+ let persister;
+ let new_chain_monitor;
+ let nodes_1_deserialized;
+
+ let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
+ let mut nodes = create_network(3, &node_cfgs, &node_chanmgrs);
+
+ let chan_id_ab = create_announced_chan_between_nodes(&nodes, 0, 1).2;
+ let chan_id_bc = create_announced_chan_between_nodes(&nodes, 1, 2).2;
+
+ // Route a payment from A, through B, to C, then claim it on C. Once we pass B the
+ // `update_fulfill_htlc` we have a monitor update for both of B's channels. We complete the one
+ // on the B<->C channel but leave the A<->B monitor update pending, then reload B.
+ let (payment_preimage, payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 100_000);
+
+ let mon_ab = get_monitor!(nodes[1], chan_id_ab).encode();
+ let mut manager_b = Vec::new();
+ if !with_latest_manager {
+ manager_b = nodes[1].node.encode();
+ }
+
+ nodes[2].node.claim_funds(payment_preimage);
+ check_added_monitors(&nodes[2], 1);
+ expect_payment_claimed!(nodes[2], payment_hash, 100_000);
+
+ chanmon_cfgs[1].persister.set_update_ret(ChannelMonitorUpdateStatus::InProgress);
+ let cs_updates = get_htlc_update_msgs(&nodes[2], &nodes[1].node.get_our_node_id());
+ nodes[1].node.handle_update_fulfill_htlc(&nodes[2].node.get_our_node_id(), &cs_updates.update_fulfill_htlcs[0]);
+
+ // B generates a new monitor update for the A <-> B channel, but doesn't send the new messages
+ // for it since the monitor update is marked in-progress.
+ check_added_monitors(&nodes[1], 1);
+ assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
+
+ // Now step the Commitment Signed Dance between B and C forward a bit (or fully), ensuring we
+ // won't get the preimage when the nodes reconnect and we have to get it from the
+ // ChannelMonitor.
+ nodes[1].node.handle_commitment_signed(&nodes[2].node.get_our_node_id(), &cs_updates.commitment_signed);
+ check_added_monitors(&nodes[1], 1);
+ if complete_bc_commitment_dance {
+ let (bs_revoke_and_ack, bs_commitment_signed) = get_revoke_commit_msgs!(nodes[1], nodes[2].node.get_our_node_id());
+ nodes[2].node.handle_revoke_and_ack(&nodes[1].node.get_our_node_id(), &bs_revoke_and_ack);
+ check_added_monitors(&nodes[2], 1);
+ nodes[2].node.handle_commitment_signed(&nodes[1].node.get_our_node_id(), &bs_commitment_signed);
+ check_added_monitors(&nodes[2], 1);
+ let cs_raa = get_event_msg!(nodes[2], MessageSendEvent::SendRevokeAndACK, nodes[1].node.get_our_node_id());
+
+ // At this point node B still hasn't persisted the `ChannelMonitorUpdate` with the
+ // preimage in the A <-> B channel, which will prevent it from persisting the
+ // `ChannelMonitorUpdate` for the B<->C channel here to avoid "losing" the preimage.
+ nodes[1].node.handle_revoke_and_ack(&nodes[2].node.get_our_node_id(), &cs_raa);
+ check_added_monitors(&nodes[1], 0);
+ assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
+ }
+
+ // Now reload node B
+ if with_latest_manager {
+ manager_b = nodes[1].node.encode();
+ }
+
+ let mon_bc = get_monitor!(nodes[1], chan_id_bc).encode();
+ reload_node!(nodes[1], &manager_b, &[&mon_ab, &mon_bc], persister, new_chain_monitor, nodes_1_deserialized);
+
+ nodes[0].node.peer_disconnected(&nodes[1].node.get_our_node_id());
+ nodes[2].node.peer_disconnected(&nodes[1].node.get_our_node_id());
+
+ if with_latest_manager {
+ // If we used the latest ChannelManager to reload from, we should have both channels still
+ // live. The B <-> C channel's final RAA ChannelMonitorUpdate must still be blocked as
+ // before - the ChannelMonitorUpdate for the A <-> B channel hasn't completed.
+ // When we call `timer_tick_occurred` we will get that monitor update back, which we'll
+ // complete after reconnecting to our peers.
+ persister.set_update_ret(ChannelMonitorUpdateStatus::InProgress);
+ nodes[1].node.timer_tick_occurred();
+ check_added_monitors(&nodes[1], 1);
+ assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
+
+ // Now reconnect B to both A and C. If the B <-> C commitment signed dance wasn't run to
+ // the end go ahead and do that, though the
+ // `pending_responding_commitment_signed_dup_monitor` in `reconnect_args` indicates that we
+ // expect to *not* receive the final RAA ChannelMonitorUpdate.
+ if complete_bc_commitment_dance {
+ reconnect_nodes(ReconnectArgs::new(&nodes[1], &nodes[2]));
+ } else {
+ let mut reconnect_args = ReconnectArgs::new(&nodes[1], &nodes[2]);
+ reconnect_args.pending_responding_commitment_signed.1 = true;
+ reconnect_args.pending_responding_commitment_signed_dup_monitor.1 = true;
+ reconnect_args.pending_raa = (false, true);
+ reconnect_nodes(reconnect_args);
+ }
+
+ reconnect_nodes(ReconnectArgs::new(&nodes[0], &nodes[1]));
+
+ // (Finally) complete the A <-> B ChannelMonitorUpdate, ensuring the preimage is durably on
+ // disk in the proper ChannelMonitor, unblocking the B <-> C ChannelMonitor updating
+ // process.
+ let (outpoint, _, ab_update_id) = nodes[1].chain_monitor.latest_monitor_update_id.lock().unwrap().get(&chan_id_ab).unwrap().clone();
+ nodes[1].chain_monitor.chain_monitor.channel_monitor_updated(outpoint, ab_update_id).unwrap();
+
+ // When we fetch B's HTLC update messages next (now that the ChannelMonitorUpdate has
+ // completed), it will also release the final RAA ChannelMonitorUpdate on the B <-> C
+ // channel.
+ } else {
+ // If the ChannelManager used in the reload was stale, check that the B <-> C channel was
+ // closed.
+ //
+ // Note that this will also process the ChannelMonitorUpdates which were queued up when we
+ // reloaded the ChannelManager. This will re-emit the A<->B preimage as well as the B<->C
+ // force-closure ChannelMonitorUpdate. Once the A<->B preimage update completes, the claim
+ // commitment update will be allowed to go out.
+ check_added_monitors(&nodes[1], 0);
+ persister.set_update_ret(ChannelMonitorUpdateStatus::InProgress);
+ persister.set_update_ret(ChannelMonitorUpdateStatus::InProgress);
+ check_closed_event(&nodes[1], 1, ClosureReason::OutdatedChannelManager, false, &[nodes[2].node.get_our_node_id()], 100_000);
+ check_added_monitors(&nodes[1], 2);
+
+ nodes[1].node.timer_tick_occurred();
+ check_added_monitors(&nodes[1], 0);
+
+ // Don't bother to reconnect B to C - that channel has been closed. We don't need to
+ // exchange any messages here even though there's a pending commitment update because the
+ // ChannelMonitorUpdate hasn't yet completed.
+ reconnect_nodes(ReconnectArgs::new(&nodes[0], &nodes[1]));
+
+ let (outpoint, _, ab_update_id) = nodes[1].chain_monitor.latest_monitor_update_id.lock().unwrap().get(&chan_id_ab).unwrap().clone();
+ nodes[1].chain_monitor.chain_monitor.channel_monitor_updated(outpoint, ab_update_id).unwrap();
+
+ // The ChannelMonitorUpdate which was completed prior to the reconnect only contained the
+ // preimage (as it was a replay of the original ChannelMonitorUpdate from before we
+ // restarted). When we go to fetch the commitment transaction updates we'll poll the
+ // ChannelMonitorUpdate completion, then generate (and complete) a new ChannelMonitorUpdate
+ // with the actual commitment transaction, which will allow us to fulfill the HTLC with
+ // node A.
+ }
+
+ let bs_updates = get_htlc_update_msgs(&nodes[1], &nodes[0].node.get_our_node_id());
+ check_added_monitors(&nodes[1], 1);
+
+ nodes[0].node.handle_update_fulfill_htlc(&nodes[1].node.get_our_node_id(), &bs_updates.update_fulfill_htlcs[0]);
+ do_commitment_signed_dance(&nodes[0], &nodes[1], &bs_updates.commitment_signed, false, false);
+
+ expect_payment_forwarded!(nodes[1], &nodes[0], &nodes[2], Some(1_000), false, !with_latest_manager);
+
+ // Finally, check that the payment was, ultimately, seen as sent by node A.
+ expect_payment_sent(&nodes[0], payment_preimage, None, true, true);
+}
+
+#[test]
+fn test_inverted_mon_completion_order() {
+ do_test_inverted_mon_completion_order(true, true);
+ do_test_inverted_mon_completion_order(true, false);
+ do_test_inverted_mon_completion_order(false, true);
+ do_test_inverted_mon_completion_order(false, false);
+}
+
+fn do_test_durable_preimages_on_closed_channel(close_chans_before_reload: bool, close_only_a: bool, hold_post_reload_mon_update: bool) {
+ // Test that we can apply a `ChannelMonitorUpdate` with a payment preimage even if the channel
+ // is force-closed between when we generate the update on reload and when we go to handle the
+ // update or prior to generating the update at all.
+
+ if !close_chans_before_reload && close_only_a {
+ // If we're not closing, it makes no sense to "only close A"
+ panic!();
+ }
+
+ let chanmon_cfgs = create_chanmon_cfgs(3);
+ let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
+
+ let persister;
+ let new_chain_monitor;
+ let nodes_1_deserialized;
+
+ let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
+ let mut nodes = create_network(3, &node_cfgs, &node_chanmgrs);
+
+ let chan_id_ab = create_announced_chan_between_nodes(&nodes, 0, 1).2;
+ let chan_id_bc = create_announced_chan_between_nodes(&nodes, 1, 2).2;
+
+ // Route a payment from A, through B, to C, then claim it on C. Once we pass B the
+ // `update_fulfill_htlc` we have a monitor update for both of B's channels. We complete the one
+ // on the B<->C channel but leave the A<->B monitor update pending, then reload B.
+ let (payment_preimage, payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 1_000_000);
+
+ let mon_ab = get_monitor!(nodes[1], chan_id_ab).encode();
+
+ nodes[2].node.claim_funds(payment_preimage);
+ check_added_monitors(&nodes[2], 1);
+ expect_payment_claimed!(nodes[2], payment_hash, 1_000_000);
+
+ chanmon_cfgs[1].persister.set_update_ret(ChannelMonitorUpdateStatus::InProgress);
+ let cs_updates = get_htlc_update_msgs(&nodes[2], &nodes[1].node.get_our_node_id());
+ nodes[1].node.handle_update_fulfill_htlc(&nodes[2].node.get_our_node_id(), &cs_updates.update_fulfill_htlcs[0]);
+
+ // B generates a new monitor update for the A <-> B channel, but doesn't send the new messages
+ // for it since the monitor update is marked in-progress.
+ check_added_monitors(&nodes[1], 1);
+ assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
+
+ // Now step the Commitment Signed Dance between B and C forward a bit, ensuring we won't get
+ // the preimage when the nodes reconnect, at which point we have to ensure we get it from the
+ // ChannelMonitor.
+ nodes[1].node.handle_commitment_signed(&nodes[2].node.get_our_node_id(), &cs_updates.commitment_signed);
+ check_added_monitors(&nodes[1], 1);
+ let _ = get_revoke_commit_msgs!(nodes[1], nodes[2].node.get_our_node_id());
+
+ let mon_bc = get_monitor!(nodes[1], chan_id_bc).encode();
+
+ if close_chans_before_reload {
+ if !close_only_a {
+ chanmon_cfgs[1].persister.set_update_ret(ChannelMonitorUpdateStatus::InProgress);
+ nodes[1].node.force_close_broadcasting_latest_txn(&chan_id_bc, &nodes[2].node.get_our_node_id()).unwrap();
+ check_closed_broadcast(&nodes[1], 1, true);
+ check_closed_event(&nodes[1], 1, ClosureReason::HolderForceClosed, false, &[nodes[2].node.get_our_node_id()], 100000);
+ }
+
+ chanmon_cfgs[1].persister.set_update_ret(ChannelMonitorUpdateStatus::InProgress);
+ nodes[1].node.force_close_broadcasting_latest_txn(&chan_id_ab, &nodes[0].node.get_our_node_id()).unwrap();
+ check_closed_broadcast(&nodes[1], 1, true);
+ check_closed_event(&nodes[1], 1, ClosureReason::HolderForceClosed, false, &[nodes[0].node.get_our_node_id()], 100000);
+ }
+
+ // Now reload node B
+ let manager_b = nodes[1].node.encode();
+ reload_node!(nodes[1], &manager_b, &[&mon_ab, &mon_bc], persister, new_chain_monitor, nodes_1_deserialized);
+
+ nodes[0].node.peer_disconnected(&nodes[1].node.get_our_node_id());
+ nodes[2].node.peer_disconnected(&nodes[1].node.get_our_node_id());
+
+ if close_chans_before_reload {
+ // If the channels were already closed, B will rebroadcast its closing transactions here.
+ let bs_close_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
+ if close_only_a {
+ assert_eq!(bs_close_txn.len(), 2);
+ } else {
+ assert_eq!(bs_close_txn.len(), 3);
+ }
+ }
+
+ nodes[0].node.force_close_broadcasting_latest_txn(&chan_id_ab, &nodes[1].node.get_our_node_id()).unwrap();
+ check_closed_event(&nodes[0], 1, ClosureReason::HolderForceClosed, false, &[nodes[1].node.get_our_node_id()], 100000);
+ let as_closing_tx = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
+ assert_eq!(as_closing_tx.len(), 1);
+
+ // In order to give A's closing transaction to B without processing background events first,
+ // use the _without_consistency_checks utility method. This is similar to connecting blocks
+ // during startup prior to the node being full initialized.
+ mine_transaction_without_consistency_checks(&nodes[1], &as_closing_tx[0]);
+
+ // After a timer tick a payment preimage ChannelMonitorUpdate is applied to the A<->B
+ // ChannelMonitor (possible twice), even though the channel has since been closed.
+ check_added_monitors(&nodes[1], 0);
+ let mons_added = if close_chans_before_reload { if !close_only_a { 4 } else { 3 } } else { 2 };
+ if hold_post_reload_mon_update {
+ for _ in 0..mons_added {
+ persister.set_update_ret(ChannelMonitorUpdateStatus::InProgress);
+ }
+ }
+ nodes[1].node.timer_tick_occurred();
+ check_added_monitors(&nodes[1], mons_added);
+
+ // Finally, check that B created a payment preimage transaction and close out the payment.
+ let bs_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
+ assert_eq!(bs_txn.len(), if close_chans_before_reload && !close_only_a { 2 } else { 1 });
+ let bs_preimage_tx = &bs_txn[0];
+ check_spends!(bs_preimage_tx, as_closing_tx[0]);
+
+ if !close_chans_before_reload {
+ check_closed_broadcast(&nodes[1], 1, true);
+ check_closed_event(&nodes[1], 1, ClosureReason::CommitmentTxConfirmed, false, &[nodes[0].node.get_our_node_id()], 100000);
+ } else {
+ // While we forwarded the payment a while ago, we don't want to process events too early or
+ // we'll run background tasks we wanted to test individually.
+ expect_payment_forwarded!(nodes[1], nodes[0], nodes[2], None, true, !close_only_a);
+ }
+
+ mine_transactions(&nodes[0], &[&as_closing_tx[0], bs_preimage_tx]);
+ check_closed_broadcast(&nodes[0], 1, true);
+ expect_payment_sent(&nodes[0], payment_preimage, None, true, true);
+
+ if !close_chans_before_reload || close_only_a {
+ // Make sure the B<->C channel is still alive and well by sending a payment over it.
+ let mut reconnect_args = ReconnectArgs::new(&nodes[1], &nodes[2]);
+ reconnect_args.pending_responding_commitment_signed.1 = true;
+ if !close_chans_before_reload {
+ // TODO: If the A<->B channel was closed before we reloaded, the `ChannelManager`
+ // will consider the forwarded payment complete and allow the B<->C
+ // `ChannelMonitorUpdate` to complete, wiping the payment preimage. This should not
+ // be allowed, and needs fixing.
+ reconnect_args.pending_responding_commitment_signed_dup_monitor.1 = true;
+ }
+ reconnect_args.pending_raa.1 = true;
+
+ reconnect_nodes(reconnect_args);
+ let (outpoint, ab_update_id, _) = nodes[1].chain_monitor.latest_monitor_update_id.lock().unwrap().get(&chan_id_ab).unwrap().clone();
+ nodes[1].chain_monitor.chain_monitor.force_channel_monitor_updated(outpoint, ab_update_id);
+ expect_payment_forwarded!(nodes[1], nodes[0], nodes[2], Some(1000), true, false);
+ if !close_chans_before_reload {
+ // Once we call `process_pending_events` the final `ChannelMonitor` for the B<->C
+ // channel will fly, removing the payment preimage from it.
+ check_added_monitors(&nodes[1], 1);
+ }
+ assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
+ send_payment(&nodes[1], &[&nodes[2]], 100_000);
+ }
+}
+
+#[test]
+fn test_durable_preimages_on_closed_channel() {
+ do_test_durable_preimages_on_closed_channel(true, true, true);
+ do_test_durable_preimages_on_closed_channel(true, true, false);
+ do_test_durable_preimages_on_closed_channel(true, false, true);
+ do_test_durable_preimages_on_closed_channel(true, false, false);
+ do_test_durable_preimages_on_closed_channel(false, false, true);
+ do_test_durable_preimages_on_closed_channel(false, false, false);
+}
+
+fn do_test_reload_mon_update_completion_actions(close_during_reload: bool) {
+ // Test that if a `ChannelMonitorUpdate` completes but a `ChannelManager` isn't serialized
+ // before restart we run the monitor update completion action on startup.
+ let chanmon_cfgs = create_chanmon_cfgs(3);
+ let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
+
+ let persister;
+ let new_chain_monitor;
+ let nodes_1_deserialized;
+
+ let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
+ let mut nodes = create_network(3, &node_cfgs, &node_chanmgrs);
+
+ let chan_id_ab = create_announced_chan_between_nodes(&nodes, 0, 1).2;
+ let chan_id_bc = create_announced_chan_between_nodes(&nodes, 1, 2).2;
+
+ // Route a payment from A, through B, to C, then claim it on C. Once we pass B the
+ // `update_fulfill_htlc`+`commitment_signed` we have a monitor update for both of B's channels.
+ // We complete the commitment signed dance on the B<->C channel but leave the A<->B monitor
+ // update pending, then reload B. At that point, the final monitor update on the B<->C channel
+ // is still pending because it can't fly until the preimage is persisted on the A<->B monitor.
+ 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);
+
+ chanmon_cfgs[1].persister.set_update_ret(ChannelMonitorUpdateStatus::InProgress);
+ let cs_updates = get_htlc_update_msgs(&nodes[2], &nodes[1].node.get_our_node_id());
+ nodes[1].node.handle_update_fulfill_htlc(&nodes[2].node.get_our_node_id(), &cs_updates.update_fulfill_htlcs[0]);
+
+ // B generates a new monitor update for the A <-> B channel, but doesn't send the new messages
+ // for it since the monitor update is marked in-progress.
+ check_added_monitors(&nodes[1], 1);
+ assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
+
+ // Now step the Commitment Signed Dance between B and C and check that after the final RAA B
+ // doesn't let the preimage-removing monitor update fly.
+ nodes[1].node.handle_commitment_signed(&nodes[2].node.get_our_node_id(), &cs_updates.commitment_signed);
+ check_added_monitors(&nodes[1], 1);
+ let (bs_raa, bs_cs) = get_revoke_commit_msgs!(nodes[1], nodes[2].node.get_our_node_id());
+
+ nodes[2].node.handle_revoke_and_ack(&nodes[1].node.get_our_node_id(), &bs_raa);
+ check_added_monitors(&nodes[2], 1);
+ nodes[2].node.handle_commitment_signed(&nodes[1].node.get_our_node_id(), &bs_cs);
+ check_added_monitors(&nodes[2], 1);
+
+ let cs_final_raa = get_event_msg!(nodes[2], MessageSendEvent::SendRevokeAndACK, nodes[1].node.get_our_node_id());
+ nodes[1].node.handle_revoke_and_ack(&nodes[2].node.get_our_node_id(), &cs_final_raa);
+ check_added_monitors(&nodes[1], 0);
+
+ // Finally, reload node B and check that after we call `process_pending_events` once we realize
+ // we've completed the A<->B preimage-including monitor update and so can release the B<->C
+ // preimage-removing monitor update.
+ let mon_ab = get_monitor!(nodes[1], chan_id_ab).encode();
+ let mon_bc = get_monitor!(nodes[1], chan_id_bc).encode();
+ let manager_b = nodes[1].node.encode();
+ reload_node!(nodes[1], &manager_b, &[&mon_ab, &mon_bc], persister, new_chain_monitor, nodes_1_deserialized);
+
+ if close_during_reload {
+ // Test that we still free the B<->C channel if the A<->B channel closed while we reloaded
+ // (as learned about during the on-reload block connection).
+ nodes[0].node.force_close_broadcasting_latest_txn(&chan_id_ab, &nodes[1].node.get_our_node_id()).unwrap();
+ check_added_monitors!(nodes[0], 1);
+ check_closed_broadcast!(nodes[0], true);
+ check_closed_event(&nodes[0], 1, ClosureReason::HolderForceClosed, false, &[nodes[1].node.get_our_node_id()], 100_000);
+ let as_closing_tx = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
+ mine_transaction_without_consistency_checks(&nodes[1], &as_closing_tx[0]);
+ }
+
+ let bc_update_id = nodes[1].chain_monitor.latest_monitor_update_id.lock().unwrap().get(&chan_id_bc).unwrap().2;
+ let mut events = nodes[1].node.get_and_clear_pending_events();
+ assert_eq!(events.len(), if close_during_reload { 2 } else { 1 });
+ expect_payment_forwarded(events.pop().unwrap(), &nodes[1], &nodes[0], &nodes[2], Some(1000), close_during_reload, false);
+ if close_during_reload {
+ match events[0] {
+ Event::ChannelClosed { .. } => {},
+ _ => panic!(),
+ }
+ check_closed_broadcast!(nodes[1], true);
+ }
+
+ // Once we run event processing the monitor should free, check that it was indeed the B<->C
+ // channel which was updated.
+ check_added_monitors(&nodes[1], if close_during_reload { 2 } else { 1 });
+ let post_ev_bc_update_id = nodes[1].chain_monitor.latest_monitor_update_id.lock().unwrap().get(&chan_id_bc).unwrap().2;
+ assert!(bc_update_id != post_ev_bc_update_id);
+
+ // Finally, check that there's nothing left to do on B<->C reconnect and the channel operates
+ // fine.
+ nodes[2].node.peer_disconnected(&nodes[1].node.get_our_node_id());
+ reconnect_nodes(ReconnectArgs::new(&nodes[1], &nodes[2]));
+ send_payment(&nodes[1], &[&nodes[2]], 100_000);
+}
+
+#[test]
+fn test_reload_mon_update_completion_actions() {
+ do_test_reload_mon_update_completion_actions(true);
+ do_test_reload_mon_update_completion_actions(false);
+}
use bitcoin::secp256k1::{Secp256k1,ecdsa::Signature};
use bitcoin::secp256k1;
-use crate::ln::{PaymentPreimage, PaymentHash};
+use crate::ln::{ChannelId, PaymentPreimage, PaymentHash};
use crate::ln::features::{ChannelTypeFeatures, InitFeatures};
use crate::ln::msgs;
use crate::ln::msgs::DecodeError;
use crate::chain::chaininterface::{FeeEstimator, ConfirmationTarget, LowerBoundedFeeEstimator};
use crate::chain::channelmonitor::{ChannelMonitor, ChannelMonitorUpdate, ChannelMonitorUpdateStep, LATENCY_GRACE_PERIOD_BLOCKS, CLOSED_CHANNEL_UPDATE_ID};
use crate::chain::transaction::{OutPoint, TransactionData};
-use crate::sign::{WriteableEcdsaChannelSigner, EntropySource, ChannelSigner, SignerProvider, NodeSigner, Recipient};
+use crate::sign::{EcdsaChannelSigner, WriteableEcdsaChannelSigner, EntropySource, ChannelSigner, SignerProvider, NodeSigner, Recipient};
use crate::events::ClosureReason;
use crate::routing::gossip::NodeId;
use crate::util::ser::{Readable, ReadableArgs, Writeable, Writer, VecWriter};
use crate::util::logger::Logger;
use crate::util::errors::APIError;
-use crate::util::config::{UserConfig, ChannelConfig, LegacyChannelConfig, ChannelHandshakeConfig, ChannelHandshakeLimits};
+use crate::util::config::{UserConfig, ChannelConfig, LegacyChannelConfig, ChannelHandshakeConfig, ChannelHandshakeLimits, MaxDustHTLCExposure};
use crate::util::scid_utils::scid_from_parts;
use crate::io;
#[cfg(any(test, fuzzing, debug_assertions))]
use crate::sync::Mutex;
use bitcoin::hashes::hex::ToHex;
+use crate::sign::type_resolver::ChannelSignerType;
#[cfg(test)]
pub struct ChannelValueStat {
}
pub struct AvailableBalances {
- /// The amount that would go to us if we close the channel, ignoring any on-chain fees.
- pub balance_msat: u64,
/// Total amount available for our counterparty to send to us.
pub inbound_capacity_msat: u64,
/// Total amount available for us to send to our counterparty.
}
/// There are a few "states" and then a number of flags which can be applied:
-/// We first move through init with OurInitSent -> TheirInitSent -> FundingCreated -> FundingSent.
-/// TheirChannelReady and OurChannelReady then get set on FundingSent, and when both are set we
-/// move on to ChannelReady.
-/// Note that PeerDisconnected can be set on both ChannelReady and FundingSent.
-/// ChannelReady can then get all remaining flags set on it, until we finish shutdown, then we
-/// move on to ShutdownComplete, at which point most calls into this channel are disallowed.
+/// We first move through init with `OurInitSent` -> `TheirInitSent` -> `FundingCreated` -> `FundingSent`.
+/// `TheirChannelReady` and `OurChannelReady` then get set on `FundingSent`, and when both are set we
+/// move on to `ChannelReady`.
+/// Note that `PeerDisconnected` can be set on both `ChannelReady` and `FundingSent`.
+/// `ChannelReady` can then get all remaining flags set on it, until we finish shutdown, then we
+/// move on to `ShutdownComplete`, at which point most calls into this channel are disallowed.
enum ChannelState {
/// Implies we have (or are prepared to) send our open_channel/accept_channel message
OurInitSent = 1 << 0,
- /// Implies we have received their open_channel/accept_channel message
+ /// Implies we have received their `open_channel`/`accept_channel` message
TheirInitSent = 1 << 1,
- /// We have sent funding_created and are awaiting a funding_signed to advance to FundingSent.
- /// Note that this is nonsense for an inbound channel as we immediately generate funding_signed
- /// upon receipt of funding_created, so simply skip this state.
+ /// We have sent `funding_created` and are awaiting a `funding_signed` to advance to `FundingSent`.
+ /// Note that this is nonsense for an inbound channel as we immediately generate `funding_signed`
+ /// upon receipt of `funding_created`, so simply skip this state.
FundingCreated = 4,
- /// Set when we have received/sent funding_created and funding_signed and are thus now waiting
- /// on the funding transaction to confirm. The ChannelReady flags are set to indicate when we
+ /// Set when we have received/sent `funding_created` and `funding_signed` and are thus now waiting
+ /// on the funding transaction to confirm. The `ChannelReady` flags are set to indicate when we
/// and our counterparty consider the funding transaction confirmed.
FundingSent = 8,
- /// Flag which can be set on FundingSent to indicate they sent us a channel_ready message.
- /// Once both TheirChannelReady and OurChannelReady are set, state moves on to ChannelReady.
+ /// Flag which can be set on `FundingSent` to indicate they sent us a `channel_ready` message.
+ /// Once both `TheirChannelReady` and `OurChannelReady` are set, state moves on to `ChannelReady`.
TheirChannelReady = 1 << 4,
- /// Flag which can be set on FundingSent to indicate we sent them a channel_ready message.
- /// Once both TheirChannelReady and OurChannelReady are set, state moves on to ChannelReady.
+ /// Flag which can be set on `FundingSent` to indicate we sent them a `channel_ready` message.
+ /// Once both `TheirChannelReady` and `OurChannelReady` are set, state moves on to `ChannelReady`.
OurChannelReady = 1 << 5,
ChannelReady = 64,
- /// Flag which is set on ChannelReady and FundingSent indicating remote side is considered
- /// "disconnected" and no updates are allowed until after we've done a channel_reestablish
+ /// Flag which is set on `ChannelReady` and `FundingSent` indicating remote side is considered
+ /// "disconnected" and no updates are allowed until after we've done a `channel_reestablish`
/// dance.
PeerDisconnected = 1 << 7,
- /// Flag which is set on ChannelReady, FundingCreated, and FundingSent indicating the user has
- /// told us a ChannelMonitor update is pending async persistence somewhere and we should pause
+ /// Flag which is set on `ChannelReady`, FundingCreated, and `FundingSent` indicating the user has
+ /// told us a `ChannelMonitor` update is pending async persistence somewhere and we should pause
/// sending any outbound messages until they've managed to finish.
MonitorUpdateInProgress = 1 << 8,
/// Flag which implies that we have sent a commitment_signed but are awaiting the responding
/// messages as then we will be unable to determine which HTLCs they included in their
/// revoke_and_ack implicit ACK, so instead we have to hold them away temporarily to be sent
/// later.
- /// Flag is set on ChannelReady.
+ /// Flag is set on `ChannelReady`.
AwaitingRemoteRevoke = 1 << 9,
- /// Flag which is set on ChannelReady or FundingSent after receiving a shutdown message from
+ /// Flag which is set on `ChannelReady` or `FundingSent` after receiving a shutdown message from
/// the remote end. If set, they may not add any new HTLCs to the channel, and we are expected
/// to respond with our own shutdown message when possible.
RemoteShutdownSent = 1 << 10,
- /// Flag which is set on ChannelReady or FundingSent after sending a shutdown message. At this
+ /// Flag which is set on `ChannelReady` or `FundingSent` after sending a shutdown message. At this
/// point, we may not add any new HTLCs to the channel.
LocalShutdownSent = 1 << 11,
/// We've successfully negotiated a closing_signed dance. At this point ChannelManager is about
/// to drop us, but we store this anyway.
ShutdownComplete = 4096,
+ /// Flag which is set on `FundingSent` to indicate this channel is funded in a batch and the
+ /// broadcasting of the funding transaction is being held until all channels in the batch
+ /// have received funding_signed and have their monitors persisted.
+ WaitingForBatch = 1 << 13,
}
-const BOTH_SIDES_SHUTDOWN_MASK: u32 = ChannelState::LocalShutdownSent as u32 | ChannelState::RemoteShutdownSent as u32;
-const MULTI_STATE_FLAGS: u32 = BOTH_SIDES_SHUTDOWN_MASK | ChannelState::PeerDisconnected as u32 | ChannelState::MonitorUpdateInProgress as u32;
+const BOTH_SIDES_SHUTDOWN_MASK: u32 =
+ ChannelState::LocalShutdownSent as u32 |
+ ChannelState::RemoteShutdownSent as u32;
+const MULTI_STATE_FLAGS: u32 =
+ BOTH_SIDES_SHUTDOWN_MASK |
+ ChannelState::PeerDisconnected as u32 |
+ ChannelState::MonitorUpdateInProgress as u32;
+const STATE_FLAGS: u32 =
+ MULTI_STATE_FLAGS |
+ ChannelState::TheirChannelReady as u32 |
+ ChannelState::OurChannelReady as u32 |
+ ChannelState::AwaitingRemoteRevoke as u32 |
+ ChannelState::WaitingForBatch as u32;
pub const INITIAL_COMMITMENT_NUMBER: u64 = (1 << 48) - 1;
}
/// 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, [u8; 32])>
+ Vec<(HTLCSource, PaymentHash, PublicKey, ChannelId)>,
+ Option<Txid>
);
/// If the majority of the channels funds are to the fundee and the initiator holds only just
/// See [`ChannelContext::sent_message_awaiting_response`] for more information.
pub(crate) const DISCONNECT_PEER_AWAITING_RESPONSE_TICKS: usize = 2;
+/// The number of ticks that may elapse while we're waiting for an unfunded outbound/inbound channel
+/// to be promoted to a [`Channel`] since the unfunded channel was created. An unfunded channel
+/// exceeding this age limit will be force-closed and purged from memory.
+pub(crate) const UNFUNDED_CHANNEL_AGE_LIMIT_TICKS: usize = 60;
+
+/// Number of blocks needed for an output from a coinbase transaction to be spendable.
+pub(crate) const COINBASE_MATURITY: u32 = 100;
+
struct PendingChannelMonitorUpdate {
update: ChannelMonitorUpdate,
}
(0, update, required),
});
+/// The `ChannelPhase` enum describes the current phase in life of a lightning channel with each of
+/// its variants containing an appropriate channel struct.
+pub(super) enum ChannelPhase<SP: Deref> where SP::Target: SignerProvider {
+ UnfundedOutboundV1(OutboundV1Channel<SP>),
+ UnfundedInboundV1(InboundV1Channel<SP>),
+ Funded(Channel<SP>),
+}
+
+impl<'a, SP: Deref> ChannelPhase<SP> where
+ SP::Target: SignerProvider,
+ <SP::Target as SignerProvider>::Signer: ChannelSigner,
+{
+ pub fn context(&'a self) -> &'a ChannelContext<SP> {
+ match self {
+ ChannelPhase::Funded(chan) => &chan.context,
+ ChannelPhase::UnfundedOutboundV1(chan) => &chan.context,
+ ChannelPhase::UnfundedInboundV1(chan) => &chan.context,
+ }
+ }
+
+ pub fn context_mut(&'a mut self) -> &'a mut ChannelContext<SP> {
+ match self {
+ ChannelPhase::Funded(ref mut chan) => &mut chan.context,
+ ChannelPhase::UnfundedOutboundV1(ref mut chan) => &mut chan.context,
+ ChannelPhase::UnfundedInboundV1(ref mut chan) => &mut chan.context,
+ }
+ }
+}
+
+/// Contains all state common to unfunded inbound/outbound channels.
+pub(super) struct UnfundedChannelContext {
+ /// A counter tracking how many ticks have elapsed since this unfunded channel was
+ /// created. If this unfunded channel reaches peer has yet to respond after reaching
+ /// `UNFUNDED_CHANNEL_AGE_LIMIT_TICKS`, it will be force-closed and purged from memory.
+ ///
+ /// This is so that we don't keep channels around that haven't progressed to a funded state
+ /// in a timely manner.
+ unfunded_channel_age_ticks: usize,
+}
+
+impl UnfundedChannelContext {
+ /// Determines whether we should force-close and purge this unfunded channel from memory due to it
+ /// having reached the unfunded channel age limit.
+ ///
+ /// This should be called on every [`super::channelmanager::ChannelManager::timer_tick_occurred`].
+ pub fn should_expire_unfunded_channel(&mut self) -> bool {
+ self.unfunded_channel_age_ticks += 1;
+ self.unfunded_channel_age_ticks >= UNFUNDED_CHANNEL_AGE_LIMIT_TICKS
+ }
+}
+
/// Contains everything about the channel including state, and various flags.
-pub(super) struct ChannelContext<Signer: ChannelSigner> {
+pub(super) struct ChannelContext<SP: Deref> where SP::Target: SignerProvider {
config: LegacyChannelConfig,
// Track the previous `ChannelConfig` so that we can continue forwarding HTLCs that were
user_id: u128,
- channel_id: [u8; 32],
- temporary_channel_id: Option<[u8; 32]>, // Will be `None` for channels created prior to 0.0.115.
+ /// The current channel ID.
+ channel_id: ChannelId,
+ /// The temporary channel ID used during channel setup. Value kept even after transitioning to a final channel ID.
+ /// Will be `None` for channels created prior to 0.0.115.
+ temporary_channel_id: Option<ChannelId>,
channel_state: u32,
// When we reach max(6 blocks, minimum_depth), we need to send an AnnouncementSigs message to
latest_monitor_update_id: u64,
- holder_signer: Signer,
+ holder_signer: ChannelSignerType<<SP::Target as SignerProvider>::Signer>,
shutdown_scriptpubkey: Option<ShutdownScript>,
destination_script: Script,
#[cfg(not(test))]
closing_fee_limits: Option<(u64, u64)>,
- /// Flag that ensures that `accept_inbound_channel` must be called before `funding_created`
- /// is executed successfully. The reason for this flag is that when the
- /// `UserConfig::manually_accept_inbound_channels` config flag is set to true, inbound channels
- /// are required to be manually accepted by the node operator before the `msgs::AcceptChannel`
- /// message is created and sent out. During the manual accept process, `accept_inbound_channel`
- /// is called by `ChannelManager::accept_inbound_channel`.
- ///
- /// The flag counteracts that a counterparty node could theoretically send a
- /// `msgs::FundingCreated` message before the node operator has manually accepted an inbound
- /// channel request made by the counterparty node. That would execute `funding_created` before
- /// `accept_inbound_channel`, and `funding_created` should therefore not execute successfully.
- inbound_awaiting_accept: bool,
-
/// The hash of the block in which the funding transaction was included.
funding_tx_confirmed_in: Option<BlockHash>,
funding_tx_confirmation_height: u32,
pub(crate) channel_transaction_parameters: ChannelTransactionParameters,
funding_transaction: Option<Transaction>,
+ is_batch_funding: Option<()>,
counterparty_cur_commitment_point: Option<PublicKey>,
counterparty_prev_commitment_point: Option<PublicKey>,
blocked_monitor_updates: Vec<PendingChannelMonitorUpdate>,
}
-impl<Signer: ChannelSigner> ChannelContext<Signer> {
+impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
/// Allowed in any state (including after shutdown)
pub fn get_update_time_counter(&self) -> u32 {
self.update_time_counter
/// Returns true if we've ever received a message from the remote end for this Channel
pub fn have_received_message(&self) -> bool {
- self.channel_state > (ChannelState::OurInitSent as u32)
+ self.channel_state & !STATE_FLAGS > (ChannelState::OurInitSent as u32)
}
/// Returns true if this channel is fully established and not known to be closing.
// Public utilities:
- pub fn channel_id(&self) -> [u8; 32] {
+ pub fn channel_id(&self) -> ChannelId {
self.channel_id
}
// Return the `temporary_channel_id` used during channel establishment.
//
// Will return `None` for channels created prior to LDK version 0.0.115.
- pub fn temporary_channel_id(&self) -> Option<[u8; 32]> {
+ pub fn temporary_channel_id(&self) -> Option<ChannelId> {
self.temporary_channel_id
}
&self.channel_type
}
- /// Guaranteed to be Some after both ChannelReady messages have been exchanged (and, thus,
- /// is_usable() returns true).
- /// Allowed in any state (including after shutdown)
+ /// Gets the channel's `short_channel_id`.
+ ///
+ /// Will return `None` if the channel hasn't been confirmed yet.
pub fn get_short_channel_id(&self) -> Option<u64> {
self.short_channel_id
}
}
/// Only allowed immediately after deserialization if get_outbound_scid_alias returns 0,
- /// indicating we were written by LDK prior to 0.0.106 which did not set outbound SCID aliases.
+ /// indicating we were written by LDK prior to 0.0.106 which did not set outbound SCID aliases
+ /// or prior to any channel actions during `Channel` initialization.
pub fn set_outbound_scid_alias(&mut self, outbound_scid_alias: u64) {
- assert_eq!(self.outbound_scid_alias, 0);
+ debug_assert_eq!(self.outbound_scid_alias, 0);
self.outbound_scid_alias = outbound_scid_alias;
}
/// Returns the funding_txo we either got from our peer, or were given by
- /// get_outbound_funding_created.
+ /// get_funding_created.
pub fn get_funding_txo(&self) -> Option<OutPoint> {
self.channel_transaction_parameters.funding_outpoint
}
cmp::max(self.config.options.cltv_expiry_delta, MIN_CLTV_EXPIRY_DELTA)
}
- pub fn get_max_dust_htlc_exposure_msat(&self) -> u64 {
- self.config.options.max_dust_htlc_exposure_msat
+ pub fn get_max_dust_htlc_exposure_msat<F: Deref>(&self,
+ fee_estimator: &LowerBoundedFeeEstimator<F>) -> u64
+ where F::Target: FeeEstimator
+ {
+ match self.config.options.max_dust_htlc_exposure {
+ MaxDustHTLCExposure::FeeRateMultiplier(multiplier) => {
+ let feerate_per_kw = fee_estimator.bounded_sat_per_1000_weight(
+ ConfirmationTarget::HighPriority);
+ feerate_per_kw as u64 * multiplier
+ },
+ MaxDustHTLCExposure::FixedLimitMsat(limit) => limit,
+ }
}
/// Returns the previous [`ChannelConfig`] applied to this channel, if any.
// Checks whether we should emit a `ChannelPending` event.
pub(crate) fn should_emit_channel_pending_event(&mut self) -> bool {
- self.is_funding_initiated() && !self.channel_pending_event_emitted
+ self.is_funding_broadcast() && !self.channel_pending_event_emitted
}
// Returns whether we already emitted a `ChannelPending` event.
did_channel_update
}
- /// Returns true if funding_created was sent/received.
- pub fn is_funding_initiated(&self) -> bool {
- self.channel_state >= ChannelState::FundingSent as u32
+ /// Returns true if funding_signed was sent/received and the
+ /// funding transaction has been broadcast if necessary.
+ pub fn is_funding_broadcast(&self) -> bool {
+ self.channel_state & !STATE_FLAGS >= ChannelState::FundingSent as u32 &&
+ self.channel_state & ChannelState::WaitingForBatch as u32 == 0
}
/// Transaction nomenclature is somewhat confusing here as there are many different cases - a
log_trace!(logger, "Building commitment transaction number {} (really {} xor {}) for channel {} for {}, generated by {} with fee {}...",
commitment_number, (INITIAL_COMMITMENT_NUMBER - commitment_number),
get_commitment_transaction_number_obscure_factor(&self.get_holder_pubkeys().payment_point, &self.get_counterparty_pubkeys().payment_point, self.is_outbound()),
- log_bytes!(self.channel_id), if local { "us" } else { "remote" }, if generated_by_local { "us" } else { "remote" }, feerate_per_kw);
+ &self.channel_id,
+ if local { "us" } else { "remote" }, if generated_by_local { "us" } else { "remote" }, feerate_per_kw);
macro_rules! get_htlc_in_commitment {
($htlc: expr, $offered: expr) => {
feerate_per_kw as u64 * htlc_timeout_tx_weight(self.get_channel_type()) / 1000
};
if $htlc.amount_msat / 1000 >= broadcaster_dust_limit_satoshis + htlc_tx_fee {
- log_trace!(logger, " ...including {} {} HTLC {} (hash {}) with value {}", if $outbound { "outbound" } else { "inbound" }, $state_name, $htlc.htlc_id, log_bytes!($htlc.payment_hash.0), $htlc.amount_msat);
+ log_trace!(logger, " ...including {} {} HTLC {} (hash {}) with value {}", if $outbound { "outbound" } else { "inbound" }, $state_name, $htlc.htlc_id, &$htlc.payment_hash, $htlc.amount_msat);
included_non_dust_htlcs.push((htlc_in_tx, $source));
} else {
- log_trace!(logger, " ...including {} {} dust HTLC {} (hash {}) with value {} due to dust limit", if $outbound { "outbound" } else { "inbound" }, $state_name, $htlc.htlc_id, log_bytes!($htlc.payment_hash.0), $htlc.amount_msat);
+ log_trace!(logger, " ...including {} {} dust HTLC {} (hash {}) with value {} due to dust limit", if $outbound { "outbound" } else { "inbound" }, $state_name, $htlc.htlc_id, &$htlc.payment_hash, $htlc.amount_msat);
included_dust_htlcs.push((htlc_in_tx, $source));
}
} else {
feerate_per_kw as u64 * htlc_success_tx_weight(self.get_channel_type()) / 1000
};
if $htlc.amount_msat / 1000 >= broadcaster_dust_limit_satoshis + htlc_tx_fee {
- log_trace!(logger, " ...including {} {} HTLC {} (hash {}) with value {}", if $outbound { "outbound" } else { "inbound" }, $state_name, $htlc.htlc_id, log_bytes!($htlc.payment_hash.0), $htlc.amount_msat);
+ log_trace!(logger, " ...including {} {} HTLC {} (hash {}) with value {}", if $outbound { "outbound" } else { "inbound" }, $state_name, $htlc.htlc_id, &$htlc.payment_hash, $htlc.amount_msat);
included_non_dust_htlcs.push((htlc_in_tx, $source));
} else {
- log_trace!(logger, " ...including {} {} dust HTLC {} (hash {}) with value {}", if $outbound { "outbound" } else { "inbound" }, $state_name, $htlc.htlc_id, log_bytes!($htlc.payment_hash.0), $htlc.amount_msat);
+ log_trace!(logger, " ...including {} {} dust HTLC {} (hash {}) with value {}", if $outbound { "outbound" } else { "inbound" }, $state_name, $htlc.htlc_id, &$htlc.payment_hash, $htlc.amount_msat);
included_dust_htlcs.push((htlc_in_tx, $source));
}
}
add_htlc_output!(htlc, false, None, state_name);
remote_htlc_total_msat += htlc.amount_msat;
} else {
- log_trace!(logger, " ...not including inbound HTLC {} (hash {}) with value {} due to state ({})", htlc.htlc_id, log_bytes!(htlc.payment_hash.0), htlc.amount_msat, state_name);
+ log_trace!(logger, " ...not including inbound HTLC {} (hash {}) with value {} due to state ({})", htlc.htlc_id, &htlc.payment_hash, htlc.amount_msat, state_name);
match &htlc.state {
&InboundHTLCState::LocalRemoved(ref reason) => {
if generated_by_local {
add_htlc_output!(htlc, true, Some(&htlc.source), state_name);
local_htlc_total_msat += htlc.amount_msat;
} else {
- log_trace!(logger, " ...not including outbound HTLC {} (hash {}) with value {} due to state ({})", htlc.htlc_id, log_bytes!(htlc.payment_hash.0), htlc.amount_msat, state_name);
+ log_trace!(logger, " ...not including outbound HTLC {} (hash {}) with value {} due to state ({})", htlc.htlc_id, &htlc.payment_hash, htlc.amount_msat, state_name);
match htlc.state {
OutboundHTLCState::AwaitingRemoteRevokeToRemove(OutboundHTLCOutcome::Success(_))|OutboundHTLCState::AwaitingRemovedRemoteRevoke(OutboundHTLCOutcome::Success(_)) => {
value_to_self_msat_offset -= htlc.amount_msat as i64;
/// The result is a transaction which we can revoke broadcastership of (ie a "local" transaction)
/// TODO Some magic rust shit to compile-time check this?
fn build_holder_transaction_keys(&self, commitment_number: u64) -> TxCreationKeys {
- let per_commitment_point = self.holder_signer.get_per_commitment_point(commitment_number, &self.secp_ctx);
+ let per_commitment_point = self.holder_signer.as_ref().get_per_commitment_point(commitment_number, &self.secp_ctx);
let delayed_payment_base = &self.get_holder_pubkeys().delayed_payment_basepoint;
let htlc_basepoint = &self.get_holder_pubkeys().htlc_basepoint;
let counterparty_pubkeys = self.get_counterparty_pubkeys();
#[inline]
/// Creates a set of keys for build_commitment_transaction to generate a transaction which we
/// will sign and send to our counterparty.
- /// If an Err is returned, it is a ChannelError::Close (for get_outbound_funding_created)
+ /// If an Err is returned, it is a ChannelError::Close (for get_funding_created)
fn build_remote_transaction_keys(&self) -> TxCreationKeys {
//TODO: Ensure that the payment_key derived here ends up in the library users' wallet as we
//may see payments to it!
/// Doesn't bother handling the
/// if-we-removed-it-already-but-haven't-fully-resolved-they-can-still-send-an-inbound-HTLC
/// corner case properly.
- pub fn get_available_balances(&self) -> AvailableBalances {
+ pub fn get_available_balances<F: Deref>(&self, fee_estimator: &LowerBoundedFeeEstimator<F>)
+ -> AvailableBalances
+ where F::Target: FeeEstimator
+ {
let context = &self;
// Note that we have to handle overflow due to the above case.
let inbound_stats = context.get_inbound_pending_htlc_stats(None);
let outbound_stats = context.get_outbound_pending_htlc_stats(None);
- let mut balance_msat = context.value_to_self_msat;
- for ref htlc in context.pending_inbound_htlcs.iter() {
- if let InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::Fulfill(_)) = htlc.state {
- balance_msat += htlc.amount_msat;
- }
- }
- balance_msat -= outbound_stats.pending_htlcs_value_msat;
-
let outbound_capacity_msat = context.value_to_self_msat
.saturating_sub(outbound_stats.pending_htlcs_value_msat)
.saturating_sub(
// send above the dust limit (as the router can always overpay to meet the dust limit).
let mut remaining_msat_below_dust_exposure_limit = None;
let mut dust_exposure_dust_limit_msat = 0;
+ let max_dust_htlc_exposure_msat = context.get_max_dust_htlc_exposure_msat(fee_estimator);
let (htlc_success_dust_limit, htlc_timeout_dust_limit) = if context.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
(context.counterparty_dust_limit_satoshis, context.holder_dust_limit_satoshis)
context.holder_dust_limit_satoshis + dust_buffer_feerate * htlc_timeout_tx_weight(context.get_channel_type()) / 1000)
};
let on_counterparty_dust_htlc_exposure_msat = inbound_stats.on_counterparty_tx_dust_exposure_msat + outbound_stats.on_counterparty_tx_dust_exposure_msat;
- if on_counterparty_dust_htlc_exposure_msat as i64 + htlc_success_dust_limit as i64 * 1000 - 1 > context.get_max_dust_htlc_exposure_msat() as i64 {
+ if on_counterparty_dust_htlc_exposure_msat as i64 + htlc_success_dust_limit as i64 * 1000 - 1 > max_dust_htlc_exposure_msat as i64 {
remaining_msat_below_dust_exposure_limit =
- Some(context.get_max_dust_htlc_exposure_msat().saturating_sub(on_counterparty_dust_htlc_exposure_msat));
+ Some(max_dust_htlc_exposure_msat.saturating_sub(on_counterparty_dust_htlc_exposure_msat));
dust_exposure_dust_limit_msat = cmp::max(dust_exposure_dust_limit_msat, htlc_success_dust_limit * 1000);
}
let on_holder_dust_htlc_exposure_msat = inbound_stats.on_holder_tx_dust_exposure_msat + outbound_stats.on_holder_tx_dust_exposure_msat;
- if on_holder_dust_htlc_exposure_msat as i64 + htlc_timeout_dust_limit as i64 * 1000 - 1 > context.get_max_dust_htlc_exposure_msat() as i64 {
+ if on_holder_dust_htlc_exposure_msat as i64 + htlc_timeout_dust_limit as i64 * 1000 - 1 > max_dust_htlc_exposure_msat as i64 {
remaining_msat_below_dust_exposure_limit = Some(cmp::min(
remaining_msat_below_dust_exposure_limit.unwrap_or(u64::max_value()),
- context.get_max_dust_htlc_exposure_msat().saturating_sub(on_holder_dust_htlc_exposure_msat)));
+ max_dust_htlc_exposure_msat.saturating_sub(on_holder_dust_htlc_exposure_msat)));
dust_exposure_dust_limit_msat = cmp::max(dust_exposure_dust_limit_msat, htlc_timeout_dust_limit * 1000);
}
outbound_capacity_msat,
next_outbound_htlc_limit_msat: available_capacity_msat,
next_outbound_htlc_minimum_msat,
- balance_msat,
}
}
res
}
- /// Returns transaction if there is pending funding transaction that is yet to broadcast
- pub fn unbroadcasted_funding(&self) -> Option<Transaction> {
- if self.channel_state & (ChannelState::FundingCreated as u32) != 0 {
- self.funding_transaction.clone()
+ fn if_unbroadcasted_funding<F, O>(&self, f: F) -> Option<O>
+ where F: Fn() -> Option<O> {
+ if self.channel_state & ChannelState::FundingCreated as u32 != 0 ||
+ self.channel_state & ChannelState::WaitingForBatch as u32 != 0 {
+ f()
} else {
None
}
}
+ /// Returns the transaction if there is a pending funding transaction that is yet to be
+ /// broadcast.
+ pub fn unbroadcasted_funding(&self) -> Option<Transaction> {
+ self.if_unbroadcasted_funding(|| self.funding_transaction.clone())
+ }
+
+ /// Returns the transaction ID if there is a pending funding transaction that is yet to be
+ /// broadcast.
+ pub fn unbroadcasted_funding_txid(&self) -> Option<Txid> {
+ self.if_unbroadcasted_funding(||
+ self.channel_transaction_parameters.funding_outpoint.map(|txo| txo.txid)
+ )
+ }
+
+ /// Returns whether the channel is funded in a batch.
+ pub fn is_batch_funding(&self) -> bool {
+ self.is_batch_funding.is_some()
+ }
+
+ /// Returns the transaction ID if there is a pending batch funding transaction that is yet to be
+ /// broadcast.
+ pub fn unbroadcasted_batch_funding_txid(&self) -> Option<Txid> {
+ self.unbroadcasted_funding_txid().filter(|_| self.is_batch_funding())
+ }
+
/// Gets the latest commitment transaction and any dependent transactions for relay (forcing
/// shutdown of this channel - no more calls into this Channel may be made afterwards except
/// those explicitly stated to be allowed after shutdown completes, eg some simple getters).
}))
} else { None }
} else { None };
+ let unbroadcasted_batch_funding_txid = self.unbroadcasted_batch_funding_txid();
self.channel_state = ChannelState::ShutdownComplete as u32;
self.update_time_counter += 1;
- (monitor_update, dropped_outbound_htlcs)
+ (monitor_update, dropped_outbound_htlcs, unbroadcasted_batch_funding_txid)
}
}
(commitment_tx_base_weight(channel_type_features) + num_htlcs as u64 * COMMITMENT_TX_WEIGHT_PER_HTLC) * feerate_per_kw as u64 / 1000 * 1000
}
-// TODO: We should refactor this to be an Inbound/OutboundChannel until initial setup handshaking
-// has been completed, and then turn into a Channel to get compiler-time enforcement of things like
-// calling channel_id() before we're set up or things like get_outbound_funding_signed on an
-// inbound channel.
-//
// Holder designates channel data owned for the benefit of the user client.
// Counterparty designates channel data owned by the another channel participant entity.
-pub(super) struct Channel<Signer: ChannelSigner> {
- pub context: ChannelContext<Signer>,
+pub(super) struct Channel<SP: Deref> where SP::Target: SignerProvider {
+ pub context: ChannelContext<SP>,
}
#[cfg(any(test, fuzzing))]
feerate: u32,
}
-impl<Signer: WriteableEcdsaChannelSigner> Channel<Signer> {
- fn check_remote_fee<F: Deref, L: Deref>(fee_estimator: &LowerBoundedFeeEstimator<F>,
- feerate_per_kw: u32, cur_feerate_per_kw: Option<u32>, logger: &L)
- -> Result<(), ChannelError> where F::Target: FeeEstimator, L::Target: Logger,
+impl<SP: Deref> Channel<SP> where
+ SP::Target: SignerProvider,
+ <SP::Target as SignerProvider>::Signer: WriteableEcdsaChannelSigner
+{
+ fn check_remote_fee<F: Deref, L: Deref>(
+ channel_type: &ChannelTypeFeatures, fee_estimator: &LowerBoundedFeeEstimator<F>,
+ feerate_per_kw: u32, cur_feerate_per_kw: Option<u32>, logger: &L
+ ) -> Result<(), ChannelError> where F::Target: FeeEstimator, L::Target: Logger,
{
// We only bound the fee updates on the upper side to prevent completely absurd feerates,
// always accepting up to 25 sat/vByte or 10x our fee estimator's "High Priority" fee.
// We generally don't care too much if they set the feerate to something very high, but it
- // could result in the channel being useless due to everything being dust.
- let upper_limit = cmp::max(250 * 25,
- fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::HighPriority) as u64 * 10);
- 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)));
- }
- let lower_limit = fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::Background);
+ // could result in the channel being useless due to everything being dust. This doesn't
+ // 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);
+ 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
+ } else {
+ ConfirmationTarget::Background
+ };
+ 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
}
assert_eq!(self.context.channel_state & ChannelState::ShutdownComplete as u32, 0);
- let payment_hash_calc = PaymentHash(Sha256::hash(&payment_preimage_arg.0[..]).into_inner());
-
// ChannelManager may generate duplicate claims/fails due to HTLC update events from
// on-chain ChannelsMonitors during block rescan. Ideally we'd figure out a way to drop
// these, but for now we just have to treat them as normal.
let mut htlc_value_msat = 0;
for (idx, htlc) in self.context.pending_inbound_htlcs.iter().enumerate() {
if htlc.htlc_id == htlc_id_arg {
- assert_eq!(htlc.payment_hash, payment_hash_calc);
+ debug_assert_eq!(htlc.payment_hash, PaymentHash(Sha256::hash(&payment_preimage_arg.0[..]).into_inner()));
+ log_debug!(logger, "Claiming inbound HTLC id {} with payment hash {} with preimage {}",
+ htlc.htlc_id, htlc.payment_hash, payment_preimage_arg);
match htlc.state {
InboundHTLCState::Committed => {},
InboundHTLCState::LocalRemoved(ref reason) => {
if let &InboundHTLCRemovalReason::Fulfill(_) = reason {
} else {
- log_warn!(logger, "Have preimage and want to fulfill HTLC with payment hash {} we already failed against channel {}", log_bytes!(htlc.payment_hash.0), log_bytes!(self.context.channel_id()));
+ log_warn!(logger, "Have preimage and want to fulfill HTLC with payment hash {} we already failed against channel {}", &htlc.payment_hash, &self.context.channel_id());
debug_assert!(false, "Tried to fulfill an HTLC that was already failed");
}
return UpdateFulfillFetch::DuplicateClaim {};
},
&HTLCUpdateAwaitingACK::FailHTLC { htlc_id, .. } => {
if htlc_id_arg == htlc_id {
- log_warn!(logger, "Have preimage and want to fulfill HTLC with pending failure against channel {}", log_bytes!(self.context.channel_id()));
+ log_warn!(logger, "Have preimage and want to fulfill HTLC with pending failure against channel {}", &self.context.channel_id());
// TODO: We may actually be able to switch to a fulfill here, though its
// rare enough it may not be worth the complexity burden.
debug_assert!(false, "Tried to fulfill an HTLC that was already failed");
_ => {}
}
}
- log_trace!(logger, "Adding HTLC claim to holding_cell in channel {}! Current state: {}", log_bytes!(self.context.channel_id()), self.context.channel_state);
+ log_trace!(logger, "Adding HTLC claim to holding_cell in channel {}! Current state: {}", &self.context.channel_id(), self.context.channel_state);
self.context.holding_cell_htlc_updates.push(HTLCUpdateAwaitingACK::ClaimHTLC {
payment_preimage: payment_preimage_arg, htlc_id: htlc_id_arg,
});
debug_assert!(false, "Have an inbound HTLC we tried to claim before it was fully committed to");
return UpdateFulfillFetch::NewClaim { monitor_update, htlc_value_msat, msg: None };
}
- log_trace!(logger, "Upgrading HTLC {} to LocalRemoved with a Fulfill in channel {}!", log_bytes!(htlc.payment_hash.0), log_bytes!(self.context.channel_id));
+ log_trace!(logger, "Upgrading HTLC {} to LocalRemoved with a Fulfill in channel {}!", &htlc.payment_hash, &self.context.channel_id);
htlc.state = InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::Fulfill(payment_preimage_arg.clone()));
}
_ => {}
}
}
- log_trace!(logger, "Placing failure for HTLC ID {} in holding cell in channel {}.", htlc_id_arg, log_bytes!(self.context.channel_id()));
+ log_trace!(logger, "Placing failure for HTLC ID {} in holding cell in channel {}.", htlc_id_arg, &self.context.channel_id());
self.context.holding_cell_htlc_updates.push(HTLCUpdateAwaitingACK::FailHTLC {
htlc_id: htlc_id_arg,
err_packet,
return Ok(None);
}
- log_trace!(logger, "Failing HTLC ID {} back with a update_fail_htlc message in channel {}.", htlc_id_arg, log_bytes!(self.context.channel_id()));
+ log_trace!(logger, "Failing HTLC ID {} back with a update_fail_htlc message in channel {}.", htlc_id_arg, &self.context.channel_id());
{
let htlc = &mut self.context.pending_inbound_htlcs[pending_idx];
htlc.state = InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::FailRelay(err_packet.clone()));
/// Handles a funding_signed message from the remote end.
/// If this call is successful, broadcast the funding transaction (and not before!)
- pub fn funding_signed<SP: Deref, L: Deref>(
+ pub fn funding_signed<L: Deref>(
&mut self, msg: &msgs::FundingSigned, best_block: BestBlock, signer_provider: &SP, logger: &L
- ) -> Result<ChannelMonitor<Signer>, ChannelError>
+ ) -> Result<ChannelMonitor<<SP::Target as SignerProvider>::Signer>, ChannelError>
where
- SP::Target: SignerProvider<Signer = Signer>,
L::Target: Logger
{
if !self.context.is_outbound() {
let counterparty_initial_bitcoin_tx = counterparty_trusted_tx.built_transaction();
log_trace!(logger, "Initial counterparty tx for channel {} is: txid {} tx {}",
- log_bytes!(self.context.channel_id()), counterparty_initial_bitcoin_tx.txid, encode::serialize_hex(&counterparty_initial_bitcoin_tx.transaction));
+ &self.context.channel_id(), counterparty_initial_bitcoin_tx.txid, encode::serialize_hex(&counterparty_initial_bitcoin_tx.transaction));
let holder_signer = self.context.build_holder_transaction_keys(self.context.cur_holder_commitment_transaction_number);
let initial_commitment_tx = self.context.build_commitment_transaction(self.context.cur_holder_commitment_transaction_number, &holder_signer, true, false, logger).tx;
self.context.counterparty_funding_pubkey()
);
- self.context.holder_signer.validate_holder_commitment(&holder_commitment_tx, Vec::new())
+ self.context.holder_signer.as_ref().validate_holder_commitment(&holder_commitment_tx, Vec::new())
.map_err(|_| ChannelError::Close("Failed to validate our commitment".to_owned()))?;
obscure_factor,
holder_commitment_tx, best_block, self.context.counterparty_node_id);
- channel_monitor.provide_latest_counterparty_commitment_tx(counterparty_initial_bitcoin_tx.txid, Vec::new(), self.context.cur_counterparty_commitment_transaction_number, self.context.counterparty_cur_commitment_point.unwrap(), logger);
+ channel_monitor.provide_initial_counterparty_commitment_tx(
+ counterparty_initial_bitcoin_tx.txid, Vec::new(),
+ self.context.cur_counterparty_commitment_transaction_number,
+ self.context.counterparty_cur_commitment_point.unwrap(),
+ counterparty_initial_commitment_tx.feerate_per_kw(),
+ counterparty_initial_commitment_tx.to_broadcaster_value_sat(),
+ counterparty_initial_commitment_tx.to_countersignatory_value_sat(), logger);
assert_eq!(self.context.channel_state & (ChannelState::MonitorUpdateInProgress as u32), 0); // We have no had any monitor(s) yet to fail update!
- self.context.channel_state = ChannelState::FundingSent as u32;
+ if self.context.is_batch_funding() {
+ self.context.channel_state = ChannelState::FundingSent as u32 | ChannelState::WaitingForBatch as u32;
+ } else {
+ self.context.channel_state = ChannelState::FundingSent as u32;
+ }
self.context.cur_holder_commitment_transaction_number -= 1;
self.context.cur_counterparty_commitment_transaction_number -= 1;
- log_info!(logger, "Received funding_signed from peer for channel {}", log_bytes!(self.context.channel_id()));
+ log_info!(logger, "Received funding_signed from peer for channel {}", &self.context.channel_id());
let need_channel_ready = self.check_get_channel_ready(0).is_some();
self.monitor_updating_paused(false, false, need_channel_ready, Vec::new(), Vec::new(), Vec::new());
Ok(channel_monitor)
}
+ /// Updates the state of the channel to indicate that all channels in the batch have received
+ /// funding_signed and persisted their monitors.
+ /// The funding transaction is consequently allowed to be broadcast, and the channel can be
+ /// treated as a non-batch channel going forward.
+ pub fn set_batch_ready(&mut self) {
+ self.context.is_batch_funding = None;
+ self.context.channel_state &= !(ChannelState::WaitingForBatch as u32);
+ }
+
/// Handles a channel_ready message from our peer. If we've already sent our channel_ready
/// and the channel is now usable (and public), this may generate an announcement_signatures to
/// reply with.
let non_shutdown_state = self.context.channel_state & (!MULTI_STATE_FLAGS);
- if non_shutdown_state == ChannelState::FundingSent as u32 {
+ // Our channel_ready shouldn't have been sent if we are waiting for other channels in the
+ // batch, but we can receive channel_ready messages.
+ debug_assert!(
+ non_shutdown_state & ChannelState::OurChannelReady as u32 == 0 ||
+ non_shutdown_state & ChannelState::WaitingForBatch as u32 == 0
+ );
+ if non_shutdown_state & !(ChannelState::WaitingForBatch as u32) == ChannelState::FundingSent as u32 {
self.context.channel_state |= ChannelState::TheirChannelReady as u32;
} else if non_shutdown_state == (ChannelState::FundingSent as u32 | ChannelState::OurChannelReady as u32) {
self.context.channel_state = ChannelState::ChannelReady as u32 | (self.context.channel_state & MULTI_STATE_FLAGS);
self.context.counterparty_prev_commitment_point = self.context.counterparty_cur_commitment_point;
self.context.counterparty_cur_commitment_point = Some(msg.next_per_commitment_point);
- log_info!(logger, "Received channel_ready from peer for channel {}", log_bytes!(self.context.channel_id()));
+ 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))
}
- pub fn update_add_htlc<F, L: Deref>(&mut self, msg: &msgs::UpdateAddHTLC, mut pending_forward_status: PendingHTLCStatus, create_pending_htlc_status: F, logger: &L) -> Result<(), ChannelError>
- where F: for<'a> Fn(&'a Self, PendingHTLCStatus, u16) -> PendingHTLCStatus, L::Target: Logger {
+ pub fn update_add_htlc<F, FE: Deref, L: Deref>(
+ &mut self, msg: &msgs::UpdateAddHTLC, mut pending_forward_status: PendingHTLCStatus,
+ create_pending_htlc_status: F, fee_estimator: &LowerBoundedFeeEstimator<FE>, logger: &L
+ ) -> Result<(), ChannelError>
+ where F: for<'a> Fn(&'a Self, PendingHTLCStatus, u16) -> PendingHTLCStatus,
+ FE::Target: FeeEstimator, L::Target: Logger,
+ {
// We can't accept HTLCs sent after we've sent a shutdown.
let local_sent_shutdown = (self.context.channel_state & (ChannelState::ChannelReady as u32 | ChannelState::LocalShutdownSent as u32)) != (ChannelState::ChannelReady as u32);
if local_sent_shutdown {
}
}
+ let max_dust_htlc_exposure_msat = self.context.get_max_dust_htlc_exposure_msat(fee_estimator);
let (htlc_timeout_dust_limit, htlc_success_dust_limit) = if self.context.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
(0, 0)
} else {
let exposure_dust_limit_timeout_sats = htlc_timeout_dust_limit + self.context.counterparty_dust_limit_satoshis;
if msg.amount_msat / 1000 < exposure_dust_limit_timeout_sats {
let on_counterparty_tx_dust_htlc_exposure_msat = inbound_stats.on_counterparty_tx_dust_exposure_msat + outbound_stats.on_counterparty_tx_dust_exposure_msat + msg.amount_msat;
- if on_counterparty_tx_dust_htlc_exposure_msat > self.context.get_max_dust_htlc_exposure_msat() {
+ if on_counterparty_tx_dust_htlc_exposure_msat > max_dust_htlc_exposure_msat {
log_info!(logger, "Cannot accept value that would put our exposure to dust HTLCs at {} over the limit {} on counterparty commitment tx",
- on_counterparty_tx_dust_htlc_exposure_msat, self.context.get_max_dust_htlc_exposure_msat());
+ on_counterparty_tx_dust_htlc_exposure_msat, max_dust_htlc_exposure_msat);
pending_forward_status = create_pending_htlc_status(self, pending_forward_status, 0x1000|7);
}
}
let exposure_dust_limit_success_sats = htlc_success_dust_limit + self.context.holder_dust_limit_satoshis;
if msg.amount_msat / 1000 < exposure_dust_limit_success_sats {
let on_holder_tx_dust_htlc_exposure_msat = inbound_stats.on_holder_tx_dust_exposure_msat + outbound_stats.on_holder_tx_dust_exposure_msat + msg.amount_msat;
- if on_holder_tx_dust_htlc_exposure_msat > self.context.get_max_dust_htlc_exposure_msat() {
+ if on_holder_tx_dust_htlc_exposure_msat > max_dust_htlc_exposure_msat {
log_info!(logger, "Cannot accept value that would put our exposure to dust HTLCs at {} over the limit {} on holder commitment tx",
- on_holder_tx_dust_htlc_exposure_msat, self.context.get_max_dust_htlc_exposure_msat());
+ on_holder_tx_dust_htlc_exposure_msat, max_dust_htlc_exposure_msat);
pending_forward_status = create_pending_htlc_status(self, pending_forward_status, 0x1000|7);
}
}
if pending_remote_value_msat - msg.amount_msat - self.context.holder_selected_channel_reserve_satoshis * 1000 < 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.", log_bytes!(self.context.channel_id()));
+ log_info!(logger, "Attempting to fail HTLC due to fee spike buffer violation in channel {}. Rebalancing is required.", &self.context.channel_id());
pending_forward_status = create_pending_htlc_status(self, pending_forward_status, 0x1000|7);
}
} else {
log_trace!(logger, "Checking commitment tx signature {} by key {} against tx {} (sighash {}) with redeemscript {} in channel {}",
log_bytes!(msg.signature.serialize_compact()[..]),
log_bytes!(self.context.counterparty_funding_pubkey().serialize()), encode::serialize_hex(&bitcoin_tx.transaction),
- log_bytes!(sighash[..]), encode::serialize_hex(&funding_script), log_bytes!(self.context.channel_id()));
+ log_bytes!(sighash[..]), encode::serialize_hex(&funding_script), &self.context.channel_id());
if let Err(_) = self.context.secp_ctx.verify_ecdsa(&sighash, &msg.signature, &self.context.counterparty_funding_pubkey()) {
return Err(ChannelError::Close("Invalid commitment tx signature from peer".to_owned()));
}
let htlc_sighash = hash_to_message!(&sighash::SighashCache::new(&htlc_tx).segwit_signature_hash(0, &htlc_redeemscript, htlc.amount_msat / 1000, htlc_sighashtype).unwrap()[..]);
log_trace!(logger, "Checking HTLC tx signature {} by key {} against tx {} (sighash {}) with redeemscript {} in channel {}.",
log_bytes!(msg.htlc_signatures[idx].serialize_compact()[..]), log_bytes!(keys.countersignatory_htlc_key.serialize()),
- encode::serialize_hex(&htlc_tx), log_bytes!(htlc_sighash[..]), encode::serialize_hex(&htlc_redeemscript), log_bytes!(self.context.channel_id()));
+ encode::serialize_hex(&htlc_tx), log_bytes!(htlc_sighash[..]), encode::serialize_hex(&htlc_redeemscript), &self.context.channel_id());
if let Err(_) = self.context.secp_ctx.verify_ecdsa(&htlc_sighash, &msg.htlc_signatures[idx], &keys.countersignatory_htlc_key) {
return Err(ChannelError::Close("Invalid HTLC tx signature from peer".to_owned()));
}
self.context.counterparty_funding_pubkey()
);
- self.context.holder_signer.validate_holder_commitment(&holder_commitment_tx, commitment_stats.preimages)
+ self.context.holder_signer.as_ref().validate_holder_commitment(&holder_commitment_tx, commitment_stats.preimages)
.map_err(|_| ChannelError::Close("Failed to validate our commitment".to_owned()))?;
// Update state now that we've passed all the can-fail calls...
} else { None };
if let Some(forward_info) = new_forward {
log_trace!(logger, "Updating HTLC {} to AwaitingRemoteRevokeToAnnounce due to commitment_signed in channel {}.",
- log_bytes!(htlc.payment_hash.0), log_bytes!(self.context.channel_id));
+ &htlc.payment_hash, &self.context.channel_id);
htlc.state = InboundHTLCState::AwaitingRemoteRevokeToAnnounce(forward_info);
need_commitment = true;
}
for htlc in self.context.pending_outbound_htlcs.iter_mut() {
if let &mut OutboundHTLCState::RemoteRemoved(ref mut outcome) = &mut htlc.state {
log_trace!(logger, "Updating HTLC {} to AwaitingRemoteRevokeToRemove due to commitment_signed in channel {}.",
- log_bytes!(htlc.payment_hash.0), log_bytes!(self.context.channel_id));
+ &htlc.payment_hash, &self.context.channel_id);
// Grab the preimage, if it exists, instead of cloning
let mut reason = OutboundHTLCOutcome::Success(None);
mem::swap(outcome, &mut reason);
monitor_update.updates.append(&mut additional_update.updates);
}
log_debug!(logger, "Received valid commitment_signed from peer in channel {}, updated HTLC state but awaiting a monitor update resolution to reply.",
- log_bytes!(self.context.channel_id));
+ &self.context.channel_id);
return Ok(self.push_ret_blockable_mon_update(monitor_update));
}
} else { false };
log_debug!(logger, "Received valid commitment_signed from peer in channel {}, updating HTLC state and responding with{} a revoke_and_ack.",
- log_bytes!(self.context.channel_id()), if need_commitment_signed { " our own commitment_signed and" } else { "" });
+ &self.context.channel_id(), if need_commitment_signed { " our own commitment_signed and" } else { "" });
self.monitor_updating_paused(true, need_commitment_signed, false, Vec::new(), Vec::new(), Vec::new());
return Ok(self.push_ret_blockable_mon_update(monitor_update));
}
/// Public version of the below, checking relevant preconditions first.
/// If we're not in a state where freeing the holding cell makes sense, this is a no-op and
/// returns `(None, Vec::new())`.
- pub fn maybe_free_holding_cell_htlcs<L: Deref>(&mut self, logger: &L) -> (Option<ChannelMonitorUpdate>, Vec<(HTLCSource, PaymentHash)>) where L::Target: Logger {
- if self.context.channel_state >= ChannelState::ChannelReady as u32 &&
+ pub fn maybe_free_holding_cell_htlcs<F: Deref, L: Deref>(
+ &mut self, fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L
+ ) -> (Option<ChannelMonitorUpdate>, Vec<(HTLCSource, PaymentHash)>)
+ where F::Target: FeeEstimator, L::Target: Logger
+ {
+ if self.context.channel_state & !STATE_FLAGS >= ChannelState::ChannelReady as u32 &&
(self.context.channel_state & (ChannelState::AwaitingRemoteRevoke as u32 | ChannelState::PeerDisconnected as u32 | ChannelState::MonitorUpdateInProgress as u32)) == 0 {
- self.free_holding_cell_htlcs(logger)
+ self.free_holding_cell_htlcs(fee_estimator, logger)
} else { (None, Vec::new()) }
}
/// Frees any pending commitment updates in the holding cell, generating the relevant messages
/// for our counterparty.
- fn free_holding_cell_htlcs<L: Deref>(&mut self, logger: &L) -> (Option<ChannelMonitorUpdate>, Vec<(HTLCSource, PaymentHash)>) where L::Target: Logger {
+ fn free_holding_cell_htlcs<F: Deref, L: Deref>(
+ &mut self, fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L
+ ) -> (Option<ChannelMonitorUpdate>, Vec<(HTLCSource, PaymentHash)>)
+ where F::Target: FeeEstimator, L::Target: Logger
+ {
assert_eq!(self.context.channel_state & ChannelState::MonitorUpdateInProgress as u32, 0);
if self.context.holding_cell_htlc_updates.len() != 0 || self.context.holding_cell_update_fee.is_some() {
log_trace!(logger, "Freeing holding cell with {} HTLC updates{} in channel {}", self.context.holding_cell_htlc_updates.len(),
- if self.context.holding_cell_update_fee.is_some() { " and a fee update" } else { "" }, log_bytes!(self.context.channel_id()));
+ if self.context.holding_cell_update_fee.is_some() { " and a fee update" } else { "" }, &self.context.channel_id());
let mut monitor_update = ChannelMonitorUpdate {
update_id: self.context.latest_monitor_update_id + 1, // We don't increment this yet!
let mut htlc_updates = Vec::new();
mem::swap(&mut htlc_updates, &mut self.context.holding_cell_htlc_updates);
- let mut update_add_htlcs = Vec::with_capacity(htlc_updates.len());
- let mut update_fulfill_htlcs = Vec::with_capacity(htlc_updates.len());
- let mut update_fail_htlcs = Vec::with_capacity(htlc_updates.len());
+ let mut update_add_count = 0;
+ let mut update_fulfill_count = 0;
+ let mut update_fail_count = 0;
let mut htlcs_to_fail = Vec::new();
for htlc_update in htlc_updates.drain(..) {
// Note that this *can* fail, though it should be due to rather-rare conditions on
skimmed_fee_msat, ..
} => {
match self.send_htlc(amount_msat, *payment_hash, cltv_expiry, source.clone(),
- onion_routing_packet.clone(), false, skimmed_fee_msat, logger)
+ onion_routing_packet.clone(), false, skimmed_fee_msat, fee_estimator, logger)
{
- Ok(update_add_msg_option) => update_add_htlcs.push(update_add_msg_option.unwrap()),
+ Ok(_) => update_add_count += 1,
Err(e) => {
match e {
ChannelError::Ignore(ref msg) => {
- log_info!(logger, "Failed to send HTLC with payment_hash {} due to {} in channel {}",
- log_bytes!(payment_hash.0), msg, log_bytes!(self.context.channel_id()));
+ log_info!(logger, "Failed to send HTLC with payment_hash {} due to {} in channel {}", &payment_hash, msg, &self.context.channel_id());
// If we fail to send here, then this HTLC should
// be failed backwards. Failing to send here
// indicates that this HTLC may keep being put back
// not fail - any in between attempts to claim the HTLC will have resulted
// in it hitting the holding cell again and we cannot change the state of a
// holding cell HTLC from fulfill to anything else.
- let (update_fulfill_msg_option, mut additional_monitor_update) =
- if let UpdateFulfillFetch::NewClaim { msg, monitor_update, .. } = self.get_update_fulfill_htlc(htlc_id, *payment_preimage, logger) {
- (msg, monitor_update)
- } else { unreachable!() };
- update_fulfill_htlcs.push(update_fulfill_msg_option.unwrap());
+ let mut additional_monitor_update =
+ if let UpdateFulfillFetch::NewClaim { monitor_update, .. } =
+ self.get_update_fulfill_htlc(htlc_id, *payment_preimage, logger)
+ { monitor_update } else { unreachable!() };
+ update_fulfill_count += 1;
monitor_update.updates.append(&mut additional_monitor_update.updates);
},
&HTLCUpdateAwaitingACK::FailHTLC { htlc_id, ref err_packet } => {
// not fail - we should never end up in a state where we double-fail
// an HTLC or fail-then-claim an HTLC as it indicates we didn't wait
// for a full revocation before failing.
- update_fail_htlcs.push(update_fail_msg_option.unwrap())
+ debug_assert!(update_fail_msg_option.is_some());
+ update_fail_count += 1;
},
Err(e) => {
if let ChannelError::Ignore(_) = e {}
},
}
}
- if update_add_htlcs.is_empty() && update_fulfill_htlcs.is_empty() && update_fail_htlcs.is_empty() && self.context.holding_cell_update_fee.is_none() {
+ if update_add_count == 0 && update_fulfill_count == 0 && update_fail_count == 0 && self.context.holding_cell_update_fee.is_none() {
return (None, htlcs_to_fail);
}
let update_fee = if let Some(feerate) = self.context.holding_cell_update_fee.take() {
- self.send_update_fee(feerate, false, logger)
+ self.send_update_fee(feerate, false, fee_estimator, logger)
} else {
None
};
monitor_update.updates.append(&mut additional_update.updates);
log_debug!(logger, "Freeing holding cell in channel {} resulted in {}{} HTLCs added, {} HTLCs fulfilled, and {} HTLCs failed.",
- log_bytes!(self.context.channel_id()), if update_fee.is_some() { "a fee update, " } else { "" },
- update_add_htlcs.len(), update_fulfill_htlcs.len(), update_fail_htlcs.len());
+ &self.context.channel_id(), if update_fee.is_some() { "a fee update, " } else { "" },
+ update_add_count, update_fulfill_count, update_fail_count);
self.monitor_updating_paused(false, true, false, Vec::new(), Vec::new(), Vec::new());
(self.push_ret_blockable_mon_update(monitor_update), htlcs_to_fail)
/// waiting on this revoke_and_ack. The generation of this new commitment_signed may also fail,
/// generating an appropriate error *after* the channel state has been updated based on the
/// revoke_and_ack message.
- pub fn revoke_and_ack<L: Deref>(&mut self, msg: &msgs::RevokeAndACK, logger: &L) -> Result<(Vec<(HTLCSource, PaymentHash)>, Option<ChannelMonitorUpdate>), ChannelError>
- where L::Target: Logger,
+ pub fn revoke_and_ack<F: Deref, L: Deref>(&mut self, msg: &msgs::RevokeAndACK,
+ fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L, hold_mon_update: bool,
+ ) -> Result<(Vec<(HTLCSource, PaymentHash)>, Option<ChannelMonitorUpdate>), ChannelError>
+ where F::Target: FeeEstimator, L::Target: Logger,
{
if (self.context.channel_state & (ChannelState::ChannelReady as u32)) != (ChannelState::ChannelReady as u32) {
return Err(ChannelError::Close("Got revoke/ACK message when channel was not in an operational state".to_owned()));
*self.context.next_remote_commitment_tx_fee_info_cached.lock().unwrap() = None;
}
- self.context.holder_signer.validate_counterparty_revocation(
- self.context.cur_counterparty_commitment_transaction_number + 1,
- &secret
- ).map_err(|_| ChannelError::Close("Failed to validate revocation from peer".to_owned()))?;
+ match &self.context.holder_signer {
+ ChannelSignerType::Ecdsa(ecdsa) => {
+ ecdsa.validate_counterparty_revocation(
+ self.context.cur_counterparty_commitment_transaction_number + 1,
+ &secret
+ ).map_err(|_| ChannelError::Close("Failed to validate revocation from peer".to_owned()))?;
+ }
+ };
self.context.commitment_secrets.provide_secret(self.context.cur_counterparty_commitment_transaction_number + 1, msg.per_commitment_secret)
.map_err(|_| ChannelError::Close("Previous secrets did not match new one".to_owned()))?;
self.context.announcement_sigs_state = AnnouncementSigsState::PeerReceived;
}
- log_trace!(logger, "Updating HTLCs on receipt of RAA in channel {}...", log_bytes!(self.context.channel_id()));
+ log_trace!(logger, "Updating HTLCs on receipt of RAA in channel {}...", &self.context.channel_id());
let mut to_forward_infos = Vec::new();
let mut revoked_htlcs = Vec::new();
let mut finalized_claimed_htlcs = Vec::new();
// We really shouldnt have two passes here, but retain gives a non-mutable ref (Rust bug)
pending_inbound_htlcs.retain(|htlc| {
if let &InboundHTLCState::LocalRemoved(ref reason) = &htlc.state {
- log_trace!(logger, " ...removing inbound LocalRemoved {}", log_bytes!(htlc.payment_hash.0));
+ log_trace!(logger, " ...removing inbound LocalRemoved {}", &htlc.payment_hash);
if let &InboundHTLCRemovalReason::Fulfill(_) = reason {
value_to_self_msat_diff += htlc.amount_msat as i64;
}
});
pending_outbound_htlcs.retain(|htlc| {
if let &OutboundHTLCState::AwaitingRemovedRemoteRevoke(ref outcome) = &htlc.state {
- log_trace!(logger, " ...removing outbound AwaitingRemovedRemoteRevoke {}", log_bytes!(htlc.payment_hash.0));
+ log_trace!(logger, " ...removing outbound AwaitingRemovedRemoteRevoke {}", &htlc.payment_hash);
if let OutboundHTLCOutcome::Failure(reason) = outcome.clone() { // We really want take() here, but, again, non-mut ref :(
revoked_htlcs.push((htlc.source.clone(), htlc.payment_hash, reason));
} else {
mem::swap(&mut state, &mut htlc.state);
if let InboundHTLCState::AwaitingRemoteRevokeToAnnounce(forward_info) = state {
- log_trace!(logger, " ...promoting inbound AwaitingRemoteRevokeToAnnounce {} to AwaitingAnnouncedRemoteRevoke", log_bytes!(htlc.payment_hash.0));
+ log_trace!(logger, " ...promoting inbound AwaitingRemoteRevokeToAnnounce {} to AwaitingAnnouncedRemoteRevoke", &htlc.payment_hash);
htlc.state = InboundHTLCState::AwaitingAnnouncedRemoteRevoke(forward_info);
require_commitment = true;
} else if let InboundHTLCState::AwaitingAnnouncedRemoteRevoke(forward_info) = state {
match forward_info {
PendingHTLCStatus::Fail(fail_msg) => {
- log_trace!(logger, " ...promoting inbound AwaitingAnnouncedRemoteRevoke {} to LocalRemoved due to PendingHTLCStatus indicating failure", log_bytes!(htlc.payment_hash.0));
+ log_trace!(logger, " ...promoting inbound AwaitingAnnouncedRemoteRevoke {} to LocalRemoved due to PendingHTLCStatus indicating failure", &htlc.payment_hash);
require_commitment = true;
match fail_msg {
HTLCFailureMsg::Relay(msg) => {
}
},
PendingHTLCStatus::Forward(forward_info) => {
- log_trace!(logger, " ...promoting inbound AwaitingAnnouncedRemoteRevoke {} to Committed", log_bytes!(htlc.payment_hash.0));
+ log_trace!(logger, " ...promoting inbound AwaitingAnnouncedRemoteRevoke {} to Committed", &htlc.payment_hash);
to_forward_infos.push((forward_info, htlc.htlc_id));
htlc.state = InboundHTLCState::Committed;
}
}
for htlc in pending_outbound_htlcs.iter_mut() {
if let OutboundHTLCState::LocalAnnounced(_) = htlc.state {
- log_trace!(logger, " ...promoting outbound LocalAnnounced {} to Committed", log_bytes!(htlc.payment_hash.0));
+ log_trace!(logger, " ...promoting outbound LocalAnnounced {} to Committed", &htlc.payment_hash);
htlc.state = OutboundHTLCState::Committed;
}
if let &mut OutboundHTLCState::AwaitingRemoteRevokeToRemove(ref mut outcome) = &mut htlc.state {
- log_trace!(logger, " ...promoting outbound AwaitingRemoteRevokeToRemove {} to AwaitingRemovedRemoteRevoke", log_bytes!(htlc.payment_hash.0));
+ log_trace!(logger, " ...promoting outbound AwaitingRemoteRevokeToRemove {} to AwaitingRemovedRemoteRevoke", &htlc.payment_hash);
// Grab the preimage, if it exists, instead of cloning
let mut reason = OutboundHTLCOutcome::Success(None);
mem::swap(outcome, &mut reason);
}
}
+ let release_monitor = self.context.blocked_monitor_updates.is_empty() && !hold_mon_update;
+ let release_state_str =
+ if hold_mon_update { "Holding" } else if release_monitor { "Releasing" } else { "Blocked" };
+ macro_rules! return_with_htlcs_to_fail {
+ ($htlcs_to_fail: expr) => {
+ if !release_monitor {
+ self.context.blocked_monitor_updates.push(PendingChannelMonitorUpdate {
+ update: monitor_update,
+ });
+ return Ok(($htlcs_to_fail, None));
+ } else {
+ return Ok(($htlcs_to_fail, Some(monitor_update)));
+ }
+ }
+ }
+
if (self.context.channel_state & ChannelState::MonitorUpdateInProgress as u32) == ChannelState::MonitorUpdateInProgress as u32 {
// We can't actually generate a new commitment transaction (incl by freeing holding
// cells) while we can't update the monitor, so we just return what we have.
self.context.monitor_pending_forwards.append(&mut to_forward_infos);
self.context.monitor_pending_failures.append(&mut revoked_htlcs);
self.context.monitor_pending_finalized_fulfills.append(&mut finalized_claimed_htlcs);
- log_debug!(logger, "Received a valid revoke_and_ack for channel {} but awaiting a monitor update resolution to reply.", log_bytes!(self.context.channel_id()));
- return Ok((Vec::new(), self.push_ret_blockable_mon_update(monitor_update)));
+ log_debug!(logger, "Received a valid revoke_and_ack for channel {} but awaiting a monitor update resolution to reply.", &self.context.channel_id());
+ return_with_htlcs_to_fail!(Vec::new());
}
- match self.free_holding_cell_htlcs(logger) {
+ match self.free_holding_cell_htlcs(fee_estimator, logger) {
(Some(mut additional_update), htlcs_to_fail) => {
// free_holding_cell_htlcs may bump latest_monitor_id multiple times but we want them to be
// strictly increasing by one, so decrement it here.
self.context.latest_monitor_update_id = monitor_update.update_id;
monitor_update.updates.append(&mut additional_update.updates);
+ log_debug!(logger, "Received a valid revoke_and_ack for channel {} with holding cell HTLCs freed. {} monitor update.",
+ &self.context.channel_id(), release_state_str);
+
self.monitor_updating_paused(false, true, false, to_forward_infos, revoked_htlcs, finalized_claimed_htlcs);
- Ok((htlcs_to_fail, self.push_ret_blockable_mon_update(monitor_update)))
+ return_with_htlcs_to_fail!(htlcs_to_fail);
},
(None, htlcs_to_fail) => {
if require_commitment {
self.context.latest_monitor_update_id = monitor_update.update_id;
monitor_update.updates.append(&mut additional_update.updates);
- log_debug!(logger, "Received a valid revoke_and_ack for channel {}. Responding with a commitment update with {} HTLCs failed.",
- log_bytes!(self.context.channel_id()), update_fail_htlcs.len() + update_fail_malformed_htlcs.len());
+ log_debug!(logger, "Received a valid revoke_and_ack for channel {}. Responding with a commitment update with {} HTLCs failed. {} monitor update.",
+ &self.context.channel_id(),
+ update_fail_htlcs.len() + update_fail_malformed_htlcs.len(),
+ release_state_str);
+
self.monitor_updating_paused(false, true, false, to_forward_infos, revoked_htlcs, finalized_claimed_htlcs);
- Ok((htlcs_to_fail, self.push_ret_blockable_mon_update(monitor_update)))
+ return_with_htlcs_to_fail!(htlcs_to_fail);
} else {
- log_debug!(logger, "Received a valid revoke_and_ack for channel {} with no reply necessary.", log_bytes!(self.context.channel_id()));
+ log_debug!(logger, "Received a valid revoke_and_ack for channel {} with no reply necessary. {} monitor update.",
+ &self.context.channel_id(), release_state_str);
+
self.monitor_updating_paused(false, false, false, to_forward_infos, revoked_htlcs, finalized_claimed_htlcs);
- Ok((htlcs_to_fail, self.push_ret_blockable_mon_update(monitor_update)))
+ return_with_htlcs_to_fail!(htlcs_to_fail);
}
}
}
/// Queues up an outbound update fee by placing it in the holding cell. You should call
/// [`Self::maybe_free_holding_cell_htlcs`] in order to actually generate and send the
/// commitment update.
- pub fn queue_update_fee<L: Deref>(&mut self, feerate_per_kw: u32, logger: &L) where L::Target: Logger {
- let msg_opt = self.send_update_fee(feerate_per_kw, true, logger);
+ pub fn queue_update_fee<F: Deref, L: Deref>(&mut self, feerate_per_kw: u32,
+ fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L)
+ where F::Target: FeeEstimator, L::Target: Logger
+ {
+ let msg_opt = self.send_update_fee(feerate_per_kw, true, fee_estimator, logger);
assert!(msg_opt.is_none(), "We forced holding cell?");
}
///
/// You MUST call [`Self::send_commitment_no_state_update`] prior to any other calls on this
/// [`Channel`] if `force_holding_cell` is false.
- fn send_update_fee<L: Deref>(&mut self, feerate_per_kw: u32, mut force_holding_cell: bool, logger: &L) -> Option<msgs::UpdateFee> where L::Target: Logger {
+ fn send_update_fee<F: Deref, L: Deref>(
+ &mut self, feerate_per_kw: u32, mut force_holding_cell: bool,
+ fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L
+ ) -> Option<msgs::UpdateFee>
+ where F::Target: FeeEstimator, L::Target: Logger
+ {
if !self.context.is_outbound() {
panic!("Cannot send fee from inbound channel");
}
// Note, we evaluate pending htlc "preemptive" trimmed-to-dust threshold at the proposed `feerate_per_kw`.
let holder_tx_dust_exposure = inbound_stats.on_holder_tx_dust_exposure_msat + outbound_stats.on_holder_tx_dust_exposure_msat;
let counterparty_tx_dust_exposure = inbound_stats.on_counterparty_tx_dust_exposure_msat + outbound_stats.on_counterparty_tx_dust_exposure_msat;
- if holder_tx_dust_exposure > self.context.get_max_dust_htlc_exposure_msat() {
+ let max_dust_htlc_exposure_msat = self.context.get_max_dust_htlc_exposure_msat(fee_estimator);
+ if holder_tx_dust_exposure > max_dust_htlc_exposure_msat {
log_debug!(logger, "Cannot afford to send new feerate at {} without infringing max dust htlc exposure", feerate_per_kw);
return None;
}
- if counterparty_tx_dust_exposure > self.context.get_max_dust_htlc_exposure_msat() {
+ if counterparty_tx_dust_exposure > max_dust_htlc_exposure_msat {
log_debug!(logger, "Cannot afford to send new feerate at {} without infringing max dust htlc exposure", feerate_per_kw);
return None;
}
/// resent.
/// No further message handling calls may be made until a channel_reestablish dance has
/// completed.
- pub fn remove_uncommitted_htlcs_and_mark_paused<L: Deref>(&mut self, logger: &L) where L::Target: Logger {
+ /// May return `Err(())`, which implies [`ChannelContext::force_shutdown`] should be called immediately.
+ pub fn remove_uncommitted_htlcs_and_mark_paused<L: Deref>(&mut self, logger: &L) -> Result<(), ()> where L::Target: Logger {
assert_eq!(self.context.channel_state & ChannelState::ShutdownComplete as u32, 0);
- if self.context.channel_state < ChannelState::FundingSent as u32 {
- self.context.channel_state = ChannelState::ShutdownComplete as u32;
- return;
+ if self.context.channel_state & !STATE_FLAGS < ChannelState::FundingSent as u32 {
+ return Err(());
}
if self.context.channel_state & (ChannelState::PeerDisconnected as u32) == (ChannelState::PeerDisconnected as u32) {
// While the below code should be idempotent, it's simpler to just return early, as
// redundant disconnect events can fire, though they should be rare.
- return;
+ return Ok(());
}
if self.context.announcement_sigs_state == AnnouncementSigsState::MessageSent || self.context.announcement_sigs_state == AnnouncementSigsState::Committed {
self.context.sent_message_awaiting_response = None;
self.context.channel_state |= ChannelState::PeerDisconnected as u32;
- log_trace!(logger, "Peer disconnection resulted in {} remote-announced HTLC drops on channel {}", inbound_drop_count, log_bytes!(self.context.channel_id()));
+ log_trace!(logger, "Peer disconnection resulted in {} remote-announced HTLC drops on channel {}", inbound_drop_count, &self.context.channel_id());
+ Ok(())
}
/// Indicates that a ChannelMonitor update is in progress and has not yet been fully persisted.
// (re-)broadcast the funding transaction as we may have declined to broadcast it when we
// first received the funding_signed.
let mut funding_broadcastable =
- if self.context.is_outbound() && self.context.channel_state & !MULTI_STATE_FLAGS >= ChannelState::FundingSent as u32 {
+ if self.context.is_outbound() && self.context.channel_state & !STATE_FLAGS >= ChannelState::FundingSent as u32 && self.context.channel_state & ChannelState::WaitingForBatch as u32 == 0 {
self.context.funding_transaction.take()
} else { None };
// That said, if the funding transaction is already confirmed (ie we're active with a
// minimum_depth over 0) don't bother re-broadcasting the confirmed funding tx.
- if self.context.channel_state & !MULTI_STATE_FLAGS >= ChannelState::ChannelReady as u32 && self.context.minimum_depth != Some(0) {
+ if self.context.channel_state & !STATE_FLAGS >= ChannelState::ChannelReady as u32 && self.context.minimum_depth != Some(0) {
funding_broadcastable = None;
}
assert!(!self.context.is_outbound() || self.context.minimum_depth == Some(0),
"Funding transaction broadcast by the local client before it should have - LDK didn't do it!");
self.context.monitor_pending_channel_ready = false;
- let next_per_commitment_point = self.context.holder_signer.get_per_commitment_point(self.context.cur_holder_commitment_transaction_number, &self.context.secp_ctx);
+ let next_per_commitment_point = self.context.holder_signer.as_ref().get_per_commitment_point(self.context.cur_holder_commitment_transaction_number, &self.context.secp_ctx);
Some(msgs::ChannelReady {
channel_id: self.context.channel_id(),
next_per_commitment_point,
self.context.monitor_pending_commitment_signed = false;
let order = self.context.resend_order.clone();
log_debug!(logger, "Restored monitor updating in channel {} resulting in {}{} commitment update and {} RAA, with {} first",
- log_bytes!(self.context.channel_id()), if funding_broadcastable.is_some() { "a funding broadcastable, " } else { "" },
+ &self.context.channel_id(), if funding_broadcastable.is_some() { "a funding broadcastable, " } else { "" },
if commitment_update.is_some() { "a" } else { "no" }, if raa.is_some() { "an" } else { "no" },
match order { RAACommitmentOrder::CommitmentFirst => "commitment", RAACommitmentOrder::RevokeAndACKFirst => "RAA"});
MonitorRestoreUpdates {
if self.context.channel_state & (ChannelState::PeerDisconnected as u32) == ChannelState::PeerDisconnected as u32 {
return Err(ChannelError::Close("Peer sent update_fee when we needed a channel_reestablish".to_owned()));
}
- Channel::<Signer>::check_remote_fee(fee_estimator, msg.feerate_per_kw, Some(self.context.feerate_per_kw), logger)?;
+ Channel::<SP>::check_remote_fee(&self.context.channel_type, fee_estimator, msg.feerate_per_kw, Some(self.context.feerate_per_kw), logger)?;
let feerate_over_dust_buffer = msg.feerate_per_kw > self.context.get_dust_buffer_feerate(None);
self.context.pending_update_fee = Some((msg.feerate_per_kw, FeeUpdateState::RemoteAnnounced));
let outbound_stats = self.context.get_outbound_pending_htlc_stats(None);
let holder_tx_dust_exposure = inbound_stats.on_holder_tx_dust_exposure_msat + outbound_stats.on_holder_tx_dust_exposure_msat;
let counterparty_tx_dust_exposure = inbound_stats.on_counterparty_tx_dust_exposure_msat + outbound_stats.on_counterparty_tx_dust_exposure_msat;
- if holder_tx_dust_exposure > self.context.get_max_dust_htlc_exposure_msat() {
+ let max_dust_htlc_exposure_msat = self.context.get_max_dust_htlc_exposure_msat(fee_estimator);
+ if holder_tx_dust_exposure > max_dust_htlc_exposure_msat {
return Err(ChannelError::Close(format!("Peer sent update_fee with a feerate ({}) which may over-expose us to dust-in-flight on our own transactions (totaling {} msat)",
msg.feerate_per_kw, holder_tx_dust_exposure)));
}
- if counterparty_tx_dust_exposure > self.context.get_max_dust_htlc_exposure_msat() {
+ if counterparty_tx_dust_exposure > max_dust_htlc_exposure_msat {
return Err(ChannelError::Close(format!("Peer sent update_fee with a feerate ({}) which may over-expose us to dust-in-flight on our counterparty's transactions (totaling {} msat)",
msg.feerate_per_kw, counterparty_tx_dust_exposure)));
}
}
fn get_last_revoke_and_ack(&self) -> msgs::RevokeAndACK {
- let next_per_commitment_point = self.context.holder_signer.get_per_commitment_point(self.context.cur_holder_commitment_transaction_number, &self.context.secp_ctx);
- let per_commitment_secret = self.context.holder_signer.release_commitment_secret(self.context.cur_holder_commitment_transaction_number + 2);
+ let next_per_commitment_point = self.context.holder_signer.as_ref().get_per_commitment_point(self.context.cur_holder_commitment_transaction_number, &self.context.secp_ctx);
+ let per_commitment_secret = self.context.holder_signer.as_ref().release_commitment_secret(self.context.cur_holder_commitment_transaction_number + 2);
msgs::RevokeAndACK {
channel_id: self.context.channel_id,
per_commitment_secret,
} else { None };
log_trace!(logger, "Regenerated latest commitment update in channel {} with{} {} update_adds, {} update_fulfills, {} update_fails, and {} update_fail_malformeds",
- log_bytes!(self.context.channel_id()), if update_fee.is_some() { " update_fee," } else { "" },
+ &self.context.channel_id(), if update_fee.is_some() { " update_fee," } else { "" },
update_add_htlcs.len(), update_fulfill_htlcs.len(), update_fail_htlcs.len(), update_fail_malformed_htlcs.len());
msgs::CommitmentUpdate {
update_add_htlcs, update_fulfill_htlcs, update_fail_htlcs, update_fail_malformed_htlcs, update_fee,
}
}
+ /// Gets the `Shutdown` message we should send our peer on reconnect, if any.
+ pub fn get_outbound_shutdown(&self) -> Option<msgs::Shutdown> {
+ if self.context.channel_state & (ChannelState::LocalShutdownSent as u32) != 0 {
+ assert!(self.context.shutdown_scriptpubkey.is_some());
+ Some(msgs::Shutdown {
+ channel_id: self.context.channel_id,
+ scriptpubkey: self.get_closing_scriptpubkey(),
+ })
+ } else { None }
+ }
+
/// May panic if some calls other than message-handling calls (which will all Err immediately)
/// have been called between remove_uncommitted_htlcs_and_mark_paused and this call.
///
}
if msg.next_remote_commitment_number > 0 {
- let expected_point = self.context.holder_signer.get_per_commitment_point(INITIAL_COMMITMENT_NUMBER - msg.next_remote_commitment_number + 1, &self.context.secp_ctx);
+ let expected_point = self.context.holder_signer.as_ref().get_per_commitment_point(INITIAL_COMMITMENT_NUMBER - msg.next_remote_commitment_number + 1, &self.context.secp_ctx);
let given_secret = SecretKey::from_slice(&msg.your_last_per_commitment_secret)
.map_err(|_| ChannelError::Close("Peer sent a garbage channel_reestablish with unparseable secret key".to_owned()))?;
if expected_point != PublicKey::from_secret_key(&self.context.secp_ctx, &given_secret) {
if msg.next_remote_commitment_number > INITIAL_COMMITMENT_NUMBER - self.context.cur_holder_commitment_transaction_number {
macro_rules! log_and_panic {
($err_msg: expr) => {
- log_error!(logger, $err_msg, log_bytes!(self.context.channel_id), log_pubkey!(self.context.counterparty_node_id));
- panic!($err_msg, log_bytes!(self.context.channel_id), log_pubkey!(self.context.counterparty_node_id));
+ log_error!(logger, $err_msg, &self.context.channel_id, log_pubkey!(self.context.counterparty_node_id));
+ panic!($err_msg, &self.context.channel_id, log_pubkey!(self.context.counterparty_node_id));
}
}
log_and_panic!("We have fallen behind - we have received proof that if we broadcast our counterparty is going to claim all our funds.\n\
self.context.channel_state &= !(ChannelState::PeerDisconnected as u32);
self.context.sent_message_awaiting_response = None;
- let shutdown_msg = if self.context.channel_state & (ChannelState::LocalShutdownSent as u32) != 0 {
- assert!(self.context.shutdown_scriptpubkey.is_some());
- Some(msgs::Shutdown {
- channel_id: self.context.channel_id,
- scriptpubkey: self.get_closing_scriptpubkey(),
- })
- } else { None };
+ 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);
}
// We have OurChannelReady set!
- let next_per_commitment_point = self.context.holder_signer.get_per_commitment_point(self.context.cur_holder_commitment_transaction_number, &self.context.secp_ctx);
+ let next_per_commitment_point = self.context.holder_signer.as_ref().get_per_commitment_point(self.context.cur_holder_commitment_transaction_number, &self.context.secp_ctx);
return Ok(ReestablishResponses {
channel_ready: Some(msgs::ChannelReady {
channel_id: self.context.channel_id(),
let channel_ready = if msg.next_local_commitment_number == 1 && INITIAL_COMMITMENT_NUMBER - self.context.cur_holder_commitment_transaction_number == 1 {
// We should never have to worry about MonitorUpdateInProgress resending ChannelReady
- let next_per_commitment_point = self.context.holder_signer.get_per_commitment_point(self.context.cur_holder_commitment_transaction_number, &self.context.secp_ctx);
+ let next_per_commitment_point = self.context.holder_signer.as_ref().get_per_commitment_point(self.context.cur_holder_commitment_transaction_number, &self.context.secp_ctx);
Some(msgs::ChannelReady {
channel_id: self.context.channel_id(),
next_per_commitment_point,
if msg.next_local_commitment_number == next_counterparty_commitment_number {
if required_revoke.is_some() {
- log_debug!(logger, "Reconnected channel {} with only lost outbound RAA", log_bytes!(self.context.channel_id()));
+ log_debug!(logger, "Reconnected channel {} with only lost outbound RAA", &self.context.channel_id());
} else {
- log_debug!(logger, "Reconnected channel {} with no loss", log_bytes!(self.context.channel_id()));
+ log_debug!(logger, "Reconnected channel {} with no loss", &self.context.channel_id());
}
Ok(ReestablishResponses {
})
} else if msg.next_local_commitment_number == next_counterparty_commitment_number - 1 {
if required_revoke.is_some() {
- log_debug!(logger, "Reconnected channel {} with lost outbound RAA and lost remote commitment tx", log_bytes!(self.context.channel_id()));
+ log_debug!(logger, "Reconnected channel {} with lost outbound RAA and lost remote commitment tx", &self.context.channel_id());
} else {
- log_debug!(logger, "Reconnected channel {} with only lost remote commitment tx", log_bytes!(self.context.channel_id()));
+ log_debug!(logger, "Reconnected channel {} with only lost remote commitment tx", &self.context.channel_id());
}
if self.context.channel_state & (ChannelState::MonitorUpdateInProgress as u32) != 0 {
log_trace!(logger, "Proposing initial closing_signed for our counterparty with a fee range of {}-{} sat (with initial proposal {} sats)",
our_min_fee, our_max_fee, total_fee_satoshis);
- let sig = self.context.holder_signer
- .sign_closing_transaction(&closing_tx, &self.context.secp_ctx)
- .map_err(|()| ChannelError::Close("Failed to get signature for closing transaction.".to_owned()))?;
+ match &self.context.holder_signer {
+ ChannelSignerType::Ecdsa(ecdsa) => {
+ let sig = ecdsa
+ .sign_closing_transaction(&closing_tx, &self.context.secp_ctx)
+ .map_err(|()| ChannelError::Close("Failed to get signature for closing transaction.".to_owned()))?;
- self.context.last_sent_closing_fee = Some((total_fee_satoshis, sig.clone()));
- Ok((Some(msgs::ClosingSigned {
- channel_id: self.context.channel_id,
- fee_satoshis: total_fee_satoshis,
- signature: sig,
- fee_range: Some(msgs::ClosingSignedFeeRange {
- min_fee_satoshis: our_min_fee,
- max_fee_satoshis: our_max_fee,
- }),
- }), None))
+ self.context.last_sent_closing_fee = Some((total_fee_satoshis, sig.clone()));
+ Ok((Some(msgs::ClosingSigned {
+ channel_id: self.context.channel_id,
+ fee_satoshis: total_fee_satoshis,
+ signature: sig,
+ fee_range: Some(msgs::ClosingSignedFeeRange {
+ min_fee_satoshis: our_min_fee,
+ max_fee_satoshis: our_max_fee,
+ }),
+ }), None))
+ }
+ }
}
// Marks a channel as waiting for a response from the counterparty. If it's not received
*ticks_elapsed >= DISCONNECT_PEER_AWAITING_RESPONSE_TICKS
}
- pub fn shutdown<SP: Deref>(
+ pub fn shutdown(
&mut self, signer_provider: &SP, their_features: &InitFeatures, msg: &msgs::Shutdown
) -> Result<(Option<msgs::Shutdown>, Option<ChannelMonitorUpdate>, Vec<(HTLCSource, PaymentHash)>), ChannelError>
- where SP::Target: SignerProvider
{
if self.context.channel_state & (ChannelState::PeerDisconnected as u32) == ChannelState::PeerDisconnected as u32 {
return Err(ChannelError::Close("Peer sent shutdown when we needed a channel_reestablish".to_owned()));
}
- if self.context.channel_state < ChannelState::FundingSent as u32 {
+ if self.context.channel_state & !STATE_FLAGS < ChannelState::FundingSent as u32 {
// Spec says we should fail the connection, not the channel, but that's nonsense, there
// are plenty of reasons you may want to fail a channel pre-funding, and spec says you
// can do that via error message without getting a connection fail anyway...
self.build_closing_transaction($new_fee, false)
};
- let sig = self.context.holder_signer
- .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 {
- 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 };
+ return match &self.context.holder_signer {
+ ChannelSignerType::Ecdsa(ecdsa) => {
+ 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()))?;
- self.context.last_sent_closing_fee = Some((used_fee, sig.clone()));
- return Ok((Some(msgs::ClosingSigned {
- channel_id: self.context.channel_id,
- fee_satoshis: used_fee,
- signature: sig,
- fee_range: Some(msgs::ClosingSignedFeeRange {
- min_fee_satoshis: our_min_fee,
- max_fee_satoshis: our_max_fee,
- }),
- }), signed_tx))
+ let signed_tx = if $new_fee == msg.fee_satoshis {
+ 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 };
+
+ self.context.last_sent_closing_fee = Some((used_fee, sig.clone()));
+ Ok((Some(msgs::ClosingSigned {
+ channel_id: self.context.channel_id,
+ fee_satoshis: used_fee,
+ signature: sig,
+ fee_range: Some(msgs::ClosingSignedFeeRange {
+ min_fee_satoshis: our_min_fee,
+ max_fee_satoshis: our_max_fee,
+ }),
+ }), signed_tx))
+ }
+ }
}
}
}
#[cfg(test)]
- pub fn get_signer(&self) -> &Signer {
+ pub fn get_signer(&self) -> &ChannelSignerType<<SP::Target as SignerProvider>::Signer> {
&self.context.holder_signer
}
pub fn is_awaiting_initial_mon_persist(&self) -> bool {
if !self.is_awaiting_monitor_update() { return false; }
if self.context.channel_state &
- !(ChannelState::TheirChannelReady as u32 | ChannelState::PeerDisconnected as u32 | ChannelState::MonitorUpdateInProgress as u32)
+ !(ChannelState::TheirChannelReady as u32 | ChannelState::PeerDisconnected as u32 | ChannelState::MonitorUpdateInProgress as u32 | ChannelState::WaitingForBatch as u32)
== ChannelState::FundingSent as u32 {
// If we're not a 0conf channel, we'll be waiting on a monitor update with only
// FundingSent set, though our peer could have sent their channel_ready.
/// Returns true if our channel_ready has been sent
pub fn is_our_channel_ready(&self) -> bool {
- (self.context.channel_state & ChannelState::OurChannelReady as u32) != 0 || self.context.channel_state >= ChannelState::ChannelReady as u32
+ (self.context.channel_state & ChannelState::OurChannelReady as u32) != 0 || self.context.channel_state & !STATE_FLAGS >= ChannelState::ChannelReady as u32
}
/// Returns true if our peer has either initiated or agreed to shut down the channel.
return None;
}
+ // Note that we don't include ChannelState::WaitingForBatch as we don't want to send
+ // channel_ready until the entire batch is ready.
let non_shutdown_state = self.context.channel_state & (!MULTI_STATE_FLAGS);
let need_commitment_update = if non_shutdown_state == ChannelState::FundingSent as u32 {
self.context.channel_state |= ChannelState::OurChannelReady as u32;
// We got a reorg but not enough to trigger a force close, just ignore.
false
} else {
- if self.context.funding_tx_confirmation_height != 0 && self.context.channel_state < ChannelState::ChannelReady as u32 {
+ if self.context.funding_tx_confirmation_height != 0 && self.context.channel_state & !STATE_FLAGS < ChannelState::ChannelReady as u32 {
// We should never see a funding transaction on-chain until we've received
// funding_signed (if we're an outbound channel), or seen funding_generated (if we're
// an inbound channel - before that we have no known funding TXID). The fuzzer,
if self.context.channel_state & (ChannelState::MonitorUpdateInProgress as u32) == 0 {
if self.context.channel_state & (ChannelState::PeerDisconnected as u32) == 0 {
let next_per_commitment_point =
- self.context.holder_signer.get_per_commitment_point(INITIAL_COMMITMENT_NUMBER - 1, &self.context.secp_ctx);
+ self.context.holder_signer.as_ref().get_per_commitment_point(INITIAL_COMMITMENT_NUMBER - 1, &self.context.secp_ctx);
return Some(msgs::ChannelReady {
channel_id: self.context.channel_id,
next_per_commitment_point,
return Err(ClosureReason::ProcessingError { err: err_reason.to_owned() });
} else {
if self.context.is_outbound() {
- for input in tx.input.iter() {
- if input.witness.is_empty() {
- // We generated a malleable funding transaction, implying we've
- // just exposed ourselves to funds loss to our counterparty.
- #[cfg(not(fuzzing))]
- panic!("Client called ChannelManager::funding_transaction_generated with bogus transaction!");
+ if !tx.is_coin_base() {
+ for input in tx.input.iter() {
+ if input.witness.is_empty() {
+ // We generated a malleable funding transaction, implying we've
+ // just exposed ourselves to funds loss to our counterparty.
+ #[cfg(not(fuzzing))]
+ panic!("Client called ChannelManager::funding_transaction_generated with bogus transaction!");
+ }
}
}
}
Err(_) => panic!("Block was bogus - either height was > 16 million, had > 16 million transactions, or had > 65k outputs"),
}
}
+ // If this is a coinbase transaction and not a 0-conf channel
+ // we should update our min_depth to 100 to handle coinbase maturity
+ if tx.is_coin_base() &&
+ self.context.minimum_depth.unwrap_or(0) > 0 &&
+ self.context.minimum_depth.unwrap_or(0) < COINBASE_MATURITY {
+ self.context.minimum_depth = Some(COINBASE_MATURITY);
+ }
}
// If we allow 1-conf funding, we may need to check for channel_ready here and
// send it immediately instead of waiting for a best_block_updated call (which
// 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 {}", log_bytes!(self.context.channel_id));
+ 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);
return Ok((Some(channel_ready), announcement_sigs));
}
}
for inp in tx.input.iter() {
if inp.previous_output == funding_txo.into_bitcoin_outpoint() {
- log_info!(logger, "Detected channel-closing tx {} spending {}:{}, closing channel {}", tx.txid(), inp.previous_output.txid, inp.previous_output.vout, log_bytes!(self.context.channel_id()));
+ log_info!(logger, "Detected channel-closing tx {} spending {}:{}, closing channel {}", tx.txid(), inp.previous_output.txid, inp.previous_output.vout, &self.context.channel_id());
return Err(ClosureReason::CommitmentTxConfirmed);
}
}
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)
} else { None };
- log_info!(logger, "Sending a channel_ready to our peer for channel {}", log_bytes!(self.context.channel_id));
+ 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));
}
let non_shutdown_state = self.context.channel_state & (!MULTI_STATE_FLAGS);
- if non_shutdown_state >= ChannelState::ChannelReady as u32 ||
+ if non_shutdown_state & !STATE_FLAGS >= ChannelState::ChannelReady as u32 ||
(non_shutdown_state & ChannelState::OurChannelReady as u32) == ChannelState::OurChannelReady as u32 {
let mut funding_tx_confirmations = height as i64 - self.context.funding_tx_confirmation_height as i64 + 1;
if self.context.funding_tx_confirmation_height == 0 {
}
} else if !self.context.is_outbound() && self.context.funding_tx_confirmed_in.is_none() &&
height >= self.context.channel_creation_height + FUNDING_CONF_DEADLINE_BLOCKS {
- log_info!(logger, "Closing channel {} due to funding timeout", log_bytes!(self.context.channel_id));
+ log_info!(logger, "Closing channel {} due to funding timeout", &self.context.channel_id);
// If funding_tx_confirmed_in is unset, the channel must not be active
- assert!(non_shutdown_state <= ChannelState::ChannelReady as u32);
+ assert!(non_shutdown_state & !STATE_FLAGS <= ChannelState::ChannelReady as u32);
assert_eq!(non_shutdown_state & ChannelState::OurChannelReady as u32, 0);
return Err(ClosureReason::FundingTimedOut);
}
// something in the handler for the message that prompted this message):
/// Gets an UnsignedChannelAnnouncement for this channel. The channel must be publicly
- /// announceable and available for use (have exchanged ChannelReady messages in both
+ /// announceable and available for use (have exchanged [`ChannelReady`] messages in both
/// directions). Should be used for both broadcasted announcements and in response to an
/// AnnouncementSignatures message from the remote peer.
///
/// closing).
///
/// This will only return ChannelError::Ignore upon failure.
+ ///
+ /// [`ChannelReady`]: crate::ln::msgs::ChannelReady
fn get_channel_announcement<NS: Deref>(
&self, node_signer: &NS, chain_hash: BlockHash, user_config: &UserConfig,
) -> Result<msgs::UnsignedChannelAnnouncement, ChannelError> where NS::Target: NodeSigner {
return Err(ChannelError::Ignore("Cannot get a ChannelAnnouncement if the channel is not currently usable".to_owned()));
}
+ let short_channel_id = self.context.get_short_channel_id()
+ .ok_or(ChannelError::Ignore("Cannot get a ChannelAnnouncement if the channel has not been confirmed yet".to_owned()))?;
let node_id = NodeId::from_pubkey(&node_signer.get_node_id(Recipient::Node)
.map_err(|_| ChannelError::Ignore("Failed to retrieve own public key".to_owned()))?);
let counterparty_node_id = NodeId::from_pubkey(&self.context.get_counterparty_node_id());
let msg = msgs::UnsignedChannelAnnouncement {
features: channelmanager::provided_channel_features(&user_config),
chain_hash,
- short_channel_id: self.context.get_short_channel_id().unwrap(),
+ short_channel_id,
node_id_1: if were_node_one { node_id } else { counterparty_node_id },
node_id_2: if were_node_one { counterparty_node_id } else { node_id },
bitcoin_key_1: NodeId::from_pubkey(if were_node_one { &self.context.get_holder_pubkeys().funding_pubkey } else { self.context.counterparty_funding_pubkey() }),
return None;
}
- log_trace!(logger, "Creating an announcement_signatures message for channel {}", log_bytes!(self.context.channel_id()));
+ 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) {
Ok(a) => a,
Err(e) => {
},
Ok(v) => v
};
- let our_bitcoin_sig = match self.context.holder_signer.sign_channel_announcement_with_funding_key(&announcement, &self.context.secp_ctx) {
- Err(_) => {
- log_error!(logger, "Signer rejected channel_announcement signing. Channel will not be announced!");
- return None;
- },
- Ok(v) => v
- };
- self.context.announcement_sigs_state = AnnouncementSigsState::MessageSent;
+ match &self.context.holder_signer {
+ ChannelSignerType::Ecdsa(ecdsa) => {
+ let our_bitcoin_sig = match ecdsa.sign_channel_announcement_with_funding_key(&announcement, &self.context.secp_ctx) {
+ Err(_) => {
+ log_error!(logger, "Signer rejected channel_announcement signing. Channel will not be announced!");
+ return None;
+ },
+ Ok(v) => v
+ };
+ let short_channel_id = match self.context.get_short_channel_id() {
+ Some(scid) => scid,
+ None => return None,
+ };
- Some(msgs::AnnouncementSignatures {
- channel_id: self.context.channel_id(),
- short_channel_id: self.context.get_short_channel_id().unwrap(),
- node_signature: our_node_sig,
- bitcoin_signature: our_bitcoin_sig,
- })
+ self.context.announcement_sigs_state = AnnouncementSigsState::MessageSent;
+
+ Some(msgs::AnnouncementSignatures {
+ channel_id: self.context.channel_id(),
+ short_channel_id,
+ node_signature: our_node_sig,
+ bitcoin_signature: our_bitcoin_sig,
+ })
+ }
+ }
}
/// Signs the given channel announcement, returning a ChannelError::Ignore if no keys are
let our_node_sig = node_signer.sign_gossip_message(msgs::UnsignedGossipMessage::ChannelAnnouncement(&announcement))
.map_err(|_| ChannelError::Ignore("Failed to generate node signature for channel_announcement".to_owned()))?;
- let our_bitcoin_sig = self.context.holder_signer.sign_channel_announcement_with_funding_key(&announcement, &self.context.secp_ctx)
- .map_err(|_| ChannelError::Ignore("Signer rejected channel_announcement".to_owned()))?;
- Ok(msgs::ChannelAnnouncement {
- node_signature_1: if were_node_one { our_node_sig } else { their_node_sig },
- node_signature_2: if were_node_one { their_node_sig } else { our_node_sig },
- bitcoin_signature_1: if were_node_one { our_bitcoin_sig } else { their_bitcoin_sig },
- bitcoin_signature_2: if were_node_one { their_bitcoin_sig } else { our_bitcoin_sig },
- contents: announcement,
- })
+ match &self.context.holder_signer {
+ ChannelSignerType::Ecdsa(ecdsa) => {
+ let our_bitcoin_sig = ecdsa.sign_channel_announcement_with_funding_key(&announcement, &self.context.secp_ctx)
+ .map_err(|_| ChannelError::Ignore("Signer rejected channel_announcement".to_owned()))?;
+ Ok(msgs::ChannelAnnouncement {
+ node_signature_1: if were_node_one { our_node_sig } else { their_node_sig },
+ node_signature_2: if were_node_one { their_node_sig } else { our_node_sig },
+ bitcoin_signature_1: if were_node_one { our_bitcoin_sig } else { their_bitcoin_sig },
+ bitcoin_signature_2: if were_node_one { their_bitcoin_sig } else { our_bitcoin_sig },
+ contents: announcement,
+ })
+ }
+ }
} else {
Err(ChannelError::Ignore("Attempted to sign channel announcement before we'd received announcement_signatures".to_string()))
}
let dummy_pubkey = PublicKey::from_slice(&pk).unwrap();
let remote_last_secret = if self.context.cur_counterparty_commitment_transaction_number + 1 < INITIAL_COMMITMENT_NUMBER {
let remote_last_secret = self.context.commitment_secrets.get_secret(self.context.cur_counterparty_commitment_transaction_number + 2).unwrap();
- log_trace!(logger, "Enough info to generate a Data Loss Protect with per_commitment_secret {} for channel {}", log_bytes!(remote_last_secret), log_bytes!(self.context.channel_id()));
+ log_trace!(logger, "Enough info to generate a Data Loss Protect with per_commitment_secret {} for channel {}", log_bytes!(remote_last_secret), &self.context.channel_id());
remote_last_secret
} else {
- log_info!(logger, "Sending a data_loss_protect with no previous remote per_commitment_secret for channel {}", log_bytes!(self.context.channel_id()));
+ log_info!(logger, "Sending a data_loss_protect with no previous remote per_commitment_secret for channel {}", &self.context.channel_id());
[0;32]
};
self.mark_awaiting_response();
/// commitment update.
///
/// `Err`s will only be [`ChannelError::Ignore`].
- pub fn queue_add_htlc<L: Deref>(
+ pub fn queue_add_htlc<F: Deref, L: Deref>(
&mut self, amount_msat: u64, payment_hash: PaymentHash, cltv_expiry: u32, source: HTLCSource,
- onion_routing_packet: msgs::OnionPacket, skimmed_fee_msat: Option<u64>, logger: &L
- ) -> Result<(), ChannelError> where L::Target: Logger {
+ onion_routing_packet: msgs::OnionPacket, skimmed_fee_msat: Option<u64>,
+ fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L
+ ) -> Result<(), ChannelError>
+ where F::Target: FeeEstimator, L::Target: Logger
+ {
self
.send_htlc(amount_msat, payment_hash, cltv_expiry, source, onion_routing_packet, true,
- skimmed_fee_msat, logger)
+ skimmed_fee_msat, fee_estimator, logger)
.map(|msg_opt| assert!(msg_opt.is_none(), "We forced holding cell?"))
.map_err(|err| {
if let ChannelError::Ignore(_) = err { /* fine */ }
/// on this [`Channel`] if `force_holding_cell` is false.
///
/// `Err`s will only be [`ChannelError::Ignore`].
- fn send_htlc<L: Deref>(
+ fn send_htlc<F: Deref, L: Deref>(
&mut self, amount_msat: u64, payment_hash: PaymentHash, cltv_expiry: u32, source: HTLCSource,
onion_routing_packet: msgs::OnionPacket, mut force_holding_cell: bool,
- skimmed_fee_msat: Option<u64>, logger: &L
- ) -> Result<Option<msgs::UpdateAddHTLC>, ChannelError> where L::Target: Logger {
+ skimmed_fee_msat: Option<u64>, fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L
+ ) -> Result<Option<msgs::UpdateAddHTLC>, ChannelError>
+ where F::Target: FeeEstimator, L::Target: Logger
+ {
if (self.context.channel_state & (ChannelState::ChannelReady as u32 | BOTH_SIDES_SHUTDOWN_MASK)) != (ChannelState::ChannelReady as u32) {
return Err(ChannelError::Ignore("Cannot send HTLC until channel is fully established and we haven't started shutting down".to_owned()));
}
return Err(ChannelError::Ignore("Cannot send 0-msat HTLC".to_owned()));
}
- let available_balances = self.context.get_available_balances();
+ let available_balances = self.context.get_available_balances(fee_estimator);
if amount_msat < available_balances.next_outbound_htlc_minimum_msat {
return Err(ChannelError::Ignore(format!("Cannot send less than our next-HTLC minimum - {} msat",
available_balances.next_outbound_htlc_minimum_msat)));
}
let need_holding_cell = (self.context.channel_state & (ChannelState::AwaitingRemoteRevoke as u32 | ChannelState::MonitorUpdateInProgress as u32)) != 0;
- log_debug!(logger, "Pushing new outbound HTLC for {} msat {}", amount_msat,
+ log_debug!(logger, "Pushing new outbound HTLC with hash {} for {} msat {}",
+ payment_hash, amount_msat,
if force_holding_cell { "into holding cell" }
else if need_holding_cell { "into holding cell as we're awaiting an RAA or monitor" }
else { "to peer" });
Some(InboundHTLCState::AwaitingAnnouncedRemoteRevoke(forward_info.clone()))
} else { None };
if let Some(state) = new_state {
- log_trace!(logger, " ...promoting inbound AwaitingRemoteRevokeToAnnounce {} to AwaitingAnnouncedRemoteRevoke", log_bytes!(htlc.payment_hash.0));
+ log_trace!(logger, " ...promoting inbound AwaitingRemoteRevokeToAnnounce {} to AwaitingAnnouncedRemoteRevoke", &htlc.payment_hash);
htlc.state = state;
}
}
for htlc in self.context.pending_outbound_htlcs.iter_mut() {
if let &mut OutboundHTLCState::AwaitingRemoteRevokeToRemove(ref mut outcome) = &mut htlc.state {
- log_trace!(logger, " ...promoting outbound AwaitingRemoteRevokeToRemove {} to AwaitingRemovedRemoteRevoke", log_bytes!(htlc.payment_hash.0));
+ log_trace!(logger, " ...promoting outbound AwaitingRemoteRevokeToRemove {} to AwaitingRemovedRemoteRevoke", &htlc.payment_hash);
// Grab the preimage, if it exists, instead of cloning
let mut reason = OutboundHTLCOutcome::Success(None);
mem::swap(outcome, &mut reason);
}
self.context.resend_order = RAACommitmentOrder::RevokeAndACKFirst;
- let (counterparty_commitment_txid, mut htlcs_ref) = self.build_commitment_no_state_update(logger);
+ let (mut htlcs_ref, counterparty_commitment_tx) =
+ self.build_commitment_no_state_update(logger);
+ let counterparty_commitment_txid = counterparty_commitment_tx.trust().txid();
let htlcs: Vec<(HTLCOutputInCommitment, Option<Box<HTLCSource>>)> =
htlcs_ref.drain(..).map(|(htlc, htlc_source)| (htlc, htlc_source.map(|source_ref| Box::new(source_ref.clone())))).collect();
commitment_txid: counterparty_commitment_txid,
htlc_outputs: htlcs.clone(),
commitment_number: self.context.cur_counterparty_commitment_transaction_number,
- their_per_commitment_point: self.context.counterparty_cur_commitment_point.unwrap()
+ their_per_commitment_point: self.context.counterparty_cur_commitment_point.unwrap(),
+ feerate_per_kw: Some(counterparty_commitment_tx.feerate_per_kw()),
+ to_broadcaster_value_sat: Some(counterparty_commitment_tx.to_broadcaster_value_sat()),
+ to_countersignatory_value_sat: Some(counterparty_commitment_tx.to_countersignatory_value_sat()),
}]
};
self.context.channel_state |= ChannelState::AwaitingRemoteRevoke as u32;
monitor_update
}
- fn build_commitment_no_state_update<L: Deref>(&self, logger: &L) -> (Txid, Vec<(HTLCOutputInCommitment, Option<&HTLCSource>)>) where L::Target: Logger {
+ fn build_commitment_no_state_update<L: Deref>(&self, logger: &L)
+ -> (Vec<(HTLCOutputInCommitment, Option<&HTLCSource>)>, CommitmentTransaction)
+ where L::Target: Logger
+ {
let counterparty_keys = self.context.build_remote_transaction_keys();
let commitment_stats = self.context.build_commitment_transaction(self.context.cur_counterparty_commitment_transaction_number, &counterparty_keys, false, true, logger);
- let counterparty_commitment_txid = commitment_stats.tx.trust().txid();
+ let counterparty_commitment_tx = commitment_stats.tx;
#[cfg(any(test, fuzzing))]
{
}
}
- (counterparty_commitment_txid, commitment_stats.htlcs_included)
+ (commitment_stats.htlcs_included, counterparty_commitment_tx)
}
/// Only fails in case of signer rejection. Used for channel_reestablish commitment_signed
let counterparty_keys = self.context.build_remote_transaction_keys();
let commitment_stats = self.context.build_commitment_transaction(self.context.cur_counterparty_commitment_transaction_number, &counterparty_keys, false, true, logger);
let counterparty_commitment_txid = commitment_stats.tx.trust().txid();
- let (signature, htlc_signatures);
- {
- let mut htlcs = Vec::with_capacity(commitment_stats.htlcs_included.len());
- for &(ref htlc, _) in commitment_stats.htlcs_included.iter() {
- htlcs.push(htlc);
- }
+ match &self.context.holder_signer {
+ ChannelSignerType::Ecdsa(ecdsa) => {
+ let (signature, htlc_signatures);
- let res = self.context.holder_signer.sign_counterparty_commitment(&commitment_stats.tx, commitment_stats.preimages, &self.context.secp_ctx)
- .map_err(|_| ChannelError::Close("Failed to get signatures for new commitment_signed".to_owned()))?;
- signature = res.0;
- htlc_signatures = res.1;
+ {
+ let mut htlcs = Vec::with_capacity(commitment_stats.htlcs_included.len());
+ for &(ref htlc, _) in commitment_stats.htlcs_included.iter() {
+ htlcs.push(htlc);
+ }
- log_trace!(logger, "Signed remote commitment tx {} (txid {}) with redeemscript {} -> {} in channel {}",
- encode::serialize_hex(&commitment_stats.tx.trust().built_transaction().transaction),
- &counterparty_commitment_txid, encode::serialize_hex(&self.context.get_funding_redeemscript()),
- log_bytes!(signature.serialize_compact()[..]), log_bytes!(self.context.channel_id()));
+ let res = ecdsa.sign_counterparty_commitment(&commitment_stats.tx, commitment_stats.preimages, &self.context.secp_ctx)
+ .map_err(|_| ChannelError::Close("Failed to get signatures for new commitment_signed".to_owned()))?;
+ signature = res.0;
+ htlc_signatures = res.1;
+
+ log_trace!(logger, "Signed remote commitment tx {} (txid {}) with redeemscript {} -> {} in channel {}",
+ encode::serialize_hex(&commitment_stats.tx.trust().built_transaction().transaction),
+ &counterparty_commitment_txid, encode::serialize_hex(&self.context.get_funding_redeemscript()),
+ log_bytes!(signature.serialize_compact()[..]), &self.context.channel_id());
+
+ for (ref htlc_sig, ref htlc) in htlc_signatures.iter().zip(htlcs) {
+ log_trace!(logger, "Signed remote HTLC tx {} with redeemscript {} with pubkey {} -> {} in channel {}",
+ encode::serialize_hex(&chan_utils::build_htlc_transaction(&counterparty_commitment_txid, commitment_stats.feerate_per_kw, self.context.get_holder_selected_contest_delay(), htlc, &self.context.channel_type, &counterparty_keys.broadcaster_delayed_payment_key, &counterparty_keys.revocation_key)),
+ encode::serialize_hex(&chan_utils::get_htlc_redeemscript(&htlc, &self.context.channel_type, &counterparty_keys)),
+ log_bytes!(counterparty_keys.broadcaster_htlc_key.serialize()),
+ log_bytes!(htlc_sig.serialize_compact()[..]), &self.context.channel_id());
+ }
+ }
- for (ref htlc_sig, ref htlc) in htlc_signatures.iter().zip(htlcs) {
- log_trace!(logger, "Signed remote HTLC tx {} with redeemscript {} with pubkey {} -> {} in channel {}",
- encode::serialize_hex(&chan_utils::build_htlc_transaction(&counterparty_commitment_txid, commitment_stats.feerate_per_kw, self.context.get_holder_selected_contest_delay(), htlc, &self.context.channel_type, &counterparty_keys.broadcaster_delayed_payment_key, &counterparty_keys.revocation_key)),
- encode::serialize_hex(&chan_utils::get_htlc_redeemscript(&htlc, &self.context.channel_type, &counterparty_keys)),
- log_bytes!(counterparty_keys.broadcaster_htlc_key.serialize()),
- log_bytes!(htlc_sig.serialize_compact()[..]), log_bytes!(self.context.channel_id()));
+ Ok((msgs::CommitmentSigned {
+ channel_id: self.context.channel_id,
+ signature,
+ htlc_signatures,
+ #[cfg(taproot)]
+ partial_signature_with_nonce: None,
+ }, (counterparty_commitment_txid, commitment_stats.htlcs_included)))
}
}
-
- Ok((msgs::CommitmentSigned {
- channel_id: self.context.channel_id,
- signature,
- htlc_signatures,
- #[cfg(taproot)]
- partial_signature_with_nonce: None,
- }, (counterparty_commitment_txid, commitment_stats.htlcs_included)))
}
/// Adds a pending outbound HTLC to this channel, and builds a new remote commitment
///
/// Shorthand for calling [`Self::send_htlc`] followed by a commitment update, see docs on
/// [`Self::send_htlc`] and [`Self::build_commitment_no_state_update`] for more info.
- pub fn send_htlc_and_commit<L: Deref>(
- &mut self, amount_msat: u64, payment_hash: PaymentHash, cltv_expiry: u32, source: HTLCSource,
- onion_routing_packet: msgs::OnionPacket, skimmed_fee_msat: Option<u64>, logger: &L
- ) -> Result<Option<ChannelMonitorUpdate>, ChannelError> where L::Target: Logger {
+ pub fn send_htlc_and_commit<F: Deref, L: Deref>(
+ &mut self, amount_msat: u64, payment_hash: PaymentHash, cltv_expiry: u32,
+ source: HTLCSource, onion_routing_packet: msgs::OnionPacket, skimmed_fee_msat: Option<u64>,
+ fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L
+ ) -> Result<Option<ChannelMonitorUpdate>, ChannelError>
+ where F::Target: FeeEstimator, L::Target: Logger
+ {
let send_res = self.send_htlc(amount_msat, payment_hash, cltv_expiry, source,
- onion_routing_packet, false, skimmed_fee_msat, logger);
+ onion_routing_packet, false, skimmed_fee_msat, fee_estimator, logger);
if let Err(e) = &send_res { if let ChannelError::Ignore(_) = e {} else { debug_assert!(false, "Sending cannot trigger channel failure"); } }
match send_res? {
Some(_) => {
///
/// May jump to the channel being fully shutdown (see [`Self::is_shutdown`]) in which case no
/// [`ChannelMonitorUpdate`] will be returned).
- pub fn get_shutdown<SP: Deref>(&mut self, signer_provider: &SP, their_features: &InitFeatures,
+ 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>
- where SP::Target: SignerProvider {
+ {
for htlc in self.context.pending_outbound_htlcs.iter() {
if let OutboundHTLCState::LocalAnnounced(_) = htlc.state {
return Err(APIError::APIMisuseError{err: "Cannot begin shutdown with pending HTLCs. Process pending events first".to_owned()});
// If we haven't funded the channel yet, we don't need to bother ensuring the shutdown
// script is set, we just force-close and call it a day.
let mut chan_closed = false;
- if self.context.channel_state < ChannelState::FundingSent as u32 {
+ if self.context.channel_state & !STATE_FLAGS < ChannelState::FundingSent as u32 {
chan_closed = true;
}
// 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 < ChannelState::FundingSent as u32 {
+ if self.context.channel_state & !STATE_FLAGS < ChannelState::FundingSent as u32 {
self.context.channel_state = ChannelState::ShutdownComplete as u32;
} else {
self.context.channel_state |= ChannelState::LocalShutdownSent as u32;
}
/// A not-yet-funded outbound (from holder) channel using V1 channel establishment.
-pub(super) struct OutboundV1Channel<Signer: ChannelSigner> {
- pub context: ChannelContext<Signer>,
+pub(super) struct OutboundV1Channel<SP: Deref> where SP::Target: SignerProvider {
+ pub context: ChannelContext<SP>,
+ pub unfunded_context: UnfundedChannelContext,
}
-impl<Signer: WriteableEcdsaChannelSigner> OutboundV1Channel<Signer> {
- pub fn new<ES: Deref, SP: Deref, F: Deref>(
+impl<SP: Deref> OutboundV1Channel<SP> where SP::Target: SignerProvider {
+ pub fn new<ES: Deref, F: Deref>(
fee_estimator: &LowerBoundedFeeEstimator<F>, entropy_source: &ES, signer_provider: &SP, counterparty_node_id: PublicKey, their_features: &InitFeatures,
channel_value_satoshis: u64, push_msat: u64, user_id: u128, config: &UserConfig, current_chain_height: u32,
outbound_scid_alias: u64
- ) -> Result<OutboundV1Channel<Signer>, APIError>
+ ) -> Result<OutboundV1Channel<SP>, APIError>
where ES::Target: EntropySource,
- SP::Target: SignerProvider<Signer = Signer>,
- F::Target: FeeEstimator,
+ F::Target: FeeEstimator
{
let holder_selected_contest_delay = config.channel_handshake_config.our_to_self_delay;
let channel_keys_id = signer_provider.generate_channel_keys_id(false, channel_value_satoshis, user_id);
let channel_type = Self::get_initial_channel_type(&config, their_features);
debug_assert!(channel_type.is_subset(&channelmanager::provided_channel_type_features(&config)));
- let feerate = fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::Normal);
+ let commitment_conf_target = if channel_type.supports_anchors_zero_fee_htlc_tx() {
+ ConfirmationTarget::MempoolMinimum
+ } else {
+ ConfirmationTarget::Normal
+ };
+ 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(feerate, MIN_AFFORDABLE_HTLC_COUNT, &channel_type);
+ let commitment_tx_fee = commit_tx_fee_msat(commitment_feerate, MIN_AFFORDABLE_HTLC_COUNT, &channel_type);
if value_to_self_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) });
}
Err(_) => return Err(APIError::ChannelUnavailable { err: "Failed to get destination script".to_owned()}),
};
- let temporary_channel_id = entropy_source.get_secure_random_bytes();
+ let temporary_channel_id = ChannelId::temporary_from_entropy_source(entropy_source);
Ok(Self {
context: ChannelContext {
latest_monitor_update_id: 0,
- holder_signer,
+ holder_signer: ChannelSignerType::Ecdsa(holder_signer),
shutdown_scriptpubkey,
destination_script,
closing_fee_limits: None,
target_closing_feerate_sats_per_kw: None,
- inbound_awaiting_accept: false,
-
funding_tx_confirmed_in: None,
funding_tx_confirmation_height: 0,
short_channel_id: None,
channel_creation_height: current_chain_height,
- feerate_per_kw: feerate,
+ feerate_per_kw: commitment_feerate,
counterparty_dust_limit_satoshis: 0,
holder_dust_limit_satoshis: MIN_CHAN_DUST_LIMIT_SATOSHIS,
counterparty_max_htlc_value_in_flight_msat: 0,
channel_type_features: channel_type.clone()
},
funding_transaction: None,
+ is_batch_funding: None,
counterparty_cur_commitment_point: None,
counterparty_prev_commitment_point: None,
channel_keys_id,
blocked_monitor_updates: Vec::new(),
- }
+ },
+ unfunded_context: UnfundedChannelContext { unfunded_channel_age_ticks: 0 }
})
}
- /// If an Err is returned, it is a ChannelError::Close (for get_outbound_funding_created)
- fn get_outbound_funding_created_signature<L: Deref>(&mut self, logger: &L) -> Result<Signature, ChannelError> where L::Target: Logger {
+ /// If an Err is returned, it is a ChannelError::Close (for get_funding_created)
+ fn get_funding_created_signature<L: Deref>(&mut self, logger: &L) -> Result<Signature, ChannelError> where L::Target: Logger {
let counterparty_keys = self.context.build_remote_transaction_keys();
let counterparty_initial_commitment_tx = self.context.build_commitment_transaction(self.context.cur_counterparty_commitment_transaction_number, &counterparty_keys, false, false, logger).tx;
- Ok(self.context.holder_signer.sign_counterparty_commitment(&counterparty_initial_commitment_tx, Vec::new(), &self.context.secp_ctx)
- .map_err(|_| ChannelError::Close("Failed to get signatures for new commitment_signed".to_owned()))?.0)
+ match &self.context.holder_signer {
+ // TODO (taproot|arik): move match into calling method for Taproot
+ ChannelSignerType::Ecdsa(ecdsa) => {
+ Ok(ecdsa.sign_counterparty_commitment(&counterparty_initial_commitment_tx, Vec::new(), &self.context.secp_ctx)
+ .map_err(|_| ChannelError::Close("Failed to get signatures for new commitment_signed".to_owned()))?.0)
+ }
+ }
}
/// Updates channel state with knowledge of the funding transaction's txid/index, and generates
/// Note that channel_id changes during this call!
/// Do NOT broadcast the funding transaction until after a successful funding_signed call!
/// If an Err is returned, it is a ChannelError::Close.
- pub fn get_outbound_funding_created<L: Deref>(mut self, funding_transaction: Transaction, funding_txo: OutPoint, logger: &L)
- -> Result<(Channel<Signer>, msgs::FundingCreated), (Self, ChannelError)> where L::Target: Logger {
+ pub fn get_funding_created<L: Deref>(mut self, funding_transaction: Transaction, funding_txo: OutPoint, is_batch_funding: bool, logger: &L)
+ -> Result<(Channel<SP>, msgs::FundingCreated), (Self, ChannelError)> where L::Target: Logger {
if !self.context.is_outbound() {
panic!("Tried to create outbound funding_created message on an inbound channel!");
}
}
self.context.channel_transaction_parameters.funding_outpoint = Some(funding_txo);
- self.context.holder_signer.provide_channel_parameters(&self.context.channel_transaction_parameters);
+ self.context.holder_signer.as_mut().provide_channel_parameters(&self.context.channel_transaction_parameters);
- let signature = match self.get_outbound_funding_created_signature(logger) {
+ let signature = match self.get_funding_created_signature(logger) {
Ok(res) => res,
Err(e) => {
log_error!(logger, "Got bad signatures: {:?}!", e);
self.context.channel_state = ChannelState::FundingCreated as u32;
self.context.channel_id = funding_txo.to_channel_id();
+
+ // If the funding transaction is a coinbase transaction, we need to set the minimum depth to 100.
+ // We can skip this if it is a zero-conf channel.
+ if funding_transaction.is_coin_base() &&
+ self.context.minimum_depth.unwrap_or(0) > 0 &&
+ self.context.minimum_depth.unwrap_or(0) < COINBASE_MATURITY {
+ self.context.minimum_depth = Some(COINBASE_MATURITY);
+ }
+
self.context.funding_transaction = Some(funding_transaction);
+ self.context.is_batch_funding = Some(()).filter(|_| is_batch_funding);
let channel = Channel {
context: self.context,
/// If we receive an error message, it may only be a rejection of the channel type we tried,
/// 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(&mut self, chain_hash: BlockHash) -> Result<msgs::OpenChannel, ()> {
+ pub(crate) fn maybe_handle_error_without_close<F: Deref>(
+ &mut self, chain_hash: BlockHash, fee_estimator: &LowerBoundedFeeEstimator<F>
+ ) -> Result<msgs::OpenChannel, ()>
+ where
+ F::Target: FeeEstimator
+ {
if !self.context.is_outbound() || self.context.channel_state != ChannelState::OurInitSent as u32 { return Err(()); }
if self.context.channel_type == ChannelTypeFeatures::only_static_remote_key() {
// We've exhausted our options
// 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);
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();
panic!("Tried to send an open_channel for a channel that has already advanced");
}
- let first_per_commitment_point = self.context.holder_signer.get_per_commitment_point(self.context.cur_holder_commitment_transaction_number, &self.context.secp_ctx);
+ let first_per_commitment_point = self.context.holder_signer.as_ref().get_per_commitment_point(self.context.cur_holder_commitment_transaction_number, &self.context.secp_ctx);
let keys = self.context.get_holder_pubkeys();
msgs::OpenChannel {
}
/// A not-yet-funded inbound (from counterparty) channel using V1 channel establishment.
-pub(super) struct InboundV1Channel<Signer: ChannelSigner> {
- pub context: ChannelContext<Signer>,
+pub(super) struct InboundV1Channel<SP: Deref> where SP::Target: SignerProvider {
+ pub context: ChannelContext<SP>,
+ pub unfunded_context: UnfundedChannelContext,
}
-impl<Signer: WriteableEcdsaChannelSigner> InboundV1Channel<Signer> {
+impl<SP: Deref> InboundV1Channel<SP> where SP::Target: SignerProvider {
/// Creates a new channel from a remote sides' request for one.
/// Assumes chain_hash has already been checked and corresponds with what we expect!
- pub fn new<ES: Deref, SP: Deref, F: Deref, L: Deref>(
+ pub fn new<ES: Deref, F: Deref, L: Deref>(
fee_estimator: &LowerBoundedFeeEstimator<F>, entropy_source: &ES, signer_provider: &SP,
counterparty_node_id: PublicKey, our_supported_features: &ChannelTypeFeatures,
their_features: &InitFeatures, msg: &msgs::OpenChannel, user_id: u128, config: &UserConfig,
- current_chain_height: u32, logger: &L, outbound_scid_alias: u64
- ) -> Result<InboundV1Channel<Signer>, ChannelError>
+ current_chain_height: u32, logger: &L, is_0conf: bool,
+ ) -> Result<InboundV1Channel<SP>, ChannelError>
where ES::Target: EntropySource,
- SP::Target: SignerProvider<Signer = Signer>,
F::Target: FeeEstimator,
L::Target: Logger,
{
if msg.htlc_minimum_msat >= full_channel_value_msat {
return Err(ChannelError::Close(format!("Minimum htlc value ({}) was larger than full channel value ({})", msg.htlc_minimum_msat, full_channel_value_msat)));
}
- Channel::<Signer>::check_remote_fee(fee_estimator, msg.feerate_per_kw, None, logger)?;
+ Channel::<SP>::check_remote_fee(&channel_type, fee_estimator, msg.feerate_per_kw, None, logger)?;
let max_counterparty_selected_contest_delay = u16::min(config.channel_handshake_limits.their_to_self_delay, MAX_LOCAL_BREAKDOWN_TIMEOUT);
if msg.to_self_delay > max_counterparty_selected_contest_delay {
let mut secp_ctx = Secp256k1::new();
secp_ctx.seeded_randomize(&entropy_source.get_secure_random_bytes());
+ let minimum_depth = if is_0conf {
+ Some(0)
+ } else {
+ Some(cmp::max(config.channel_handshake_config.minimum_depth, 1))
+ };
+
let chan = Self {
context: ChannelContext {
user_id,
latest_monitor_update_id: 0,
- holder_signer,
+ holder_signer: ChannelSignerType::Ecdsa(holder_signer),
shutdown_scriptpubkey,
destination_script,
closing_fee_limits: None,
target_closing_feerate_sats_per_kw: None,
- inbound_awaiting_accept: true,
-
funding_tx_confirmed_in: None,
funding_tx_confirmation_height: 0,
short_channel_id: None,
holder_htlc_minimum_msat: if config.channel_handshake_config.our_htlc_minimum_msat == 0 { 1 } else { config.channel_handshake_config.our_htlc_minimum_msat },
counterparty_max_accepted_htlcs: msg.max_accepted_htlcs,
holder_max_accepted_htlcs: cmp::min(config.channel_handshake_config.our_max_accepted_htlcs, MAX_HTLCS),
- minimum_depth: Some(cmp::max(config.channel_handshake_config.minimum_depth, 1)),
+ minimum_depth,
counterparty_forwarding_info: None,
channel_type_features: channel_type.clone()
},
funding_transaction: None,
+ is_batch_funding: None,
counterparty_cur_commitment_point: Some(msg.first_per_commitment_point),
counterparty_prev_commitment_point: None,
sent_message_awaiting_response: None,
latest_inbound_scid_alias: None,
- outbound_scid_alias,
+ outbound_scid_alias: 0,
channel_pending_event_emitted: false,
channel_ready_event_emitted: false,
channel_keys_id,
blocked_monitor_updates: Vec::new(),
- }
+ },
+ unfunded_context: UnfundedChannelContext { unfunded_channel_age_ticks: 0 }
};
Ok(chan)
}
- pub fn is_awaiting_accept(&self) -> bool {
- self.context.inbound_awaiting_accept
- }
-
- /// Sets this channel to accepting 0conf, must be done before `get_accept_channel`
- pub fn set_0conf(&mut self) {
- assert!(self.context.inbound_awaiting_accept);
- self.context.minimum_depth = Some(0);
- }
-
/// Marks an inbound channel as accepted and generates a [`msgs::AcceptChannel`] message which
/// should be sent back to the counterparty node.
///
/// [`msgs::AcceptChannel`]: crate::ln::msgs::AcceptChannel
- pub fn accept_inbound_channel(&mut self, user_id: u128) -> msgs::AcceptChannel {
+ pub fn accept_inbound_channel(&mut self) -> msgs::AcceptChannel {
if self.context.is_outbound() {
panic!("Tried to send accept_channel for an outbound channel?");
}
if self.context.cur_holder_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER {
panic!("Tried to send an accept_channel for a channel that has already advanced");
}
- if !self.context.inbound_awaiting_accept {
- panic!("The inbound channel has already been accepted");
- }
-
- self.context.user_id = user_id;
- self.context.inbound_awaiting_accept = false;
self.generate_accept_channel_message()
}
///
/// [`msgs::AcceptChannel`]: crate::ln::msgs::AcceptChannel
fn generate_accept_channel_message(&self) -> msgs::AcceptChannel {
- let first_per_commitment_point = self.context.holder_signer.get_per_commitment_point(self.context.cur_holder_commitment_transaction_number, &self.context.secp_ctx);
+ let first_per_commitment_point = self.context.holder_signer.as_ref().get_per_commitment_point(self.context.cur_holder_commitment_transaction_number, &self.context.secp_ctx);
let keys = self.context.get_holder_pubkeys();
msgs::AcceptChannel {
self.generate_accept_channel_message()
}
- fn funding_created_signature<L: Deref>(&mut self, sig: &Signature, logger: &L) -> Result<(Txid, CommitmentTransaction, Signature), ChannelError> where L::Target: Logger {
+ fn funding_created_signature<L: Deref>(&mut self, sig: &Signature, logger: &L) -> Result<(CommitmentTransaction, CommitmentTransaction, Signature), ChannelError> where L::Target: Logger {
let funding_script = self.context.get_funding_redeemscript();
let keys = self.context.build_holder_transaction_keys(self.context.cur_holder_commitment_transaction_number);
log_trace!(logger, "Checking funding_created tx signature {} by key {} against tx {} (sighash {}) with redeemscript {} for channel {}.",
log_bytes!(sig.serialize_compact()[..]), log_bytes!(self.context.counterparty_funding_pubkey().serialize()),
encode::serialize_hex(&initial_commitment_bitcoin_tx.transaction), log_bytes!(sighash[..]),
- encode::serialize_hex(&funding_script), log_bytes!(self.context.channel_id()));
+ encode::serialize_hex(&funding_script), &self.context.channel_id());
secp_check!(self.context.secp_ctx.verify_ecdsa(&sighash, &sig, self.context.counterparty_funding_pubkey()), "Invalid funding_created signature from peer".to_owned());
}
let counterparty_trusted_tx = counterparty_initial_commitment_tx.trust();
let counterparty_initial_bitcoin_tx = counterparty_trusted_tx.built_transaction();
log_trace!(logger, "Initial counterparty tx for channel {} is: txid {} tx {}",
- log_bytes!(self.context.channel_id()), counterparty_initial_bitcoin_tx.txid, encode::serialize_hex(&counterparty_initial_bitcoin_tx.transaction));
+ &self.context.channel_id(), counterparty_initial_bitcoin_tx.txid, encode::serialize_hex(&counterparty_initial_bitcoin_tx.transaction));
- let counterparty_signature = self.context.holder_signer.sign_counterparty_commitment(&counterparty_initial_commitment_tx, Vec::new(), &self.context.secp_ctx)
- .map_err(|_| ChannelError::Close("Failed to get signatures for new commitment_signed".to_owned()))?.0;
+ match &self.context.holder_signer {
+ // TODO (arik): move match into calling method for Taproot
+ ChannelSignerType::Ecdsa(ecdsa) => {
+ let counterparty_signature = ecdsa.sign_counterparty_commitment(&counterparty_initial_commitment_tx, Vec::new(), &self.context.secp_ctx)
+ .map_err(|_| ChannelError::Close("Failed to get signatures for new commitment_signed".to_owned()))?.0;
- // We sign "counterparty" commitment transaction, allowing them to broadcast the tx if they wish.
- Ok((counterparty_initial_bitcoin_tx.txid, initial_commitment_tx, counterparty_signature))
+ // We sign "counterparty" commitment transaction, allowing them to broadcast the tx if they wish.
+ Ok((counterparty_initial_commitment_tx, initial_commitment_tx, counterparty_signature))
+ }
+ }
}
- pub fn funding_created<SP: Deref, L: Deref>(
+ pub fn funding_created<L: Deref>(
mut self, msg: &msgs::FundingCreated, best_block: BestBlock, signer_provider: &SP, logger: &L
- ) -> Result<(Channel<Signer>, msgs::FundingSigned, ChannelMonitor<Signer>), (Self, ChannelError)>
+ ) -> Result<(Channel<SP>, msgs::FundingSigned, ChannelMonitor<<SP::Target as SignerProvider>::Signer>), (Self, ChannelError)>
where
- SP::Target: SignerProvider<Signer = Signer>,
L::Target: Logger
{
if self.context.is_outbound() {
// channel.
return Err((self, ChannelError::Close("Received funding_created after we got the channel!".to_owned())));
}
- if self.context.inbound_awaiting_accept {
- return Err((self, ChannelError::Close("FundingCreated message received before the channel was accepted".to_owned())));
- }
if self.context.commitment_secrets.get_min_seen_secret() != (1 << 48) ||
self.context.cur_counterparty_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER ||
self.context.cur_holder_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER {
self.context.channel_transaction_parameters.funding_outpoint = Some(funding_txo);
// This is an externally observable change before we finish all our checks. In particular
// funding_created_signature may fail.
- self.context.holder_signer.provide_channel_parameters(&self.context.channel_transaction_parameters);
+ self.context.holder_signer.as_mut().provide_channel_parameters(&self.context.channel_transaction_parameters);
- let (counterparty_initial_commitment_txid, initial_commitment_tx, signature) = match self.funding_created_signature(&msg.signature, logger) {
+ let (counterparty_initial_commitment_tx, initial_commitment_tx, signature) = match self.funding_created_signature(&msg.signature, logger) {
Ok(res) => res,
Err(ChannelError::Close(e)) => {
self.context.channel_transaction_parameters.funding_outpoint = None;
self.context.counterparty_funding_pubkey()
);
- if let Err(_) = self.context.holder_signer.validate_holder_commitment(&holder_commitment_tx, Vec::new()) {
+ if let Err(_) = self.context.holder_signer.as_ref().validate_holder_commitment(&holder_commitment_tx, Vec::new()) {
return Err((self, ChannelError::Close("Failed to validate our commitment".to_owned())));
}
obscure_factor,
holder_commitment_tx, best_block, self.context.counterparty_node_id);
- channel_monitor.provide_latest_counterparty_commitment_tx(counterparty_initial_commitment_txid, Vec::new(), self.context.cur_counterparty_commitment_transaction_number, self.context.counterparty_cur_commitment_point.unwrap(), logger);
+ channel_monitor.provide_initial_counterparty_commitment_tx(
+ counterparty_initial_commitment_tx.trust().txid(), Vec::new(),
+ self.context.cur_counterparty_commitment_transaction_number,
+ self.context.counterparty_cur_commitment_point.unwrap(), self.context.feerate_per_kw,
+ counterparty_initial_commitment_tx.to_broadcaster_value_sat(),
+ counterparty_initial_commitment_tx.to_countersignatory_value_sat(), logger);
self.context.channel_state = ChannelState::FundingSent as u32;
self.context.channel_id = funding_txo.to_channel_id();
self.context.cur_counterparty_commitment_transaction_number -= 1;
self.context.cur_holder_commitment_transaction_number -= 1;
- log_info!(logger, "Generated funding_signed for peer for channel {}", log_bytes!(self.context.channel_id()));
+ log_info!(logger, "Generated funding_signed for peer for channel {}", &self.context.channel_id());
// Promote the channel to a full-fledged one now that we have updated the state and have a
// `ChannelMonitor`.
}
}
-impl<Signer: WriteableEcdsaChannelSigner> Writeable for Channel<Signer> {
+impl<SP: Deref> Writeable for Channel<SP> where SP::Target: SignerProvider {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
// Note that we write out as if remove_uncommitted_htlcs_and_mark_paused had just been
// called.
self.context.latest_monitor_update_id.write(writer)?;
let mut key_data = VecWriter(Vec::new());
- self.context.holder_signer.write(&mut key_data)?;
+ // TODO (taproot|arik): Introduce serialization distinction for non-ECDSA signers.
+ self.context.holder_signer.as_ecdsa().expect("Only ECDSA signers may be serialized").write(&mut key_data)?;
assert!(key_data.0.len() < core::usize::MAX);
assert!(key_data.0.len() < core::u32::MAX as usize);
(key_data.0.len() as u32).write(writer)?;
(5, self.context.config, required),
(6, serialized_holder_htlc_max_in_flight, option),
(7, self.context.shutdown_scriptpubkey, option),
- (8, self.context.blocked_monitor_updates, vec_type),
+ (8, self.context.blocked_monitor_updates, optional_vec),
(9, self.context.target_closing_feerate_sats_per_kw, option),
- (11, self.context.monitor_pending_finalized_fulfills, vec_type),
+ (11, self.context.monitor_pending_finalized_fulfills, required_vec),
(13, self.context.channel_creation_height, required),
- (15, preimages, vec_type),
+ (15, preimages, required_vec),
(17, self.context.announcement_sigs_state, required),
(19, self.context.latest_inbound_scid_alias, option),
(21, self.context.outbound_scid_alias, required),
(31, channel_pending_event_emitted, option),
(35, pending_outbound_skimmed_fees, optional_vec),
(37, holding_cell_skimmed_fees, optional_vec),
+ (38, self.context.is_batch_funding, option),
});
Ok(())
}
const MAX_ALLOC_SIZE: usize = 64*1024;
-impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c ChannelTypeFeatures)> for Channel<<SP::Target as SignerProvider>::Signer>
+impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c ChannelTypeFeatures)> for Channel<SP>
where
ES::Target: EntropySource,
SP::Target: SignerProvider
};
let mut channel_parameters: ChannelTransactionParameters = Readable::read(reader)?;
- let funding_transaction = Readable::read(reader)?;
+ let funding_transaction: Option<Transaction> = Readable::read(reader)?;
let counterparty_cur_commitment_point = Readable::read(reader)?;
let mut user_id_high_opt: Option<u64> = None;
let mut channel_keys_id: Option<[u8; 32]> = None;
- let mut temporary_channel_id: Option<[u8; 32]> = None;
+ let mut temporary_channel_id: Option<ChannelId> = None;
let mut holder_max_accepted_htlcs: Option<u16> = None;
let mut blocked_monitor_updates = Some(Vec::new());
let mut pending_outbound_skimmed_fees_opt: Option<Vec<Option<u64>>> = None;
let mut holding_cell_skimmed_fees_opt: Option<Vec<Option<u64>>> = None;
+ let mut is_batch_funding: Option<()> = None;
+
read_tlv_fields!(reader, {
(0, announcement_sigs, option),
(1, minimum_depth, option),
(5, config, option), // Note that if none is provided we will *not* overwrite the existing one.
(6, holder_max_htlc_value_in_flight_msat, option),
(7, shutdown_scriptpubkey, option),
- (8, blocked_monitor_updates, vec_type),
+ (8, blocked_monitor_updates, optional_vec),
(9, target_closing_feerate_sats_per_kw, option),
- (11, monitor_pending_finalized_fulfills, vec_type),
+ (11, monitor_pending_finalized_fulfills, optional_vec),
(13, channel_creation_height, option),
- (15, preimages_opt, vec_type),
+ (15, preimages_opt, optional_vec),
(17, announcement_sigs_state, option),
(19, latest_inbound_scid_alias, option),
(21, outbound_scid_alias, option),
(31, channel_pending_event_emitted, option),
(35, pending_outbound_skimmed_fees_opt, optional_vec),
(37, holding_cell_skimmed_fees_opt, optional_vec),
+ (38, is_batch_funding, option),
});
let (channel_keys_id, holder_signer) = if let Some(channel_keys_id) = channel_keys_id {
// If we've gotten to the funding stage of the channel, populate the signer with its
// required channel parameters.
let non_shutdown_state = channel_state & (!MULTI_STATE_FLAGS);
- if non_shutdown_state >= (ChannelState::FundingCreated as u32) {
+ if non_shutdown_state & !STATE_FLAGS >= (ChannelState::FundingCreated as u32) {
holder_signer.provide_channel_parameters(&channel_parameters);
}
(channel_keys_id, holder_signer)
latest_monitor_update_id,
- holder_signer,
+ holder_signer: ChannelSignerType::Ecdsa(holder_signer),
shutdown_scriptpubkey,
destination_script,
closing_fee_limits: None,
target_closing_feerate_sats_per_kw,
- inbound_awaiting_accept: false,
-
funding_tx_confirmed_in,
funding_tx_confirmation_height,
short_channel_id,
channel_transaction_parameters: channel_parameters,
funding_transaction,
+ is_batch_funding,
counterparty_cur_commitment_point,
counterparty_prev_commitment_point,
use crate::ln::PaymentHash;
use crate::ln::channelmanager::{self, HTLCSource, PaymentId};
use crate::ln::channel::InitFeatures;
- use crate::ln::channel::{Channel, InboundHTLCOutput, OutboundV1Channel, InboundV1Channel, OutboundHTLCOutput, InboundHTLCState, OutboundHTLCState, HTLCCandidate, HTLCInitiator, commit_tx_fee_msat};
+ use crate::ln::channel::{Channel, ChannelState, InboundHTLCOutput, OutboundV1Channel, InboundV1Channel, OutboundHTLCOutput, InboundHTLCState, OutboundHTLCState, HTLCCandidate, HTLCInitiator, commit_tx_fee_msat};
use crate::ln::channel::{MAX_FUNDING_SATOSHIS_NO_WUMBO, TOTAL_BITCOIN_SUPPLY_SATOSHIS, MIN_THEIR_CHAN_RESERVE_SATOSHIS};
use crate::ln::features::ChannelTypeFeatures;
use crate::ln::msgs::{ChannelUpdate, DecodeError, UnsignedChannelUpdate, MAX_VALUE_MSAT};
use crate::chain::transaction::OutPoint;
use crate::routing::router::Path;
use crate::util::config::UserConfig;
- use crate::util::enforcing_trait_impls::EnforcingSigner;
use crate::util::errors::APIError;
use crate::util::test_utils;
- use crate::util::test_utils::OnGetShutdownScriptpubkey;
+ use crate::util::test_utils::{OnGetShutdownScriptpubkey, TestKeysInterface};
use bitcoin::secp256k1::{Secp256k1, ecdsa::Signature};
use bitcoin::secp256k1::ffi::Signature as FFISignature;
use bitcoin::secp256k1::{SecretKey,PublicKey};
// arithmetic, causing a panic with debug assertions enabled.
let fee_est = TestFeeEstimator { fee_est: 42 };
let bounded_fee_estimator = LowerBoundedFeeEstimator::new(&fee_est);
- assert!(Channel::<InMemorySigner>::check_remote_fee(&bounded_fee_estimator,
+ assert!(Channel::<&TestKeysInterface>::check_remote_fee(
+ &ChannelTypeFeatures::only_static_remote_key(), &bounded_fee_estimator,
u32::max_value(), None, &&test_utils::TestLogger::new()).is_err());
}
let secp_ctx = Secp256k1::new();
let node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
let config = UserConfig::default();
- match OutboundV1Channel::<EnforcingSigner>::new(&LowerBoundedFeeEstimator::new(&TestFeeEstimator { fee_est: 253 }), &&keys_provider, &&keys_provider, node_id, &features, 10000000, 100000, 42, &config, 0, 42) {
+ match OutboundV1Channel::<&TestKeysInterface>::new(&LowerBoundedFeeEstimator::new(&TestFeeEstimator { fee_est: 253 }), &&keys_provider, &&keys_provider, node_id, &features, 10000000, 100000, 42, &config, 0, 42) {
Err(APIError::IncompatibleShutdownScript { script }) => {
assert_eq!(script.into_inner(), non_v0_segwit_shutdown_script.into_inner());
},
let node_a_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
let config = UserConfig::default();
- let node_a_chan = OutboundV1Channel::<EnforcingSigner>::new(&bounded_fee_estimator, &&keys_provider, &&keys_provider, node_a_node_id, &channelmanager::provided_init_features(&config), 10000000, 100000, 42, &config, 0, 42).unwrap();
+ let node_a_chan = OutboundV1Channel::<&TestKeysInterface>::new(&bounded_fee_estimator, &&keys_provider, &&keys_provider, node_a_node_id, &channelmanager::provided_init_features(&config), 10000000, 100000, 42, &config, 0, 42).unwrap();
// Now change the fee so we can check that the fee in the open_channel message is the
// same as the old fee.
// Create Node A's channel pointing to Node B's pubkey
let node_b_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
let config = UserConfig::default();
- let mut node_a_chan = OutboundV1Channel::<EnforcingSigner>::new(&feeest, &&keys_provider, &&keys_provider, node_b_node_id, &channelmanager::provided_init_features(&config), 10000000, 100000, 42, &config, 0, 42).unwrap();
+ let mut node_a_chan = OutboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider, node_b_node_id, &channelmanager::provided_init_features(&config), 10000000, 100000, 42, &config, 0, 42).unwrap();
// 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 node_b_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[7; 32]).unwrap());
- let mut node_b_chan = InboundV1Channel::<EnforcingSigner>::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, 42).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();
// Node B --> Node A: accept channel, explicitly setting B's dust limit.
- let mut accept_channel_msg = node_b_chan.accept_inbound_channel(0);
+ let mut accept_channel_msg = node_b_chan.accept_inbound_channel();
accept_channel_msg.dust_limit_satoshis = 546;
node_a_chan.accept_channel(&accept_channel_msg, &config.channel_handshake_limits, &channelmanager::provided_init_features(&config)).unwrap();
node_a_chan.context.holder_dust_limit_satoshis = 1560;
value: 10000000, script_pubkey: output_script.clone(),
}]};
let funding_outpoint = OutPoint{ txid: tx.txid(), index: 0 };
- let (mut node_a_chan, funding_created_msg) = node_a_chan.get_outbound_funding_created(tx.clone(), funding_outpoint, &&logger).map_err(|_| ()).unwrap();
+ let (mut node_a_chan, funding_created_msg) = node_a_chan.get_funding_created(tx.clone(), funding_outpoint, false, &&logger).map_err(|_| ()).unwrap();
let (_, funding_signed_msg, _) = node_b_chan.funding_created(&funding_created_msg, best_block, &&keys_provider, &&logger).map_err(|_| ()).unwrap();
// Node B --> Node A: funding signed
let node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
let config = UserConfig::default();
- let mut chan = OutboundV1Channel::<EnforcingSigner>::new(&fee_est, &&keys_provider, &&keys_provider, node_id, &channelmanager::provided_init_features(&config), 10000000, 100000, 42, &config, 0, 42).unwrap();
+ let mut chan = OutboundV1Channel::<&TestKeysInterface>::new(&fee_est, &&keys_provider, &&keys_provider, node_id, &channelmanager::provided_init_features(&config), 10000000, 100000, 42, &config, 0, 42).unwrap();
let commitment_tx_fee_0_htlcs = commit_tx_fee_msat(chan.context.feerate_per_kw, 0, chan.context.get_channel_type());
let commitment_tx_fee_1_htlc = commit_tx_fee_msat(chan.context.feerate_per_kw, 1, chan.context.get_channel_type());
// Create Node A's channel pointing to Node B's pubkey
let node_b_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
let config = UserConfig::default();
- let mut node_a_chan = OutboundV1Channel::<EnforcingSigner>::new(&feeest, &&keys_provider, &&keys_provider, node_b_node_id, &channelmanager::provided_init_features(&config), 10000000, 100000, 42, &config, 0, 42).unwrap();
+ let mut node_a_chan = OutboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider, node_b_node_id, &channelmanager::provided_init_features(&config), 10000000, 100000, 42, &config, 0, 42).unwrap();
// Create Node B's channel by receiving Node A's open_channel message
let open_channel_msg = node_a_chan.get_open_channel(chain_hash);
let node_b_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[7; 32]).unwrap());
- let mut node_b_chan = InboundV1Channel::<EnforcingSigner>::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, 42).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();
// Node B --> Node A: accept channel
- let accept_channel_msg = node_b_chan.accept_inbound_channel(0);
+ let accept_channel_msg = node_b_chan.accept_inbound_channel();
node_a_chan.accept_channel(&accept_channel_msg, &config.channel_handshake_limits, &channelmanager::provided_init_features(&config)).unwrap();
// Node A --> Node B: funding created
value: 10000000, script_pubkey: output_script.clone(),
}]};
let funding_outpoint = OutPoint{ txid: tx.txid(), index: 0 };
- let (mut node_a_chan, funding_created_msg) = node_a_chan.get_outbound_funding_created(tx.clone(), funding_outpoint, &&logger).map_err(|_| ()).unwrap();
+ let (mut node_a_chan, funding_created_msg) = node_a_chan.get_funding_created(tx.clone(), funding_outpoint, false, &&logger).map_err(|_| ()).unwrap();
let (mut node_b_chan, funding_signed_msg, _) = node_b_chan.funding_created(&funding_created_msg, best_block, &&keys_provider, &&logger).map_err(|_| ()).unwrap();
// Node B --> Node A: funding signed
// Now disconnect the two nodes and check that the commitment point in
// Node B's channel_reestablish message is sane.
- node_b_chan.remove_uncommitted_htlcs_and_mark_paused(&&logger);
+ assert!(node_b_chan.remove_uncommitted_htlcs_and_mark_paused(&&logger).is_ok());
let msg = node_b_chan.get_channel_reestablish(&&logger);
assert_eq!(msg.next_local_commitment_number, 1); // now called next_commitment_number
assert_eq!(msg.next_remote_commitment_number, 0); // now called next_revocation_number
// Check that the commitment point in Node A's channel_reestablish message
// is sane.
- node_a_chan.remove_uncommitted_htlcs_and_mark_paused(&&logger);
+ assert!(node_a_chan.remove_uncommitted_htlcs_and_mark_paused(&&logger).is_ok());
let msg = node_a_chan.get_channel_reestablish(&&logger);
assert_eq!(msg.next_local_commitment_number, 1); // now called next_commitment_number
assert_eq!(msg.next_remote_commitment_number, 0); // now called next_revocation_number
// Test that `OutboundV1Channel::new` creates a channel with the correct value for
// `holder_max_htlc_value_in_flight_msat`, when configured with a valid percentage value,
// which is set to the lower bound + 1 (2%) of the `channel_value`.
- let chan_1 = OutboundV1Channel::<EnforcingSigner>::new(&feeest, &&keys_provider, &&keys_provider, outbound_node_id, &channelmanager::provided_init_features(&config_2_percent), 10000000, 100000, 42, &config_2_percent, 0, 42).unwrap();
+ let chan_1 = OutboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider, outbound_node_id, &channelmanager::provided_init_features(&config_2_percent), 10000000, 100000, 42, &config_2_percent, 0, 42).unwrap();
let chan_1_value_msat = chan_1.context.channel_value_satoshis * 1000;
assert_eq!(chan_1.context.holder_max_htlc_value_in_flight_msat, (chan_1_value_msat as f64 * 0.02) as u64);
// Test with the upper bound - 1 of valid values (99%).
- let chan_2 = OutboundV1Channel::<EnforcingSigner>::new(&feeest, &&keys_provider, &&keys_provider, outbound_node_id, &channelmanager::provided_init_features(&config_99_percent), 10000000, 100000, 42, &config_99_percent, 0, 42).unwrap();
+ let chan_2 = OutboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider, outbound_node_id, &channelmanager::provided_init_features(&config_99_percent), 10000000, 100000, 42, &config_99_percent, 0, 42).unwrap();
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);
// 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,
// which is set to the lower bound - 1 (2%) of the `channel_value`.
- let chan_3 = InboundV1Channel::<EnforcingSigner>::new(&feeest, &&keys_provider, &&keys_provider, inbound_node_id, &channelmanager::provided_channel_type_features(&config_2_percent), &channelmanager::provided_init_features(&config_2_percent), &chan_1_open_channel_msg, 7, &config_2_percent, 0, &&logger, 42).unwrap();
+ let chan_3 = InboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider, inbound_node_id, &channelmanager::provided_channel_type_features(&config_2_percent), &channelmanager::provided_init_features(&config_2_percent), &chan_1_open_channel_msg, 7, &config_2_percent, 0, &&logger, /*is_0conf=*/false).unwrap();
let chan_3_value_msat = chan_3.context.channel_value_satoshis * 1000;
assert_eq!(chan_3.context.holder_max_htlc_value_in_flight_msat, (chan_3_value_msat as f64 * 0.02) as u64);
// Test with the upper bound - 1 of valid values (99%).
- let chan_4 = InboundV1Channel::<EnforcingSigner>::new(&feeest, &&keys_provider, &&keys_provider, inbound_node_id, &channelmanager::provided_channel_type_features(&config_99_percent), &channelmanager::provided_init_features(&config_99_percent), &chan_1_open_channel_msg, 7, &config_99_percent, 0, &&logger, 42).unwrap();
+ let chan_4 = InboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider, inbound_node_id, &channelmanager::provided_channel_type_features(&config_99_percent), &channelmanager::provided_init_features(&config_99_percent), &chan_1_open_channel_msg, 7, &config_99_percent, 0, &&logger, /*is_0conf=*/false).unwrap();
let chan_4_value_msat = chan_4.context.channel_value_satoshis * 1000;
assert_eq!(chan_4.context.holder_max_htlc_value_in_flight_msat, (chan_4_value_msat as f64 * 0.99) as u64);
// Test that `OutboundV1Channel::new` uses the lower bound of the configurable percentage values (1%)
// if `max_inbound_htlc_value_in_flight_percent_of_channel` is set to a value less than 1.
- let chan_5 = OutboundV1Channel::<EnforcingSigner>::new(&feeest, &&keys_provider, &&keys_provider, outbound_node_id, &channelmanager::provided_init_features(&config_0_percent), 10000000, 100000, 42, &config_0_percent, 0, 42).unwrap();
+ let chan_5 = OutboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider, outbound_node_id, &channelmanager::provided_init_features(&config_0_percent), 10000000, 100000, 42, &config_0_percent, 0, 42).unwrap();
let chan_5_value_msat = chan_5.context.channel_value_satoshis * 1000;
assert_eq!(chan_5.context.holder_max_htlc_value_in_flight_msat, (chan_5_value_msat as f64 * 0.01) as u64);
// Test that `OutboundV1Channel::new` uses the upper bound of the configurable percentage values
// (100%) if `max_inbound_htlc_value_in_flight_percent_of_channel` is set to a larger value
// than 100.
- let chan_6 = OutboundV1Channel::<EnforcingSigner>::new(&feeest, &&keys_provider, &&keys_provider, outbound_node_id, &channelmanager::provided_init_features(&config_101_percent), 10000000, 100000, 42, &config_101_percent, 0, 42).unwrap();
+ let chan_6 = OutboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider, outbound_node_id, &channelmanager::provided_init_features(&config_101_percent), 10000000, 100000, 42, &config_101_percent, 0, 42).unwrap();
let chan_6_value_msat = chan_6.context.channel_value_satoshis * 1000;
assert_eq!(chan_6.context.holder_max_htlc_value_in_flight_msat, chan_6_value_msat);
// Test that `InboundV1Channel::new` uses the lower bound of the configurable percentage values (1%)
// if `max_inbound_htlc_value_in_flight_percent_of_channel` is set to a value less than 1.
- let chan_7 = InboundV1Channel::<EnforcingSigner>::new(&feeest, &&keys_provider, &&keys_provider, inbound_node_id, &channelmanager::provided_channel_type_features(&config_0_percent), &channelmanager::provided_init_features(&config_0_percent), &chan_1_open_channel_msg, 7, &config_0_percent, 0, &&logger, 42).unwrap();
+ let chan_7 = InboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider, inbound_node_id, &channelmanager::provided_channel_type_features(&config_0_percent), &channelmanager::provided_init_features(&config_0_percent), &chan_1_open_channel_msg, 7, &config_0_percent, 0, &&logger, /*is_0conf=*/false).unwrap();
let chan_7_value_msat = chan_7.context.channel_value_satoshis * 1000;
assert_eq!(chan_7.context.holder_max_htlc_value_in_flight_msat, (chan_7_value_msat as f64 * 0.01) as u64);
// Test that `InboundV1Channel::new` uses the upper bound of the configurable percentage values
// (100%) if `max_inbound_htlc_value_in_flight_percent_of_channel` is set to a larger value
// than 100.
- let chan_8 = InboundV1Channel::<EnforcingSigner>::new(&feeest, &&keys_provider, &&keys_provider, inbound_node_id, &channelmanager::provided_channel_type_features(&config_101_percent), &channelmanager::provided_init_features(&config_101_percent), &chan_1_open_channel_msg, 7, &config_101_percent, 0, &&logger, 42).unwrap();
+ let chan_8 = InboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider, inbound_node_id, &channelmanager::provided_channel_type_features(&config_101_percent), &channelmanager::provided_init_features(&config_101_percent), &chan_1_open_channel_msg, 7, &config_101_percent, 0, &&logger, /*is_0conf=*/false).unwrap();
let chan_8_value_msat = chan_8.context.channel_value_satoshis * 1000;
assert_eq!(chan_8.context.holder_max_htlc_value_in_flight_msat, chan_8_value_msat);
}
let mut outbound_node_config = UserConfig::default();
outbound_node_config.channel_handshake_config.their_channel_reserve_proportional_millionths = (outbound_selected_channel_reserve_perc * 1_000_000.0) as u32;
- let chan = OutboundV1Channel::<EnforcingSigner>::new(&&fee_est, &&keys_provider, &&keys_provider, outbound_node_id, &channelmanager::provided_init_features(&outbound_node_config), channel_value_satoshis, 100_000, 42, &outbound_node_config, 0, 42).unwrap();
+ let chan = OutboundV1Channel::<&TestKeysInterface>::new(&&fee_est, &&keys_provider, &&keys_provider, outbound_node_id, &channelmanager::provided_init_features(&outbound_node_config), channel_value_satoshis, 100_000, 42, &outbound_node_config, 0, 42).unwrap();
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);
inbound_node_config.channel_handshake_config.their_channel_reserve_proportional_millionths = (inbound_selected_channel_reserve_perc * 1_000_000.0) as u32;
if outbound_selected_channel_reserve_perc + inbound_selected_channel_reserve_perc < 1.0 {
- let chan_inbound_node = InboundV1Channel::<EnforcingSigner>::new(&&fee_est, &&keys_provider, &&keys_provider, inbound_node_id, &channelmanager::provided_channel_type_features(&inbound_node_config), &channelmanager::provided_init_features(&outbound_node_config), &chan_open_channel_msg, 7, &inbound_node_config, 0, &&logger, 42).unwrap();
+ let chan_inbound_node = InboundV1Channel::<&TestKeysInterface>::new(&&fee_est, &&keys_provider, &&keys_provider, inbound_node_id, &channelmanager::provided_channel_type_features(&inbound_node_config), &channelmanager::provided_init_features(&outbound_node_config), &chan_open_channel_msg, 7, &inbound_node_config, 0, &&logger, /*is_0conf=*/false).unwrap();
let expected_inbound_selected_chan_reserve = cmp::max(MIN_THEIR_CHAN_RESERVE_SATOSHIS, (chan.context.channel_value_satoshis as f64 * inbound_selected_channel_reserve_perc) as u64);
assert_eq!(chan_inbound_node.context.counterparty_selected_channel_reserve_satoshis.unwrap(), expected_outbound_selected_chan_reserve);
} else {
// Channel Negotiations failed
- let result = InboundV1Channel::<EnforcingSigner>::new(&&fee_est, &&keys_provider, &&keys_provider, inbound_node_id, &channelmanager::provided_channel_type_features(&inbound_node_config), &channelmanager::provided_init_features(&outbound_node_config), &chan_open_channel_msg, 7, &inbound_node_config, 0, &&logger, 42);
+ let result = InboundV1Channel::<&TestKeysInterface>::new(&&fee_est, &&keys_provider, &&keys_provider, inbound_node_id, &channelmanager::provided_channel_type_features(&inbound_node_config), &channelmanager::provided_init_features(&outbound_node_config), &chan_open_channel_msg, 7, &inbound_node_config, 0, &&logger, /*is_0conf=*/false);
assert!(result.is_err());
}
}
// Create Node A's channel pointing to Node B's pubkey
let node_b_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
let config = UserConfig::default();
- let mut node_a_chan = OutboundV1Channel::<EnforcingSigner>::new(&feeest, &&keys_provider, &&keys_provider, node_b_node_id, &channelmanager::provided_init_features(&config), 10000000, 100000, 42, &config, 0, 42).unwrap();
+ let mut node_a_chan = OutboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider, node_b_node_id, &channelmanager::provided_init_features(&config), 10000000, 100000, 42, &config, 0, 42).unwrap();
// 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 node_b_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[7; 32]).unwrap());
- let mut node_b_chan = InboundV1Channel::<EnforcingSigner>::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, 42).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();
// Node B --> Node A: accept channel, explicitly setting B's dust limit.
- let mut accept_channel_msg = node_b_chan.accept_inbound_channel(0);
+ let mut accept_channel_msg = node_b_chan.accept_inbound_channel();
accept_channel_msg.dust_limit_satoshis = 546;
node_a_chan.accept_channel(&accept_channel_msg, &config.channel_handshake_limits, &channelmanager::provided_init_features(&config)).unwrap();
node_a_chan.context.holder_dust_limit_satoshis = 1560;
value: 10000000, script_pubkey: output_script.clone(),
}]};
let funding_outpoint = OutPoint{ txid: tx.txid(), index: 0 };
- let (mut node_a_chan, funding_created_msg) = node_a_chan.get_outbound_funding_created(tx.clone(), funding_outpoint, &&logger).map_err(|_| ()).unwrap();
+ let (mut node_a_chan, funding_created_msg) = node_a_chan.get_funding_created(tx.clone(), funding_outpoint, false, &&logger).map_err(|_| ()).unwrap();
let (_, funding_signed_msg, _) = node_b_chan.funding_created(&funding_created_msg, best_block, &&keys_provider, &&logger).map_err(|_| ()).unwrap();
// Node B --> Node A: funding signed
let counterparty_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
let mut config = UserConfig::default();
config.channel_handshake_config.announced_channel = false;
- let mut chan = OutboundV1Channel::<InMemorySigner>::new(&LowerBoundedFeeEstimator::new(&feeest), &&keys_provider, &&keys_provider, counterparty_node_id, &channelmanager::provided_init_features(&config), 10_000_000, 0, 42, &config, 0, 42).unwrap(); // Nothing uses their network key in this test
+ let mut chan = OutboundV1Channel::<&Keys>::new(&LowerBoundedFeeEstimator::new(&feeest), &&keys_provider, &&keys_provider, counterparty_node_id, &channelmanager::provided_init_features(&config), 10_000_000, 0, 42, &config, 0, 42).unwrap(); // Nothing uses their network key in this test
chan.context.holder_dust_limit_satoshis = 546;
chan.context.counterparty_selected_channel_reserve_satoshis = Some(0); // Filled in in accept_channel
// We can't just use build_holder_transaction_keys here as the per_commitment_secret is not
// derived from a commitment_seed, so instead we copy it here and call
// build_commitment_transaction.
- let delayed_payment_base = &chan.context.holder_signer.pubkeys().delayed_payment_basepoint;
+ let delayed_payment_base = &chan.context.holder_signer.as_ref().pubkeys().delayed_payment_basepoint;
let per_commitment_secret = SecretKey::from_slice(&hex::decode("1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100").unwrap()[..]).unwrap();
let per_commitment_point = PublicKey::from_secret_key(&secp_ctx, &per_commitment_secret);
- let htlc_basepoint = &chan.context.holder_signer.pubkeys().htlc_basepoint;
+ let htlc_basepoint = &chan.context.holder_signer.as_ref().pubkeys().htlc_basepoint;
let keys = TxCreationKeys::derive_new(&secp_ctx, &per_commitment_point, delayed_payment_base, htlc_basepoint, &counterparty_pubkeys.revocation_basepoint, &counterparty_pubkeys.htlc_basepoint);
macro_rules! test_commitment {
commitment_tx.clone(),
counterparty_signature,
counterparty_htlc_sigs,
- &chan.context.holder_signer.pubkeys().funding_pubkey,
+ &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 node_b_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
let config = UserConfig::default();
- let node_a_chan = OutboundV1Channel::<EnforcingSigner>::new(&feeest, &&keys_provider, &&keys_provider,
+ let node_a_chan = OutboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider,
node_b_node_id, &channelmanager::provided_init_features(&config), 10000000, 100000, 42, &config, 0, 42).unwrap();
let mut channel_type_features = ChannelTypeFeatures::only_static_remote_key();
let mut open_channel_msg = node_a_chan.get_open_channel(genesis_block(network).header.block_hash());
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::<EnforcingSigner>::new(&feeest, &&keys_provider, &&keys_provider,
+ let res = 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, 42);
+ &channelmanager::provided_init_features(&config), &open_channel_msg, 7, &config, 0, &&logger, /*is_0conf=*/false);
assert!(res.is_ok());
}
// It is not enough for just the initiator to signal `option_anchors_zero_fee_htlc_tx`, both
// need to signal it.
- let channel_a = OutboundV1Channel::<EnforcingSigner>::new(
+ let channel_a = OutboundV1Channel::<&TestKeysInterface>::new(
&fee_estimator, &&keys_provider, &&keys_provider, node_id_b,
&channelmanager::provided_init_features(&UserConfig::default()), 10000000, 100000, 42,
&config, 0, 42
expected_channel_type.set_static_remote_key_required();
expected_channel_type.set_anchors_zero_fee_htlc_tx_required();
- let channel_a = OutboundV1Channel::<EnforcingSigner>::new(
+ let channel_a = OutboundV1Channel::<&TestKeysInterface>::new(
&fee_estimator, &&keys_provider, &&keys_provider, node_id_b,
&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 channel_b = InboundV1Channel::<EnforcingSigner>::new(
+ 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),
- &open_channel_msg, 7, &config, 0, &&logger, 42
+ &open_channel_msg, 7, &config, 0, &&logger, /*is_0conf=*/false
).unwrap();
assert_eq!(channel_a.context.channel_type, expected_channel_type);
let raw_init_features = static_remote_key_required | simple_anchors_required;
let init_features_with_simple_anchors = InitFeatures::from_le_bytes(raw_init_features.to_le_bytes().to_vec());
- let channel_a = OutboundV1Channel::<EnforcingSigner>::new(
+ let channel_a = OutboundV1Channel::<&TestKeysInterface>::new(
&fee_estimator, &&keys_provider, &&keys_provider, node_id_b,
&channelmanager::provided_init_features(&config), 10000000, 100000, 42, &config, 0, 42
).unwrap();
// Since A supports both `static_remote_key` and `option_anchors`, but B only accepts
// `static_remote_key`, it will fail the channel.
- let channel_b = InboundV1Channel::<EnforcingSigner>::new(
+ let channel_b = InboundV1Channel::<&TestKeysInterface>::new(
&fee_estimator, &&keys_provider, &&keys_provider, node_id_a,
&channelmanager::provided_channel_type_features(&config), &init_features_with_simple_anchors,
- &open_channel_msg, 7, &config, 0, &&logger, 42
+ &open_channel_msg, 7, &config, 0, &&logger, /*is_0conf=*/false
);
assert!(channel_b.is_err());
}
// First, we'll try to open a channel between A and B where A requests a channel type for
// the original `option_anchors` feature (non zero fee htlc tx). This should be rejected by
// B as it's not supported by LDK.
- let channel_a = OutboundV1Channel::<EnforcingSigner>::new(
+ let channel_a = OutboundV1Channel::<&TestKeysInterface>::new(
&fee_estimator, &&keys_provider, &&keys_provider, node_id_b,
&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());
open_channel_msg.channel_type = Some(simple_anchors_channel_type.clone());
- let res = InboundV1Channel::<EnforcingSigner>::new(
+ let res = InboundV1Channel::<&TestKeysInterface>::new(
&fee_estimator, &&keys_provider, &&keys_provider, node_id_a,
&channelmanager::provided_channel_type_features(&config), &simple_anchors_init,
- &open_channel_msg, 7, &config, 0, &&logger, 42
+ &open_channel_msg, 7, &config, 0, &&logger, /*is_0conf=*/false
);
assert!(res.is_err());
// `anchors_zero_fee_htlc_tx`. B is malicious and tries to downgrade the channel type to the
// original `option_anchors` feature, which should be rejected by A as it's not supported by
// LDK.
- let mut channel_a = OutboundV1Channel::<EnforcingSigner>::new(
+ let mut channel_a = OutboundV1Channel::<&TestKeysInterface>::new(
&fee_estimator, &&keys_provider, &&keys_provider, node_id_b, &simple_anchors_init,
10000000, 100000, 42, &config, 0, 42
).unwrap();
let open_channel_msg = channel_a.get_open_channel(genesis_block(network).header.block_hash());
- let channel_b = InboundV1Channel::<EnforcingSigner>::new(
+ 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),
- &open_channel_msg, 7, &config, 0, &&logger, 42
+ &open_channel_msg, 7, &config, 0, &&logger, /*is_0conf=*/false
).unwrap();
let mut accept_channel_msg = channel_b.get_accept_channel_message();
);
assert!(res.is_err());
}
+
+ #[test]
+ fn test_waiting_for_batch() {
+ let feeest = LowerBoundedFeeEstimator::new(&TestFeeEstimator{fee_est: 15000});
+ let logger = test_utils::TestLogger::new();
+ let secp_ctx = Secp256k1::new();
+ 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 keys_provider = test_utils::TestKeysInterface::new(&seed, network);
+
+ let mut config = UserConfig::default();
+ // Set trust_own_funding_0conf while ensuring we don't send channel_ready for a
+ // channel in a batch before all channels are ready.
+ config.channel_handshake_limits.trust_own_funding_0conf = true;
+
+ // Create a channel from node a to node b that will be part of batch funding.
+ let node_b_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
+ let mut node_a_chan = OutboundV1Channel::<&TestKeysInterface>::new(
+ &feeest,
+ &&keys_provider,
+ &&keys_provider,
+ node_b_node_id,
+ &channelmanager::provided_init_features(&config),
+ 10000000,
+ 100000,
+ 42,
+ &config,
+ 0,
+ 42,
+ ).unwrap();
+
+ let open_channel_msg = node_a_chan.get_open_channel(genesis_block(network).header.block_hash());
+ 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,
+ true, // Allow node b to send a 0conf channel_ready.
+ ).unwrap();
+
+ let accept_channel_msg = node_b_chan.accept_inbound_channel();
+ node_a_chan.accept_channel(
+ &accept_channel_msg,
+ &config.channel_handshake_limits,
+ &channelmanager::provided_init_features(&config),
+ ).unwrap();
+
+ // Fund the channel with a batch funding transaction.
+ let output_script = node_a_chan.context.get_funding_redeemscript();
+ let tx = Transaction {
+ version: 1,
+ lock_time: PackedLockTime::ZERO,
+ input: Vec::new(),
+ output: vec![
+ TxOut {
+ value: 10000000, script_pubkey: output_script.clone(),
+ },
+ TxOut {
+ value: 10000000, script_pubkey: Builder::new().into_script(),
+ },
+ ]};
+ let funding_outpoint = OutPoint{ txid: tx.txid(), index: 0 };
+ let (mut node_a_chan, funding_created_msg) = node_a_chan.get_funding_created(
+ tx.clone(),
+ funding_outpoint,
+ true,
+ &&logger,
+ ).map_err(|_| ()).unwrap();
+ let (mut node_b_chan, funding_signed_msg, _) = node_b_chan.funding_created(
+ &funding_created_msg,
+ best_block,
+ &&keys_provider,
+ &&logger,
+ ).map_err(|_| ()).unwrap();
+ let node_b_updates = node_b_chan.monitor_updating_restored(
+ &&logger,
+ &&keys_provider,
+ chain_hash,
+ &config,
+ 0,
+ );
+
+ // Receive funding_signed, but the channel will be configured to hold sending channel_ready and
+ // broadcasting the funding transaction until the batch is ready.
+ let _ = node_a_chan.funding_signed(
+ &funding_signed_msg,
+ best_block,
+ &&keys_provider,
+ &&logger,
+ ).unwrap();
+ let node_a_updates = node_a_chan.monitor_updating_restored(
+ &&logger,
+ &&keys_provider,
+ chain_hash,
+ &config,
+ 0,
+ );
+ // Our channel_ready shouldn't be sent yet, even with trust_own_funding_0conf set,
+ // as the funding transaction depends on all channels in the batch becoming ready.
+ assert!(node_a_updates.channel_ready.is_none());
+ assert!(node_a_updates.funding_broadcastable.is_none());
+ assert_eq!(
+ node_a_chan.context.channel_state,
+ ChannelState::FundingSent as u32 |
+ ChannelState::WaitingForBatch as u32,
+ );
+
+ // It is possible to receive a 0conf channel_ready from the remote node.
+ node_a_chan.channel_ready(
+ &node_b_updates.channel_ready.unwrap(),
+ &&keys_provider,
+ chain_hash,
+ &config,
+ &best_block,
+ &&logger,
+ ).unwrap();
+ assert_eq!(
+ node_a_chan.context.channel_state,
+ ChannelState::FundingSent as u32 |
+ ChannelState::WaitingForBatch as u32 |
+ ChannelState::TheirChannelReady as u32,
+ );
+
+ // Clear the ChannelState::WaitingForBatch only when called by ChannelManager.
+ node_a_chan.set_batch_ready();
+ assert_eq!(
+ node_a_chan.context.channel_state,
+ ChannelState::FundingSent as u32 |
+ ChannelState::TheirChannelReady as u32,
+ );
+ assert!(node_a_chan.check_get_channel_ready(0).is_some());
+ }
}
--- /dev/null
+// This file is Copyright its original authors, visible in version control
+// history.
+//
+// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
+// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// You may not use this file except in accordance with one or both of these
+// licenses.
+
+//! ChannelId definition.
+
+use crate::ln::msgs::DecodeError;
+use crate::sign::EntropySource;
+use crate::util::ser::{Readable, Writeable, Writer};
+
+use bitcoin::hashes::hex::ToHex;
+
+use crate::io;
+use crate::prelude::*;
+use core::fmt;
+use core::ops::Deref;
+
+/// A unique 32-byte identifier for a channel.
+/// Depending on how the ID is generated, several varieties are distinguished
+/// (but all are stored as 32 bytes):
+/// _v1_ and _temporary_.
+/// A _v1_ channel ID is generated based on funding tx outpoint (txid & index).
+/// A _temporary_ ID is generated randomly.
+/// (Later revocation-point-based _v2_ is a possibility.)
+/// The variety (context) is not stored, it is relevant only at creation.
+///
+/// This is not exported to bindings users as we just use [u8; 32] directly.
+#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
+pub struct ChannelId(pub [u8; 32]);
+
+impl ChannelId {
+ /// Create _v1_ channel ID based on a funding TX ID and output index
+ pub fn v1_from_funding_txid(txid: &[u8; 32], output_index: u16) -> Self {
+ let mut res = [0; 32];
+ res[..].copy_from_slice(&txid[..]);
+ res[30] ^= ((output_index >> 8) & 0xff) as u8;
+ res[31] ^= ((output_index >> 0) & 0xff) as u8;
+ Self(res)
+ }
+
+ /// Create a _temporary_ channel ID randomly, based on an entropy source.
+ pub fn temporary_from_entropy_source<ES: Deref>(entropy_source: &ES) -> Self
+ where ES::Target: EntropySource {
+ Self(entropy_source.get_secure_random_bytes())
+ }
+
+ /// Generic constructor; create a new channel ID from the provided data.
+ /// Use a more specific `*_from_*` constructor when possible.
+ pub fn from_bytes(data: [u8; 32]) -> Self {
+ Self(data)
+ }
+
+ /// Create a channel ID consisting of all-zeros data (e.g. when uninitialized or a placeholder).
+ pub fn new_zero() -> Self {
+ Self([0; 32])
+ }
+
+ /// Check whether ID is consisting of all zeros (uninitialized)
+ pub fn is_zero(&self) -> bool {
+ self.0[..] == [0; 32]
+ }
+}
+
+impl Writeable for ChannelId {
+ fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
+ self.0.write(w)
+ }
+}
+
+impl Readable for ChannelId {
+ fn read<R: io::Read>(r: &mut R) -> Result<Self, DecodeError> {
+ let buf: [u8; 32] = Readable::read(r)?;
+ Ok(ChannelId(buf))
+ }
+}
+
+impl ToHex for ChannelId {
+ fn to_hex(&self) -> String {
+ self.0.to_hex()
+ }
+}
+
+impl fmt::Display for ChannelId {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ crate::util::logger::DebugBytes(&self.0).fmt(f)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::ln::ChannelId;
+ use crate::util::ser::{Readable, Writeable};
+ use crate::util::test_utils;
+ use bitcoin::hashes::hex::ToHex;
+ use crate::prelude::*;
+ use crate::io;
+
+ #[test]
+ fn test_channel_id_v1_from_funding_txid() {
+ let channel_id = ChannelId::v1_from_funding_txid(&[2; 32], 1);
+ assert_eq!(channel_id.to_hex(), "0202020202020202020202020202020202020202020202020202020202020203");
+ }
+
+ #[test]
+ fn test_channel_id_new_from_data() {
+ let data: [u8; 32] = [2; 32];
+ let channel_id = ChannelId::from_bytes(data.clone());
+ assert_eq!(channel_id.0, data);
+ }
+
+ #[test]
+ fn test_channel_id_equals() {
+ let channel_id11 = ChannelId::v1_from_funding_txid(&[2; 32], 2);
+ let channel_id12 = ChannelId::v1_from_funding_txid(&[2; 32], 2);
+ let channel_id21 = ChannelId::v1_from_funding_txid(&[2; 32], 42);
+ assert_eq!(channel_id11, channel_id12);
+ assert_ne!(channel_id11, channel_id21);
+ }
+
+ #[test]
+ fn test_channel_id_write_read() {
+ let data: [u8; 32] = [2; 32];
+ let channel_id = ChannelId::from_bytes(data.clone());
+
+ let mut w = test_utils::TestVecWriter(Vec::new());
+ channel_id.write(&mut w).unwrap();
+
+ let channel_id_2 = ChannelId::read(&mut io::Cursor::new(&w.0)).unwrap();
+ assert_eq!(channel_id_2, channel_id);
+ assert_eq!(channel_id_2.0, data);
+ }
+
+ #[test]
+ fn test_channel_id_display() {
+ let channel_id = ChannelId::v1_from_funding_txid(&[2; 32], 1);
+ assert_eq!(format!("{}", &channel_id), "0202020202020202020202020202020202020202020202020202020202020203");
+ }
+}
use crate::events::{Event, EventHandler, EventsProvider, MessageSendEvent, MessageSendEventsProvider, ClosureReason, HTLCDestination, PaymentFailureReason};
// Since this struct is returned in `list_channels` methods, expose it here in case users want to
// construct one themselves.
-use crate::ln::{inbound_payment, PaymentHash, PaymentPreimage, PaymentSecret};
-use crate::ln::channel::{Channel, ChannelContext, ChannelError, ChannelUpdateStatus, ShutdownResult, UpdateFulfillCommitFetch, OutboundV1Channel, InboundV1Channel};
+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};
#[cfg(any(feature = "_test_utils", test))]
-use crate::ln::features::InvoiceFeatures;
+use crate::ln::features::Bolt11InvoiceFeatures;
use crate::routing::gossip::NetworkGraph;
-use crate::routing::router::{BlindedTail, DefaultRouter, InFlightHtlcs, Path, Payee, PaymentParameters, Route, RouteHop, RouteParameters, Router};
+use crate::routing::router::{BlindedTail, DefaultRouter, InFlightHtlcs, Path, Payee, PaymentParameters, Route, RouteParameters, Router};
use crate::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringFeeParameters};
use crate::ln::msgs;
use crate::ln::onion_utils;
use crate::ln::msgs::{ChannelMessageHandler, DecodeError, LightningError};
#[cfg(test)]
use crate::ln::outbound_payment;
-use crate::ln::outbound_payment::{OutboundPayments, PaymentAttempts, PendingOutboundPayment};
+use crate::ln::outbound_payment::{OutboundPayments, PaymentAttempts, PendingOutboundPayment, SendAlongPathArgs};
use crate::ln::wire::Encode;
-use crate::sign::{EntropySource, KeysManager, NodeSigner, Recipient, SignerProvider, ChannelSigner, WriteableEcdsaChannelSigner};
+use crate::sign::{EntropySource, KeysManager, NodeSigner, Recipient, SignerProvider, WriteableEcdsaChannelSigner};
use crate::util::config::{UserConfig, ChannelConfig, ChannelConfigUpdate};
use crate::util::wakers::{Future, Notifier};
use crate::util::scid_utils::fake_scid;
use crate::util::logger::{Level, Logger};
use crate::util::errors::APIError;
-use alloc::collections::BTreeMap;
+use alloc::collections::{btree_map, BTreeMap};
use crate::io;
use crate::prelude::*;
use core::ops::Deref;
// Re-export this for use in the public API.
-pub use crate::ln::outbound_payment::{PaymentSendFailure, Retry, RetryableSendFailure, RecipientOnionFields};
+pub use crate::ln::outbound_payment::{PaymentSendFailure, ProbeSendFailure, Retry, RetryableSendFailure, RecipientOnionFields};
use crate::ln::script::ShutdownScript;
// We hold various information about HTLC relay in the HTLC objects in Channel itself:
payment_metadata: Option<Vec<u8>>,
incoming_cltv_expiry: u32, // Used to track when we should expire pending HTLCs that go unclaimed
phantom_shared_secret: Option<[u8; 32]>,
+ /// See [`RecipientOnionFields::custom_tlvs`] for more info.
+ custom_tlvs: Vec<(u64, Vec<u8>)>,
},
ReceiveKeysend {
/// This was added in 0.0.116 and will break deserialization on downgrades.
payment_preimage: PaymentPreimage,
payment_metadata: Option<Vec<u8>>,
incoming_cltv_expiry: u32, // Used to track when we should expire pending HTLCs that go unclaimed
+ /// See [`RecipientOnionFields::custom_tlvs`] for more info.
+ custom_tlvs: Vec<(u64, Vec<u8>)>,
},
}
}
/// Tracks the inbound corresponding to an outbound HTLC
-#[derive(Clone, Hash, PartialEq, Eq)]
+#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub(crate) struct HTLCPreviousHopData {
// Note that this may be an outbound SCID alias for the associated channel.
short_channel_id: u64,
+ user_channel_id: Option<u128>,
htlc_id: u64,
incoming_packet_shared_secret: [u8; 32],
phantom_shared_secret: Option<[u8; 32]>,
counterparty_skimmed_fee_msat: Option<u64>,
}
-/// A payment identifier used to uniquely identify a payment to LDK.
+impl From<&ClaimableHTLC> for events::ClaimedHTLC {
+ fn from(val: &ClaimableHTLC) -> Self {
+ events::ClaimedHTLC {
+ channel_id: val.prev_hop.outpoint.to_channel_id(),
+ user_channel_id: val.prev_hop.user_channel_id.unwrap_or(0),
+ cltv_expiry: val.cltv_expiry,
+ value_msat: val.value,
+ }
+ }
+}
+
+/// A user-provided identifier in [`ChannelManager::send_payment`] used to uniquely identify
+/// a payment and ensure idempotency in LDK.
///
/// This is not exported to bindings users as we just use [u8; 32] directly
#[derive(Hash, Copy, Clone, PartialEq, Eq, Debug)]
-pub struct PaymentId(pub [u8; 32]);
+pub struct PaymentId(pub [u8; Self::LENGTH]);
+
+impl PaymentId {
+ /// Number of bytes in the id.
+ pub const LENGTH: usize = 32;
+}
impl Writeable for PaymentId {
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
}
}
+impl core::fmt::Display for PaymentId {
+ fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
+ crate::util::logger::DebugBytes(&self.0).fmt(f)
+ }
+}
+
/// An identifier used to uniquely identify an intercepted HTLC to LDK.
///
/// This is not exported to bindings users as we just use [u8; 32] directly
}
}
-#[derive(Clone, Copy, PartialEq, Eq, Hash)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
/// Uniquely describes an HTLC by its source. Just the guaranteed-unique subset of [`HTLCSource`].
pub(crate) enum SentHTLCId {
PreviousHopData { short_channel_id: u64, htlc_id: u64 },
/// Tracks the inbound corresponding to an outbound HTLC
#[allow(clippy::derive_hash_xor_eq)] // Our Hash is faithful to the data, we just don't have SecretKey::hash
-#[derive(Clone, PartialEq, Eq)]
+#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum HTLCSource {
PreviousHopData(HTLCPreviousHopData),
OutboundRoute {
}
}
-struct ReceiveError {
+struct InboundOnionErr {
err_code: u16,
err_data: Vec<u8>,
msg: &'static str,
pub enum FailureCode {
/// We had a temporary error processing the payment. Useful if no other error codes fit
/// and you want to indicate that the payer may want to retry.
- TemporaryNodeFailure = 0x2000 | 2,
+ TemporaryNodeFailure,
/// We have a required feature which was not in this onion. For example, you may require
/// some additional metadata that was not provided with this payment.
- RequiredNodeFeatureMissing = 0x4000 | 0x2000 | 3,
+ RequiredNodeFeatureMissing,
/// You may wish to use this when a `payment_preimage` is unknown, or the CLTV expiry of
/// the HTLC is too close to the current block height for safe handling.
/// Using this failure code in [`ChannelManager::fail_htlc_backwards_with_reason`] is
/// equivalent to calling [`ChannelManager::fail_htlc_backwards`].
- IncorrectOrUnknownPaymentDetails = 0x4000 | 15,
+ IncorrectOrUnknownPaymentDetails,
+ /// We failed to process the payload after the onion was decrypted. You may wish to
+ /// use this when receiving custom HTLC TLVs with even type numbers that you don't recognize.
+ ///
+ /// If available, the tuple data may include the type number and byte offset in the
+ /// decrypted byte stream where the failure occurred.
+ InvalidOnionPayload(Option<(u64, u16)>),
+}
+
+impl Into<u16> for FailureCode {
+ fn into(self) -> u16 {
+ match self {
+ FailureCode::TemporaryNodeFailure => 0x2000 | 2,
+ FailureCode::RequiredNodeFeatureMissing => 0x4000 | 0x2000 | 3,
+ FailureCode::IncorrectOrUnknownPaymentDetails => 0x4000 | 15,
+ FailureCode::InvalidOnionPayload(_) => 0x4000 | 22,
+ }
+ }
}
/// Error type returned across the peer_state mutex boundary. When an Err is generated for a
struct MsgHandleErrInternal {
err: msgs::LightningError,
- chan_id: Option<([u8; 32], u128)>, // If Some a channel of ours has been closed
+ chan_id: Option<(ChannelId, u128)>, // If Some a channel of ours has been closed
shutdown_finish: Option<(ShutdownResult, Option<msgs::ChannelUpdate>)>,
+ channel_capacity: Option<u64>,
}
impl MsgHandleErrInternal {
#[inline]
- fn send_err_msg_no_close(err: String, channel_id: [u8; 32]) -> Self {
+ fn send_err_msg_no_close(err: String, channel_id: ChannelId) -> Self {
Self {
err: LightningError {
err: err.clone(),
},
chan_id: None,
shutdown_finish: None,
+ channel_capacity: None,
}
}
#[inline]
fn from_no_close(err: msgs::LightningError) -> Self {
- Self { err, chan_id: None, shutdown_finish: None }
+ Self { err, chan_id: None, shutdown_finish: None, channel_capacity: None }
}
#[inline]
- fn from_finish_shutdown(err: String, channel_id: [u8; 32], user_channel_id: u128, shutdown_res: ShutdownResult, channel_update: Option<msgs::ChannelUpdate>) -> Self {
+ 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 {
Self {
err: LightningError {
err: err.clone(),
},
chan_id: Some((channel_id, user_channel_id)),
shutdown_finish: Some((shutdown_res, channel_update)),
+ channel_capacity: Some(channel_capacity)
}
}
#[inline]
- fn from_chan_no_close(err: ChannelError, channel_id: [u8; 32]) -> Self {
+ fn from_chan_no_close(err: ChannelError, channel_id: ChannelId) -> Self {
Self {
err: match err {
ChannelError::Warn(msg) => LightningError {
},
chan_id: None,
shutdown_finish: None,
+ channel_capacity: None,
}
}
+
+ fn closes_channel(&self) -> bool {
+ self.chan_id.is_some()
+ }
}
/// We hold back HTLCs we intend to relay for a random interval greater than this (see
amount_msat: u64,
payment_purpose: events::PaymentPurpose,
receiver_node_id: PublicKey,
+ htlcs: Vec<events::ClaimedHTLC>,
+ sender_intended_value: Option<u64>,
}
impl_writeable_tlv_based!(ClaimingPayment, {
(0, amount_msat, required),
(2, payment_purpose, required),
(4, receiver_node_id, required),
+ (5, htlcs, optional_vec),
+ (7, sender_intended_value, option),
});
struct ClaimablePayment {
/// running normally, and specifically must be processed before any other non-background
/// [`ChannelMonitorUpdate`]s are applied.
enum BackgroundEvent {
- /// Handle a ChannelMonitorUpdate which closes the channel. This is only separated from
- /// [`Self::MonitorUpdateRegeneratedOnStartup`] as the maybe-non-closing variant needs a public
- /// key to handle channel resumption, whereas if the channel has been force-closed we do not
- /// need the counterparty node_id.
+ /// Handle a ChannelMonitorUpdate which closes the channel or for an already-closed channel.
+ /// This is only separated from [`Self::MonitorUpdateRegeneratedOnStartup`] as the
+ /// maybe-non-closing variant needs a public key to handle channel resumption, whereas if the
+ /// channel has been force-closed we do not need the counterparty node_id.
///
/// Note that any such events are lost on shutdown, so in general they must be updates which
/// are regenerated on startup.
- ClosingMonitorUpdateRegeneratedOnStartup((OutPoint, ChannelMonitorUpdate)),
+ ClosedMonitorUpdateRegeneratedOnStartup((OutPoint, ChannelMonitorUpdate)),
/// Handle a ChannelMonitorUpdate which may or may not close the channel and may unblock the
/// channel to continue normal operation.
///
/// In general this should be used rather than
- /// [`Self::ClosingMonitorUpdateRegeneratedOnStartup`], however in cases where the
+ /// [`Self::ClosedMonitorUpdateRegeneratedOnStartup`], however in cases where the
/// `counterparty_node_id` is not available as the channel has closed from a [`ChannelMonitor`]
/// error the other variant is acceptable.
///
funding_txo: OutPoint,
update: ChannelMonitorUpdate
},
+ /// Some [`ChannelMonitorUpdate`] (s) completed before we were serialized but we still have
+ /// them marked pending, thus we need to run any [`MonitorUpdateCompletionAction`] (s) pending
+ /// on a channel.
+ MonitorUpdatesComplete {
+ counterparty_node_id: PublicKey,
+ channel_id: ChannelId,
+ },
}
#[derive(Debug)]
/// durably to disk.
ForwardedPaymentInboundClaim {
/// The upstream channel ID (i.e. the inbound edge).
- channel_id: [u8; 32],
+ channel_id: ChannelId,
/// The HTLC ID on the inbound edge.
htlc_id: u64,
},
}
impl RAAMonitorUpdateBlockingAction {
- #[allow(unused)]
fn from_prev_hop_data(prev_hop: &HTLCPreviousHopData) -> Self {
Self::ForwardedPaymentInboundClaim {
channel_id: prev_hop.outpoint.to_channel_id(),
/// State we hold per-peer.
-pub(super) struct PeerState<Signer: ChannelSigner> {
- /// `channel_id` -> `Channel`.
- ///
- /// Holds all funded channels where the peer is the counterparty.
- pub(super) channel_by_id: HashMap<[u8; 32], Channel<Signer>>,
- /// `temporary_channel_id` -> `OutboundV1Channel`.
- ///
- /// Holds all outbound V1 channels where the peer is the counterparty. Once an outbound channel has
- /// been assigned a `channel_id`, the entry in this map is removed and one is created in
- /// `channel_by_id`.
- pub(super) outbound_v1_channel_by_id: HashMap<[u8; 32], OutboundV1Channel<Signer>>,
- /// `temporary_channel_id` -> `InboundV1Channel`.
- ///
- /// Holds all inbound V1 channels where the peer is the counterparty. Once an inbound channel has
- /// been assigned a `channel_id`, the entry in this map is removed and one is created in
- /// `channel_by_id`.
- pub(super) inbound_v1_channel_by_id: HashMap<[u8; 32], InboundV1Channel<Signer>>,
+pub(super) struct PeerState<SP: Deref> where SP::Target: SignerProvider {
+ /// `channel_id` -> `ChannelPhase`
+ ///
+ /// Holds all channels within corresponding `ChannelPhase`s where the peer is the counterparty.
+ pub(super) channel_by_id: HashMap<ChannelId, ChannelPhase<SP>>,
+ /// `temporary_channel_id` -> `InboundChannelRequest`.
+ ///
+ /// When manual channel acceptance is enabled, this holds all unaccepted inbound channels where
+ /// the peer is the counterparty. If the channel is accepted, then the entry in this table is
+ /// removed, and an InboundV1Channel is created and placed in the `inbound_v1_channel_by_id` table. If
+ /// the channel is rejected, then the entry is simply removed.
+ pub(super) inbound_channel_request_by_id: HashMap<ChannelId, InboundChannelRequest>,
/// The latest `InitFeatures` we heard from the peer.
latest_features: InitFeatures,
/// Messages to send to the peer - pushed to in the same lock that they are generated in (except
/// same `temporary_channel_id` (or final `channel_id` in the case of 0conf channels or prior
/// to funding appearing on-chain), the downstream `ChannelMonitor` set is required to ensure
/// duplicates do not occur, so such channels should fail without a monitor update completing.
- monitor_update_blocked_actions: BTreeMap<[u8; 32], Vec<MonitorUpdateCompletionAction>>,
+ monitor_update_blocked_actions: BTreeMap<ChannelId, Vec<MonitorUpdateCompletionAction>>,
/// If another channel's [`ChannelMonitorUpdate`] needs to complete before a channel we have
/// with this peer can complete an RAA [`ChannelMonitorUpdate`] (e.g. because the RAA update
/// will remove a preimage that needs to be durably in an upstream channel first), we put an
/// entry here to note that the channel with the key's ID is blocked on a set of actions.
- actions_blocking_raa_monitor_updates: BTreeMap<[u8; 32], Vec<RAAMonitorUpdateBlockingAction>>,
+ actions_blocking_raa_monitor_updates: BTreeMap<ChannelId, Vec<RAAMonitorUpdateBlockingAction>>,
/// The peer is currently connected (i.e. we've seen a
/// [`ChannelMessageHandler::peer_connected`] and no corresponding
/// [`ChannelMessageHandler::peer_disconnected`].
is_connected: bool,
}
-impl <Signer: ChannelSigner> PeerState<Signer> {
+impl <SP: Deref> PeerState<SP> where SP::Target: SignerProvider {
/// Indicates that a peer meets the criteria where we're ok to remove it from our storage.
/// If true is passed for `require_disconnected`, the function will return false if we haven't
/// disconnected from the node already, ie. `PeerState::is_connected` is set to `true`.
if require_disconnected && self.is_connected {
return false
}
- self.channel_by_id.is_empty() && self.monitor_update_blocked_actions.is_empty()
+ self.channel_by_id.iter().filter(|(_, phase)| matches!(phase, ChannelPhase::Funded(_))).count() == 0
+ && self.monitor_update_blocked_actions.is_empty()
&& self.in_flight_monitor_updates.is_empty()
}
- // Returns a count of all channels we have with this peer, including pending channels.
+ // Returns a count of all channels we have with this peer, including unfunded channels.
fn total_channel_count(&self) -> usize {
- self.channel_by_id.len() +
- self.outbound_v1_channel_by_id.len() +
- self.inbound_v1_channel_by_id.len()
+ self.channel_by_id.len() + self.inbound_channel_request_by_id.len()
}
// Returns a bool indicating if the given `channel_id` matches a channel we have with this peer.
- fn has_channel(&self, channel_id: &[u8; 32]) -> bool {
+ fn has_channel(&self, channel_id: &ChannelId) -> bool {
self.channel_by_id.contains_key(channel_id) ||
- self.outbound_v1_channel_by_id.contains_key(channel_id) ||
- self.inbound_v1_channel_by_id.contains_key(channel_id)
+ self.inbound_channel_request_by_id.contains_key(channel_id)
}
}
+/// A not-yet-accepted inbound (from counterparty) channel. Once
+/// accepted, the parameters will be used to construct a channel.
+pub(super) struct InboundChannelRequest {
+ /// The original OpenChannel message.
+ pub open_channel_msg: msgs::OpenChannel,
+ /// The number of ticks remaining before the request expires.
+ pub ticks_remaining: i32,
+}
+
+/// The number of ticks that may elapse while we're waiting for an unaccepted inbound channel to be
+/// accepted. An unaccepted channel that exceeds this limit will be abandoned.
+const UNACCEPTED_INBOUND_CHANNEL_AGE_LIMIT_TICKS: i32 = 2;
+
/// Stores a PaymentSecret and any other data we may need to validate an inbound payment is
/// actually ours and not some duplicate HTLC sent to us by a node along the route.
///
&'g L
>;
-macro_rules! define_test_pub_trait { ($vis: vis) => {
-/// A trivial trait which describes any [`ChannelManager`] used in testing.
-$vis trait AChannelManager {
+/// A trivial trait which describes any [`ChannelManager`].
+pub trait AChannelManager {
+ /// A type implementing [`chain::Watch`].
type Watch: chain::Watch<Self::Signer> + ?Sized;
+ /// A type that may be dereferenced to [`Self::Watch`].
type M: Deref<Target = Self::Watch>;
+ /// A type implementing [`BroadcasterInterface`].
type Broadcaster: BroadcasterInterface + ?Sized;
+ /// A type that may be dereferenced to [`Self::Broadcaster`].
type T: Deref<Target = Self::Broadcaster>;
+ /// A type implementing [`EntropySource`].
type EntropySource: EntropySource + ?Sized;
+ /// A type that may be dereferenced to [`Self::EntropySource`].
type ES: Deref<Target = Self::EntropySource>;
+ /// A type implementing [`NodeSigner`].
type NodeSigner: NodeSigner + ?Sized;
+ /// A type that may be dereferenced to [`Self::NodeSigner`].
type NS: Deref<Target = Self::NodeSigner>;
+ /// A type implementing [`WriteableEcdsaChannelSigner`].
type Signer: WriteableEcdsaChannelSigner + Sized;
+ /// A type implementing [`SignerProvider`] for [`Self::Signer`].
type SignerProvider: SignerProvider<Signer = Self::Signer> + ?Sized;
+ /// A type that may be dereferenced to [`Self::SignerProvider`].
type SP: Deref<Target = Self::SignerProvider>;
+ /// A type implementing [`FeeEstimator`].
type FeeEstimator: FeeEstimator + ?Sized;
+ /// A type that may be dereferenced to [`Self::FeeEstimator`].
type F: Deref<Target = Self::FeeEstimator>;
+ /// A type implementing [`Router`].
type Router: Router + ?Sized;
+ /// A type that may be dereferenced to [`Self::Router`].
type R: Deref<Target = Self::Router>;
+ /// A type implementing [`Logger`].
type Logger: Logger + ?Sized;
+ /// A type that may be dereferenced to [`Self::Logger`].
type L: Deref<Target = Self::Logger>;
+ /// Returns a reference to the actual [`ChannelManager`] object.
fn get_cm(&self) -> &ChannelManager<Self::M, Self::T, Self::ES, Self::NS, Self::SP, Self::F, Self::R, Self::L>;
}
-} }
-#[cfg(any(test, feature = "_test_utils"))]
-define_test_pub_trait!(pub);
-#[cfg(not(any(test, feature = "_test_utils")))]
-define_test_pub_trait!(pub(crate));
+
impl<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref> AChannelManager
for ChannelManager<M, T, ES, NS, SP, F, R, L>
where
/// called [`funding_transaction_generated`] for outbound channels) being closed.
///
/// Note that you can be a bit lazier about writing out `ChannelManager` than you can be with
-/// [`ChannelMonitor`]. With [`ChannelMonitor`] you MUST write each monitor update out to disk before
-/// returning from [`chain::Watch::watch_channel`]/[`update_channel`], with ChannelManagers, writing updates
-/// happens out-of-band (and will prevent any other `ChannelManager` operations from occurring during
-/// the serialization process). If the deserialized version is out-of-date compared to the
-/// [`ChannelMonitor`] passed by reference to [`read`], those channels will be force-closed based on the
-/// `ChannelMonitor` state and no funds will be lost (mod on-chain transaction fees).
+/// [`ChannelMonitor`]. With [`ChannelMonitor`] you MUST durably write each
+/// [`ChannelMonitorUpdate`] before returning from
+/// [`chain::Watch::watch_channel`]/[`update_channel`] or before completing async writes. With
+/// `ChannelManager`s, writing updates happens out-of-band (and will prevent any other
+/// `ChannelManager` operations from occurring during the serialization process). If the
+/// deserialized version is out-of-date compared to the [`ChannelMonitor`] passed by reference to
+/// [`read`], those channels will be force-closed based on the `ChannelMonitor` state and no funds
+/// will be lost (modulo on-chain transaction fees).
///
/// Note that the deserializer is only implemented for `(`[`BlockHash`]`, `[`ChannelManager`]`)`, which
/// tells you the last block hash which was connected. You should get the best block tip before using the manager.
/// required to access the channel with the `counterparty_node_id`.
///
/// See `ChannelManager` struct-level documentation for lock order requirements.
- id_to_peer: Mutex<HashMap<[u8; 32], PublicKey>>,
+ id_to_peer: Mutex<HashMap<ChannelId, PublicKey>>,
/// SCIDs (and outbound SCID aliases) -> `counterparty_node_id`s and `channel_id`s.
///
///
/// See `ChannelManager` struct-level documentation for lock order requirements.
#[cfg(test)]
- pub(super) short_to_chan_info: FairRwLock<HashMap<u64, (PublicKey, [u8; 32])>>,
+ pub(super) short_to_chan_info: FairRwLock<HashMap<u64, (PublicKey, ChannelId)>>,
#[cfg(not(test))]
- short_to_chan_info: FairRwLock<HashMap<u64, (PublicKey, [u8; 32])>>,
+ short_to_chan_info: FairRwLock<HashMap<u64, (PublicKey, ChannelId)>>,
our_network_pubkey: PublicKey,
///
/// See `ChannelManager` struct-level documentation for lock order requirements.
#[cfg(not(any(test, feature = "_test_utils")))]
- per_peer_state: FairRwLock<HashMap<PublicKey, Mutex<PeerState<<SP::Target as SignerProvider>::Signer>>>>,
+ per_peer_state: FairRwLock<HashMap<PublicKey, Mutex<PeerState<SP>>>>,
#[cfg(any(test, feature = "_test_utils"))]
- pub(super) per_peer_state: FairRwLock<HashMap<PublicKey, Mutex<PeerState<<SP::Target as SignerProvider>::Signer>>>>,
+ pub(super) per_peer_state: FairRwLock<HashMap<PublicKey, Mutex<PeerState<SP>>>>,
/// The set of events which we need to give to the user to handle. In some cases an event may
/// require some further action after the user handles it (currently only blocking a monitor
/// could be in the middle of being processed without the direct mutex held.
///
/// See `ChannelManager` struct-level documentation for lock order requirements.
+ #[cfg(not(any(test, feature = "_test_utils")))]
pending_events: Mutex<VecDeque<(events::Event, Option<EventCompletionAction>)>>,
+ #[cfg(any(test, feature = "_test_utils"))]
+ pub(crate) pending_events: Mutex<VecDeque<(events::Event, Option<EventCompletionAction>)>>,
+
/// A simple atomic flag to ensure only one task at a time can be processing events asynchronously.
pending_events_processor: AtomicBool,
/// `PersistenceNotifierGuard::notify_on_drop(..)` and pass the lock to it, to ensure the
/// Notifier the lock contains sends out a notification when the lock is released.
total_consistency_lock: RwLock<()>,
+ /// Tracks the progress of channels going through batch funding by whether funding_signed was
+ /// received and the monitor has been persisted.
+ ///
+ /// This information does not need to be persisted as funding nodes can forget
+ /// unfunded channels upon disconnection.
+ funding_batch_states: Mutex<BTreeMap<Txid, Vec<(ChannelId, PublicKey, bool)>>>,
- #[cfg(debug_assertions)]
background_events_processed_since_startup: AtomicBool,
- persistence_notifier: Notifier,
+ event_persist_notifier: Notifier,
+ needs_persist_flag: AtomicBool,
entropy_source: ES,
node_signer: NS,
#[must_use]
enum NotifyOption {
DoPersist,
- SkipPersist,
+ SkipPersistHandleEvents,
+ SkipPersistNoEvents,
}
/// Whenever we release the `ChannelManager`'s `total_consistency_lock`, from read mode, it is
/// We allow callers to either always notify by constructing with `notify_on_drop` or choose to
/// notify or not based on whether relevant changes have been made, providing a closure to
/// `optionally_notify` which returns a `NotifyOption`.
-struct PersistenceNotifierGuard<'a, F: Fn() -> NotifyOption> {
- persistence_notifier: &'a Notifier,
+struct PersistenceNotifierGuard<'a, F: FnMut() -> NotifyOption> {
+ event_persist_notifier: &'a Notifier,
+ needs_persist_flag: &'a AtomicBool,
should_persist: F,
// We hold onto this result so the lock doesn't get released immediately.
_read_guard: RwLockReadGuard<'a, ()>,
}
impl<'a> PersistenceNotifierGuard<'a, fn() -> NotifyOption> { // We don't care what the concrete F is here, it's unused
- fn notify_on_drop<C: AChannelManager>(cm: &'a C) -> PersistenceNotifierGuard<'a, impl Fn() -> NotifyOption> {
+ /// Notifies any waiters and indicates that we need to persist, in addition to possibly having
+ /// events to handle.
+ ///
+ /// This must always be called if the changes included a `ChannelMonitorUpdate`, as well as in
+ /// other cases where losing the changes on restart may result in a force-close or otherwise
+ /// isn't ideal.
+ fn notify_on_drop<C: AChannelManager>(cm: &'a C) -> PersistenceNotifierGuard<'a, impl FnMut() -> NotifyOption> {
+ Self::optionally_notify(cm, || -> NotifyOption { NotifyOption::DoPersist })
+ }
+
+ fn optionally_notify<F: FnMut() -> NotifyOption, C: AChannelManager>(cm: &'a C, mut persist_check: F)
+ -> PersistenceNotifierGuard<'a, impl FnMut() -> NotifyOption> {
let read_guard = cm.get_cm().total_consistency_lock.read().unwrap();
- let _ = cm.get_cm().process_background_events(); // We always persist
+ let force_notify = cm.get_cm().process_background_events();
PersistenceNotifierGuard {
- persistence_notifier: &cm.get_cm().persistence_notifier,
- should_persist: || -> NotifyOption { NotifyOption::DoPersist },
+ event_persist_notifier: &cm.get_cm().event_persist_notifier,
+ needs_persist_flag: &cm.get_cm().needs_persist_flag,
+ should_persist: move || {
+ // Pick the "most" action between `persist_check` and the background events
+ // processing and return that.
+ let notify = persist_check();
+ match (notify, force_notify) {
+ (NotifyOption::DoPersist, _) => NotifyOption::DoPersist,
+ (_, NotifyOption::DoPersist) => NotifyOption::DoPersist,
+ (NotifyOption::SkipPersistHandleEvents, _) => NotifyOption::SkipPersistHandleEvents,
+ (_, NotifyOption::SkipPersistHandleEvents) => NotifyOption::SkipPersistHandleEvents,
+ _ => NotifyOption::SkipPersistNoEvents,
+ }
+ },
_read_guard: read_guard,
}
-
}
/// Note that if any [`ChannelMonitorUpdate`]s are possibly generated,
- /// [`ChannelManager::process_background_events`] MUST be called first.
- fn optionally_notify<F: Fn() -> NotifyOption>(lock: &'a RwLock<()>, notifier: &'a Notifier, persist_check: F) -> PersistenceNotifierGuard<'a, F> {
- let read_guard = lock.read().unwrap();
+ /// [`ChannelManager::process_background_events`] MUST be called first (or
+ /// [`Self::optionally_notify`] used).
+ fn optionally_notify_skipping_background_events<F: Fn() -> NotifyOption, C: AChannelManager>
+ (cm: &'a C, persist_check: F) -> PersistenceNotifierGuard<'a, F> {
+ let read_guard = cm.get_cm().total_consistency_lock.read().unwrap();
PersistenceNotifierGuard {
- persistence_notifier: notifier,
+ event_persist_notifier: &cm.get_cm().event_persist_notifier,
+ needs_persist_flag: &cm.get_cm().needs_persist_flag,
should_persist: persist_check,
_read_guard: read_guard,
}
}
}
-impl<'a, F: Fn() -> NotifyOption> Drop for PersistenceNotifierGuard<'a, F> {
+impl<'a, F: FnMut() -> NotifyOption> Drop for PersistenceNotifierGuard<'a, F> {
fn drop(&mut self) {
- if (self.should_persist)() == NotifyOption::DoPersist {
- self.persistence_notifier.notify();
+ match (self.should_persist)() {
+ NotifyOption::DoPersist => {
+ self.needs_persist_flag.store(true, Ordering::Release);
+ self.event_persist_notifier.notify()
+ },
+ NotifyOption::SkipPersistHandleEvents =>
+ self.event_persist_notifier.notify(),
+ NotifyOption::SkipPersistNoEvents => {},
}
}
}
/// The number of ticks of [`ChannelManager::timer_tick_occurred`] until expiry of incomplete MPPs
pub(crate) const MPP_TIMEOUT_TICKS: u8 = 3;
-/// The number of ticks of [`ChannelManager::timer_tick_occurred`] until we time-out the
-/// idempotency of payments by [`PaymentId`]. See
-/// [`OutboundPayments::remove_stale_resolved_payments`].
-pub(crate) const IDEMPOTENCY_TIMEOUT_TICKS: u8 = 7;
-
/// The number of ticks of [`ChannelManager::timer_tick_occurred`] where a peer is disconnected
/// until we mark the channel disabled and gossip the update.
pub(crate) const DISABLE_GOSSIP_TICKS: u8 = 10;
}
/// Details of a channel, as returned by [`ChannelManager::list_channels`] and [`ChannelManager::list_usable_channels`]
+///
+/// Balances of a channel are available through [`ChainMonitor::get_claimable_balances`] and
+/// [`ChannelMonitor::get_claimable_balances`], calculated with respect to the corresponding on-chain
+/// transactions.
+///
+/// [`ChainMonitor::get_claimable_balances`]: crate::chain::chainmonitor::ChainMonitor::get_claimable_balances
#[derive(Clone, Debug, PartialEq)]
pub struct ChannelDetails {
/// The channel's ID (prior to funding transaction generation, this is a random 32 bytes,
/// thereafter this is the txid of the funding transaction xor the funding transaction output).
/// Note that this means this value is *not* persistent - it can change once during the
/// lifetime of the channel.
- pub channel_id: [u8; 32],
+ pub channel_id: ChannelId,
/// Parameters which apply to our counterparty. See individual fields for more information.
pub counterparty: ChannelCounterparty,
/// The Channel's funding transaction output, if we've negotiated the funding transaction with
///
/// [`outbound_capacity_msat`]: ChannelDetails::outbound_capacity_msat
pub unspendable_punishment_reserve: Option<u64>,
- /// The `user_channel_id` passed in to create_channel, or a random value if the channel was
- /// inbound. This may be zero for inbound channels serialized with LDK versions prior to
- /// 0.0.113.
+ /// The `user_channel_id` value passed in to [`ChannelManager::create_channel`] for outbound
+ /// channels, or to [`ChannelManager::accept_inbound_channel`] for inbound channels if
+ /// [`UserConfig::manually_accept_inbound_channels`] config flag is set to true. Otherwise
+ /// `user_channel_id` will be randomized for an inbound channel. This may be zero for objects
+ /// serialized with LDK versions prior to 0.0.113.
+ ///
+ /// [`ChannelManager::create_channel`]: crate::ln::channelmanager::ChannelManager::create_channel
+ /// [`ChannelManager::accept_inbound_channel`]: crate::ln::channelmanager::ChannelManager::accept_inbound_channel
+ /// [`UserConfig::manually_accept_inbound_channels`]: crate::util::config::UserConfig::manually_accept_inbound_channels
pub user_channel_id: u128,
/// The currently negotiated fee rate denominated in satoshi per 1000 weight units,
/// which is applied to commitment and HTLC transactions.
///
/// This value will be `None` for objects serialized with LDK versions prior to 0.0.115.
pub feerate_sat_per_1000_weight: Option<u32>,
- /// Our total balance. This is the amount we would get if we close the channel.
- /// This value is not exact. Due to various in-flight changes and feerate changes, exactly this
- /// amount is not likely to be recoverable on close.
- ///
- /// This does not include any pending HTLCs which are not yet fully resolved (and, thus, whose
- /// balance is not available for inclusion in new outbound HTLCs). This further does not include
- /// any pending outgoing HTLCs which are awaiting some other resolution to be sent.
- /// This does not consider any on-chain fees.
- ///
- /// See also [`ChannelDetails::outbound_capacity_msat`]
- pub balance_msat: u64,
/// The available outbound capacity for sending HTLCs to the remote peer. This does not include
/// any pending HTLCs which are not yet fully resolved (and, thus, whose balance is not
/// available for inclusion in new outbound HTLCs). This further does not include any pending
/// outgoing HTLCs which are awaiting some other resolution to be sent.
///
- /// See also [`ChannelDetails::balance_msat`]
- ///
/// This value is not exact. Due to various in-flight changes, feerate changes, and our
/// conflict-avoidance policy, exactly this amount is not likely to be spendable. However, we
/// should be able to spend nearly this amount.
/// the current state and per-HTLC limit(s). This is intended for use when routing, allowing us
/// to use a limit as close as possible to the HTLC limit we can currently send.
///
- /// See also [`ChannelDetails::next_outbound_htlc_minimum_msat`],
- /// [`ChannelDetails::balance_msat`], and [`ChannelDetails::outbound_capacity_msat`].
+ /// See also [`ChannelDetails::next_outbound_htlc_minimum_msat`] and
+ /// [`ChannelDetails::outbound_capacity_msat`].
pub next_outbound_htlc_limit_msat: u64,
/// The minimum value for sending a single HTLC to the remote peer. This is the equivalent of
/// [`ChannelDetails::next_outbound_htlc_limit_msat`] but represents a lower-bound, rather than
self.short_channel_id.or(self.outbound_scid_alias)
}
- fn from_channel_context<Signer: WriteableEcdsaChannelSigner>(context: &ChannelContext<Signer>,
- best_block_height: u32, latest_features: InitFeatures) -> Self {
-
- let balance = context.get_available_balances();
+ fn from_channel_context<SP: Deref, F: Deref>(
+ context: &ChannelContext<SP>, best_block_height: u32, latest_features: InitFeatures,
+ fee_estimator: &LowerBoundedFeeEstimator<F>
+ ) -> Self
+ where
+ SP::Target: SignerProvider,
+ F::Target: FeeEstimator
+ {
+ let balance = context.get_available_balances(fee_estimator);
let (to_remote_reserve_satoshis, to_self_reserve_satoshis) =
context.get_holder_counterparty_selected_channel_reserve_satoshis();
ChannelDetails {
channel_value_satoshis: context.get_value_satoshis(),
feerate_sat_per_1000_weight: Some(context.get_feerate_sat_per_1000_weight()),
unspendable_punishment_reserve: to_self_reserve_satoshis,
- balance_msat: balance.balance_msat,
inbound_capacity_msat: balance.inbound_capacity_msat,
outbound_capacity_msat: balance.outbound_capacity_msat,
next_outbound_htlc_limit_msat: balance.next_outbound_htlc_limit_msat,
/// These include payments that have yet to find a successful path, or have unresolved HTLCs.
#[derive(Debug, PartialEq)]
pub enum RecentPaymentDetails {
+ /// When an invoice was requested and thus a payment has not yet been sent.
+ AwaitingInvoice {
+ /// A user-provided identifier in [`ChannelManager::send_payment`] used to uniquely identify
+ /// a payment and ensure idempotency in LDK.
+ payment_id: PaymentId,
+ },
/// When a payment is still being sent and awaiting successful delivery.
Pending {
+ /// A user-provided identifier in [`ChannelManager::send_payment`] used to uniquely identify
+ /// a payment and ensure idempotency in LDK.
+ payment_id: PaymentId,
/// Hash of the payment that is currently being sent but has yet to be fulfilled or
/// abandoned.
payment_hash: PaymentHash,
/// been resolved. Upon receiving [`Event::PaymentSent`], we delay for a few minutes before the
/// payment is removed from tracking.
Fulfilled {
+ /// A user-provided identifier in [`ChannelManager::send_payment`] used to uniquely identify
+ /// a payment and ensure idempotency in LDK.
+ payment_id: PaymentId,
/// Hash of the payment that was claimed. `None` for serializations of [`ChannelManager`]
/// made before LDK version 0.0.104.
payment_hash: Option<PaymentHash>,
/// abandoned via [`ChannelManager::abandon_payment`], it is marked as abandoned until all
/// pending HTLCs for this payment resolve and an [`Event::PaymentFailed`] is generated.
Abandoned {
+ /// A user-provided identifier in [`ChannelManager::send_payment`] used to uniquely identify
+ /// a payment and ensure idempotency in LDK.
+ payment_id: PaymentId,
/// Hash of the payment that we have given up trying to send.
payment_hash: PaymentHash,
},
match $internal {
Ok(msg) => Ok(msg),
- Err(MsgHandleErrInternal { err, chan_id, shutdown_finish }) => {
+ Err(MsgHandleErrInternal { err, chan_id, shutdown_finish, channel_capacity }) => {
let mut msg_events = Vec::with_capacity(2);
if let Some((shutdown_res, update_option)) = shutdown_finish {
- $self.finish_force_close_channel(shutdown_res);
+ $self.finish_close_channel(shutdown_res);
if let Some(update) = update_option {
msg_events.push(events::MessageSendEvent::BroadcastChannelUpdate {
msg: update
if let Some((channel_id, user_channel_id)) = chan_id {
$self.pending_events.lock().unwrap().push_back((events::Event::ChannelClosed {
channel_id, user_channel_id,
- reason: ClosureReason::ProcessingError { err: err.err.clone() }
+ reason: ClosureReason::ProcessingError { err: err.err.clone() },
+ counterparty_node_id: Some($counterparty_node_id),
+ channel_capacity_sats: channel_capacity,
}, None));
}
}
}
/// Returns (boolean indicating if we should remove the Channel object from memory, a mapped error)
-macro_rules! convert_chan_err {
- ($self: ident, $err: expr, $channel: expr, $channel_id: expr) => {
+macro_rules! convert_chan_phase_err {
+ ($self: ident, $err: expr, $channel: expr, $channel_id: expr, MANUAL_CHANNEL_UPDATE, $channel_update: expr) => {
match $err {
ChannelError::Warn(msg) => {
- (false, MsgHandleErrInternal::from_chan_no_close(ChannelError::Warn(msg), $channel_id.clone()))
+ (false, MsgHandleErrInternal::from_chan_no_close(ChannelError::Warn(msg), *$channel_id))
},
ChannelError::Ignore(msg) => {
- (false, MsgHandleErrInternal::from_chan_no_close(ChannelError::Ignore(msg), $channel_id.clone()))
+ (false, MsgHandleErrInternal::from_chan_no_close(ChannelError::Ignore(msg), *$channel_id))
},
ChannelError::Close(msg) => {
- log_error!($self.logger, "Closing channel {} due to close-required error: {}", log_bytes!($channel_id[..]), msg);
- update_maps_on_chan_removal!($self, &$channel.context);
+ log_error!($self.logger, "Closing channel {} due to close-required error: {}", $channel_id, msg);
+ update_maps_on_chan_removal!($self, $channel.context);
let shutdown_res = $channel.context.force_shutdown(true);
- (true, MsgHandleErrInternal::from_finish_shutdown(msg, *$channel_id, $channel.context.get_user_id(),
- shutdown_res, $self.get_channel_update_for_broadcast(&$channel).ok()))
+ let user_id = $channel.context.get_user_id();
+ let channel_capacity_satoshis = $channel.context.get_value_satoshis();
+
+ (true, MsgHandleErrInternal::from_finish_shutdown(msg, *$channel_id, user_id,
+ shutdown_res, $channel_update, channel_capacity_satoshis))
},
}
};
- ($self: ident, $err: expr, $channel_context: expr, $channel_id: expr, PREFUNDED) => {
- match $err {
- // We should only ever have `ChannelError::Close` when prefunded channels error.
- // In any case, just close the channel.
- ChannelError::Warn(msg) | ChannelError::Ignore(msg) | ChannelError::Close(msg) => {
- log_error!($self.logger, "Closing prefunded channel {} due to an error: {}", log_bytes!($channel_id[..]), msg);
- update_maps_on_chan_removal!($self, &$channel_context);
- let shutdown_res = $channel_context.force_shutdown(false);
- (true, MsgHandleErrInternal::from_finish_shutdown(msg, *$channel_id, $channel_context.get_user_id(),
- shutdown_res, None))
+ ($self: ident, $err: expr, $channel: expr, $channel_id: expr, FUNDED_CHANNEL) => {
+ convert_chan_phase_err!($self, $err, $channel, $channel_id, MANUAL_CHANNEL_UPDATE, { $self.get_channel_update_for_broadcast($channel).ok() })
+ };
+ ($self: ident, $err: expr, $channel: expr, $channel_id: expr, UNFUNDED_CHANNEL) => {
+ convert_chan_phase_err!($self, $err, $channel, $channel_id, MANUAL_CHANNEL_UPDATE, None)
+ };
+ ($self: ident, $err: expr, $channel_phase: expr, $channel_id: expr) => {
+ match $channel_phase {
+ ChannelPhase::Funded(channel) => {
+ convert_chan_phase_err!($self, $err, channel, $channel_id, FUNDED_CHANNEL)
+ },
+ ChannelPhase::UnfundedOutboundV1(channel) => {
+ convert_chan_phase_err!($self, $err, channel, $channel_id, UNFUNDED_CHANNEL)
+ },
+ ChannelPhase::UnfundedInboundV1(channel) => {
+ convert_chan_phase_err!($self, $err, channel, $channel_id, UNFUNDED_CHANNEL)
},
}
- }
+ };
}
-macro_rules! break_chan_entry {
+macro_rules! break_chan_phase_entry {
($self: ident, $res: expr, $entry: expr) => {
match $res {
Ok(res) => res,
Err(e) => {
- let (drop, res) = convert_chan_err!($self, e, $entry.get_mut(), $entry.key());
+ let key = *$entry.key();
+ let (drop, res) = convert_chan_phase_err!($self, e, $entry.get_mut(), &key);
if drop {
$entry.remove_entry();
}
}
}
-macro_rules! try_v1_outbound_chan_entry {
- ($self: ident, $res: expr, $entry: expr) => {
- match $res {
- Ok(res) => res,
- Err(e) => {
- let (drop, res) = convert_chan_err!($self, e, $entry.get_mut().context, $entry.key(), PREFUNDED);
- if drop {
- $entry.remove_entry();
- }
- return Err(res);
- }
- }
- }
-}
-
-macro_rules! try_chan_entry {
+macro_rules! try_chan_phase_entry {
($self: ident, $res: expr, $entry: expr) => {
match $res {
Ok(res) => res,
Err(e) => {
- let (drop, res) = convert_chan_err!($self, e, $entry.get_mut(), $entry.key());
+ let key = *$entry.key();
+ let (drop, res) = convert_chan_phase_err!($self, e, $entry.get_mut(), &key);
if drop {
$entry.remove_entry();
}
}
}
-macro_rules! remove_channel {
+macro_rules! remove_channel_phase {
($self: expr, $entry: expr) => {
{
let channel = $entry.remove_entry().1;
- update_maps_on_chan_removal!($self, &channel.context);
+ update_maps_on_chan_removal!($self, &channel.context());
channel
}
}
}
let channel_id = $chan.context.channel_id();
+ let unbroadcasted_batch_funding_txid = $chan.context.unbroadcasted_batch_funding_txid();
core::mem::drop($peer_state_lock);
core::mem::drop($per_peer_state_lock);
+ // If the channel belongs to a batch funding transaction, the progress of the batch
+ // should be updated as we have received funding_signed and persisted the monitor.
+ if let Some(txid) = unbroadcasted_batch_funding_txid {
+ let mut funding_batch_states = $self.funding_batch_states.lock().unwrap();
+ let mut batch_completed = false;
+ if let Some(batch_state) = funding_batch_states.get_mut(&txid) {
+ let channel_state = batch_state.iter_mut().find(|(chan_id, pubkey, _)| (
+ *chan_id == channel_id &&
+ *pubkey == counterparty_node_id
+ ));
+ if let Some(channel_state) = channel_state {
+ channel_state.2 = true;
+ } else {
+ debug_assert!(false, "Missing channel batch state for channel which completed initial monitor update");
+ }
+ batch_completed = batch_state.iter().all(|(_, _, completed)| *completed);
+ } else {
+ debug_assert!(false, "Missing batch state for channel which completed initial monitor update");
+ }
+
+ // When all channels in a batched funding transaction have become ready, it is not necessary
+ // to track the progress of the batch anymore and the state of the channels can be updated.
+ if batch_completed {
+ let removed_batch_state = funding_batch_states.remove(&txid).into_iter().flatten();
+ let per_peer_state = $self.per_peer_state.read().unwrap();
+ let mut batch_funding_tx = None;
+ for (channel_id, counterparty_node_id, _) in removed_batch_state {
+ if let Some(peer_state_mutex) = per_peer_state.get(&counterparty_node_id) {
+ let mut peer_state = peer_state_mutex.lock().unwrap();
+ if let Some(ChannelPhase::Funded(chan)) = peer_state.channel_by_id.get_mut(&channel_id) {
+ batch_funding_tx = batch_funding_tx.or_else(|| chan.context.unbroadcasted_funding());
+ chan.set_batch_ready();
+ let mut pending_events = $self.pending_events.lock().unwrap();
+ emit_channel_pending_event!(pending_events, chan);
+ }
+ }
+ }
+ if let Some(tx) = batch_funding_tx {
+ log_info!($self.logger, "Broadcasting batch funding transaction with txid {}", tx.txid());
+ $self.tx_broadcaster.broadcast_transactions(&[&tx]);
+ }
+ }
+ }
+
$self.handle_monitor_update_completion_actions(update_actions);
if let Some(forwards) = htlc_forwards {
}
macro_rules! handle_new_monitor_update {
- ($self: ident, $update_res: expr, $peer_state_lock: expr, $peer_state: expr, $per_peer_state_lock: expr, $chan: expr, _internal, $remove: expr, $completed: expr) => { {
- // update_maps_on_chan_removal needs to be able to take id_to_peer, so make sure we can in
- // any case so that it won't deadlock.
- debug_assert_ne!($self.id_to_peer.held_by_thread(), LockHeldState::HeldByThread);
- #[cfg(debug_assertions)] {
- debug_assert!($self.background_events_processed_since_startup.load(Ordering::Acquire));
- }
+ ($self: ident, $update_res: expr, $chan: expr, _internal, $completed: expr) => { {
+ debug_assert!($self.background_events_processed_since_startup.load(Ordering::Acquire));
match $update_res {
+ ChannelMonitorUpdateStatus::UnrecoverableError => {
+ let err_str = "ChannelMonitor[Update] persistence failed unrecoverably. This indicates we cannot continue normal operation and must shut down.";
+ log_error!($self.logger, "{}", err_str);
+ panic!("{}", err_str);
+ },
ChannelMonitorUpdateStatus::InProgress => {
log_debug!($self.logger, "ChannelMonitor update for {} in flight, holding messages until the update completes.",
- log_bytes!($chan.context.channel_id()[..]));
- Ok(false)
- },
- ChannelMonitorUpdateStatus::PermanentFailure => {
- log_error!($self.logger, "Closing channel {} due to monitor update ChannelMonitorUpdateStatus::PermanentFailure",
- log_bytes!($chan.context.channel_id()[..]));
- update_maps_on_chan_removal!($self, &$chan.context);
- let res = Err(MsgHandleErrInternal::from_finish_shutdown(
- "ChannelMonitor storage failure".to_owned(), $chan.context.channel_id(),
- $chan.context.get_user_id(), $chan.context.force_shutdown(false),
- $self.get_channel_update_for_broadcast(&$chan).ok()));
- $remove;
- res
+ &$chan.context.channel_id());
+ false
},
ChannelMonitorUpdateStatus::Completed => {
$completed;
- Ok(true)
+ true
},
}
} };
- ($self: ident, $update_res: expr, $peer_state_lock: expr, $peer_state: expr, $per_peer_state_lock: expr, $chan: expr, MANUALLY_REMOVING_INITIAL_MONITOR, $remove: expr) => {
- handle_new_monitor_update!($self, $update_res, $peer_state_lock, $peer_state,
- $per_peer_state_lock, $chan, _internal, $remove,
+ ($self: ident, $update_res: expr, $peer_state_lock: expr, $peer_state: expr, $per_peer_state_lock: expr, $chan: expr, INITIAL_MONITOR) => {
+ handle_new_monitor_update!($self, $update_res, $chan, _internal,
handle_monitor_update_completion!($self, $peer_state_lock, $peer_state, $per_peer_state_lock, $chan))
};
- ($self: ident, $update_res: expr, $peer_state_lock: expr, $peer_state: expr, $per_peer_state_lock: expr, $chan_entry: expr, INITIAL_MONITOR) => {
- handle_new_monitor_update!($self, $update_res, $peer_state_lock, $peer_state, $per_peer_state_lock, $chan_entry.get_mut(), MANUALLY_REMOVING_INITIAL_MONITOR, $chan_entry.remove_entry())
- };
- ($self: ident, $funding_txo: expr, $update: expr, $peer_state_lock: expr, $peer_state: expr, $per_peer_state_lock: expr, $chan: expr, MANUALLY_REMOVING, $remove: expr) => { {
+ ($self: ident, $funding_txo: expr, $update: expr, $peer_state_lock: expr, $peer_state: expr, $per_peer_state_lock: expr, $chan: expr) => { {
let in_flight_updates = $peer_state.in_flight_monitor_updates.entry($funding_txo)
.or_insert_with(Vec::new);
// During startup, we push monitor updates as background events through to here in
in_flight_updates.len() - 1
});
let update_res = $self.chain_monitor.update_channel($funding_txo, &in_flight_updates[idx]);
- handle_new_monitor_update!($self, update_res, $peer_state_lock, $peer_state,
- $per_peer_state_lock, $chan, _internal, $remove,
+ handle_new_monitor_update!($self, update_res, $chan, _internal,
{
let _ = in_flight_updates.remove(idx);
if in_flight_updates.is_empty() && $chan.blocked_monitor_updates_pending() == 0 {
}
})
} };
- ($self: ident, $funding_txo: expr, $update: expr, $peer_state_lock: expr, $peer_state: expr, $per_peer_state_lock: expr, $chan_entry: expr) => {
- handle_new_monitor_update!($self, $funding_txo, $update, $peer_state_lock, $peer_state, $per_peer_state_lock, $chan_entry.get_mut(), MANUALLY_REMOVING, $chan_entry.remove_entry())
- }
}
macro_rules! process_events_body {
return;
}
- let mut result = NotifyOption::SkipPersist;
+ let mut result;
{
// We'll acquire our total consistency lock so that we can be sure no other
// Because `handle_post_event_actions` may send `ChannelMonitorUpdate`s to the user we must
// ensure any startup-generated background events are handled first.
- if $self.process_background_events() == NotifyOption::DoPersist { result = NotifyOption::DoPersist; }
+ result = $self.process_background_events();
// TODO: This behavior should be documented. It's unintuitive that we query
// ChannelMonitors when clearing other events.
let mut pending_events = $self.pending_events.lock().unwrap();
pending_events.drain(..num_events);
processed_all_events = pending_events.is_empty();
+ // Note that `push_pending_forwards_ev` relies on `pending_events_processor` being
+ // updated here with the `pending_events` lock acquired.
$self.pending_events_processor.store(false, Ordering::Release);
}
processed_all_events = false;
}
- if result == NotifyOption::DoPersist {
- $self.persistence_notifier.notify();
+ match result {
+ NotifyOption::DoPersist => {
+ $self.needs_persist_flag.store(true, Ordering::Release);
+ $self.event_persist_notifier.notify();
+ },
+ NotifyOption::SkipPersistHandleEvents =>
+ $self.event_persist_notifier.notify(),
+ NotifyOption::SkipPersistNoEvents => {},
}
}
}
pending_events_processor: AtomicBool::new(false),
pending_background_events: Mutex::new(Vec::new()),
total_consistency_lock: RwLock::new(()),
- #[cfg(debug_assertions)]
background_events_processed_since_startup: AtomicBool::new(false),
- persistence_notifier: Notifier::new(),
+ event_persist_notifier: Notifier::new(),
+ needs_persist_flag: AtomicBool::new(false),
+ funding_batch_states: Mutex::new(BTreeMap::new()),
entropy_source,
node_signer,
/// [`Event::FundingGenerationReady::user_channel_id`]: events::Event::FundingGenerationReady::user_channel_id
/// [`Event::FundingGenerationReady::temporary_channel_id`]: events::Event::FundingGenerationReady::temporary_channel_id
/// [`Event::ChannelClosed::channel_id`]: events::Event::ChannelClosed::channel_id
- pub fn create_channel(&self, their_network_key: PublicKey, channel_value_satoshis: u64, push_msat: u64, user_channel_id: u128, override_config: Option<UserConfig>) -> Result<[u8; 32], APIError> {
+ pub fn create_channel(&self, their_network_key: PublicKey, channel_value_satoshis: u64, push_msat: u64, user_channel_id: u128, override_config: Option<UserConfig>) -> Result<ChannelId, APIError> {
if channel_value_satoshis < 1000 {
return Err(APIError::APIMisuseError { err: format!("Channel value must be at least 1000 satoshis. It was {}", channel_value_satoshis) });
}
let res = channel.get_open_channel(self.genesis_hash.clone());
let temporary_channel_id = channel.context.channel_id();
- match peer_state.outbound_v1_channel_by_id.entry(temporary_channel_id) {
+ match peer_state.channel_by_id.entry(temporary_channel_id) {
hash_map::Entry::Occupied(_) => {
if cfg!(fuzzing) {
return Err(APIError::APIMisuseError { err: "Fuzzy bad RNG".to_owned() });
panic!("RNG is bad???");
}
},
- hash_map::Entry::Vacant(entry) => { entry.insert(channel); }
+ hash_map::Entry::Vacant(entry) => { entry.insert(ChannelPhase::UnfundedOutboundV1(channel)); }
}
peer_state.pending_msg_events.push(events::MessageSendEvent::SendOpenChannel {
Ok(temporary_channel_id)
}
- fn list_funded_channels_with_filter<Fn: FnMut(&(&[u8; 32], &Channel<<SP::Target as SignerProvider>::Signer>)) -> bool + Copy>(&self, f: Fn) -> Vec<ChannelDetails> {
+ fn list_funded_channels_with_filter<Fn: FnMut(&(&ChannelId, &Channel<SP>)) -> bool + Copy>(&self, f: Fn) -> Vec<ChannelDetails> {
// Allocate our best estimate of the number of channels we have in the `res`
// Vec. Sadly the `short_to_chan_info` map doesn't cover channels without
// a scid or a scid alias, and the `id_to_peer` shouldn't be used outside
for (_cp_id, peer_state_mutex) in per_peer_state.iter() {
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
let peer_state = &mut *peer_state_lock;
- for (_channel_id, channel) in peer_state.channel_by_id.iter().filter(f) {
- let details = ChannelDetails::from_channel_context(&channel.context, best_block_height,
- peer_state.latest_features.clone());
- res.push(details);
- }
+ res.extend(peer_state.channel_by_id.iter()
+ .filter_map(|(chan_id, phase)| match phase {
+ // Only `Channels` in the `ChannelPhase::Funded` phase can be considered funded.
+ ChannelPhase::Funded(chan) => Some((chan_id, chan)),
+ _ => None,
+ })
+ .filter(f)
+ .map(|(_channel_id, channel)| {
+ ChannelDetails::from_channel_context(&channel.context, best_block_height,
+ peer_state.latest_features.clone(), &self.fee_estimator)
+ })
+ );
}
}
res
for (_cp_id, peer_state_mutex) in per_peer_state.iter() {
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
let peer_state = &mut *peer_state_lock;
- for (_channel_id, channel) in peer_state.channel_by_id.iter() {
- let details = ChannelDetails::from_channel_context(&channel.context, best_block_height,
- peer_state.latest_features.clone());
- res.push(details);
- }
- for (_channel_id, channel) in peer_state.inbound_v1_channel_by_id.iter() {
- let details = ChannelDetails::from_channel_context(&channel.context, best_block_height,
- peer_state.latest_features.clone());
- res.push(details);
- }
- for (_channel_id, channel) in peer_state.outbound_v1_channel_by_id.iter() {
- let details = ChannelDetails::from_channel_context(&channel.context, best_block_height,
- peer_state.latest_features.clone());
+ for context in peer_state.channel_by_id.iter().map(|(_, phase)| phase.context()) {
+ let details = ChannelDetails::from_channel_context(context, best_block_height,
+ peer_state.latest_features.clone(), &self.fee_estimator);
res.push(details);
}
}
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
let peer_state = &mut *peer_state_lock;
let features = &peer_state.latest_features;
+ let context_to_details = |context| {
+ ChannelDetails::from_channel_context(context, best_block_height, features.clone(), &self.fee_estimator)
+ };
return peer_state.channel_by_id
.iter()
- .map(|(_, channel)|
- ChannelDetails::from_channel_context(&channel.context, best_block_height, features.clone()))
+ .map(|(_, phase)| phase.context())
+ .map(context_to_details)
.collect();
}
vec![]
/// [`Event::PaymentSent`]: events::Event::PaymentSent
pub fn list_recent_payments(&self) -> Vec<RecentPaymentDetails> {
self.pending_outbound_payments.pending_outbound_payments.lock().unwrap().iter()
- .filter_map(|(_, pending_outbound_payment)| match pending_outbound_payment {
+ .filter_map(|(payment_id, pending_outbound_payment)| match pending_outbound_payment {
+ PendingOutboundPayment::AwaitingInvoice { .. } => {
+ Some(RecentPaymentDetails::AwaitingInvoice { payment_id: *payment_id })
+ },
+ // InvoiceReceived is an intermediate state and doesn't need to be exposed
+ PendingOutboundPayment::InvoiceReceived { .. } => {
+ Some(RecentPaymentDetails::AwaitingInvoice { payment_id: *payment_id })
+ },
PendingOutboundPayment::Retryable { payment_hash, total_msat, .. } => {
Some(RecentPaymentDetails::Pending {
+ payment_id: *payment_id,
payment_hash: *payment_hash,
total_msat: *total_msat,
})
},
PendingOutboundPayment::Abandoned { payment_hash, .. } => {
- Some(RecentPaymentDetails::Abandoned { payment_hash: *payment_hash })
+ Some(RecentPaymentDetails::Abandoned { payment_id: *payment_id, payment_hash: *payment_hash })
},
PendingOutboundPayment::Fulfilled { payment_hash, .. } => {
- Some(RecentPaymentDetails::Fulfilled { payment_hash: *payment_hash })
+ Some(RecentPaymentDetails::Fulfilled { payment_id: *payment_id, payment_hash: *payment_hash })
},
PendingOutboundPayment::Legacy { .. } => None
})
}
/// Helper function that issues the channel close events
- fn issue_channel_close_events(&self, context: &ChannelContext<<SP::Target as SignerProvider>::Signer>, closure_reason: ClosureReason) {
+ fn issue_channel_close_events(&self, context: &ChannelContext<SP>, closure_reason: ClosureReason) {
let mut pending_events_lock = self.pending_events.lock().unwrap();
match context.unbroadcasted_funding() {
Some(transaction) => {
pending_events_lock.push_back((events::Event::ChannelClosed {
channel_id: context.channel_id(),
user_channel_id: context.get_user_id(),
- reason: closure_reason
+ reason: closure_reason,
+ counterparty_node_id: Some(context.get_counterparty_node_id()),
+ channel_capacity_sats: Some(context.get_value_satoshis()),
}, None));
}
- fn close_channel_internal(&self, channel_id: &[u8; 32], counterparty_node_id: &PublicKey, target_feerate_sats_per_1000_weight: Option<u32>, override_shutdown_script: Option<ShutdownScript>) -> Result<(), APIError> {
+ fn close_channel_internal(&self, channel_id: &ChannelId, counterparty_node_id: &PublicKey, target_feerate_sats_per_1000_weight: Option<u32>, override_shutdown_script: Option<ShutdownScript>) -> Result<(), APIError> {
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
let mut failed_htlcs: Vec<(HTLCSource, PaymentHash)>;
- let result: Result<(), _> = loop {
+ let mut shutdown_result = None;
+ loop {
let per_peer_state = self.per_peer_state.read().unwrap();
let peer_state_mutex = per_peer_state.get(counterparty_node_id)
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
let peer_state = &mut *peer_state_lock;
+
match peer_state.channel_by_id.entry(channel_id.clone()) {
- hash_map::Entry::Occupied(mut chan_entry) => {
- let funding_txo_opt = chan_entry.get().context.get_funding_txo();
- let their_features = &peer_state.latest_features;
- let (shutdown_msg, mut monitor_update_opt, htlcs) = chan_entry.get_mut()
- .get_shutdown(&self.signer_provider, their_features, target_feerate_sats_per_1000_weight, override_shutdown_script)?;
- failed_htlcs = htlcs;
-
- // 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
- // `shutdown_signed`, which we'll delay if we're pending a monitor update.
- peer_state.pending_msg_events.push(events::MessageSendEvent::SendShutdown {
- node_id: *counterparty_node_id,
- msg: shutdown_msg,
- });
+ hash_map::Entry::Occupied(mut chan_phase_entry) => {
+ 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) =
+ chan.get_shutdown(&self.signer_provider, their_features, target_feerate_sats_per_1000_weight, override_shutdown_script)?;
+ failed_htlcs = htlcs;
- // Update the monitor with the shutdown script if necessary.
- if let Some(monitor_update) = monitor_update_opt.take() {
- break handle_new_monitor_update!(self, funding_txo_opt.unwrap(), monitor_update,
- peer_state_lock, peer_state, per_peer_state, chan_entry).map(|_| ());
- }
+ // 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
+ // `shutdown_signed`, which we'll delay if we're pending a monitor update.
+ peer_state.pending_msg_events.push(events::MessageSendEvent::SendShutdown {
+ node_id: *counterparty_node_id,
+ msg: shutdown_msg,
+ });
- if chan_entry.get().is_shutdown() {
- let channel = remove_channel!(self, chan_entry);
- if let Ok(channel_update) = self.get_channel_update_for_broadcast(&channel) {
- peer_state.pending_msg_events.push(events::MessageSendEvent::BroadcastChannelUpdate {
- msg: channel_update
- });
+ debug_assert!(monitor_update_opt.is_none() || !chan.is_shutdown(),
+ "We can't both complete shutdown and generate a monitor update");
+
+ // Update the monitor with the shutdown script if necessary.
+ if let Some(monitor_update) = monitor_update_opt.take() {
+ handle_new_monitor_update!(self, funding_txo_opt.unwrap(), monitor_update,
+ peer_state_lock, peer_state, per_peer_state, chan);
+ break;
+ }
+
+ if chan.is_shutdown() {
+ if let ChannelPhase::Funded(chan) = remove_channel_phase!(self, chan_phase_entry) {
+ if let Ok(channel_update) = self.get_channel_update_for_broadcast(&chan) {
+ peer_state.pending_msg_events.push(events::MessageSendEvent::BroadcastChannelUpdate {
+ msg: channel_update
+ });
+ }
+ self.issue_channel_close_events(&chan.context, ClosureReason::HolderForceClosed);
+ shutdown_result = Some((None, Vec::new(), unbroadcasted_batch_funding_txid));
+ }
}
- self.issue_channel_close_events(&channel.context, ClosureReason::HolderForceClosed);
+ break;
}
- break Ok(());
},
- hash_map::Entry::Vacant(_) => return Err(APIError::ChannelUnavailable{err: format!("Channel with id {} not found for the passed counterparty node_id {}", log_bytes!(*channel_id), counterparty_node_id) })
+ hash_map::Entry::Vacant(_) => {
+ // If we reach this point, it means that the channel_id either refers to an unfunded channel or
+ // it does not exist for this peer. Either way, we can attempt to force-close it.
+ //
+ // An appropriate error will be returned for non-existence of the channel if that's the case.
+ return self.force_close_channel_with_peer(&channel_id, counterparty_node_id, None, false).map(|_| ())
+ },
}
- };
+ }
for htlc_source in failed_htlcs.drain(..) {
let reason = HTLCFailReason::from_failure_code(0x4000 | 8);
self.fail_htlc_backwards_internal(&htlc_source.0, &htlc_source.1, &reason, receiver);
}
- let _ = handle_error!(self, result, *counterparty_node_id);
+ if let Some(shutdown_result) = shutdown_result {
+ self.finish_close_channel(shutdown_result);
+ }
+
Ok(())
}
/// [`Background`]: crate::chain::chaininterface::ConfirmationTarget::Background
/// [`Normal`]: crate::chain::chaininterface::ConfirmationTarget::Normal
/// [`SendShutdown`]: crate::events::MessageSendEvent::SendShutdown
- pub fn close_channel(&self, channel_id: &[u8; 32], counterparty_node_id: &PublicKey) -> Result<(), APIError> {
+ 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)
}
/// [`Background`]: crate::chain::chaininterface::ConfirmationTarget::Background
/// [`Normal`]: crate::chain::chaininterface::ConfirmationTarget::Normal
/// [`SendShutdown`]: crate::events::MessageSendEvent::SendShutdown
- pub fn close_channel_with_feerate_and_script(&self, channel_id: &[u8; 32], counterparty_node_id: &PublicKey, target_feerate_sats_per_1000_weight: Option<u32>, shutdown_script: Option<ShutdownScript>) -> Result<(), APIError> {
+ 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)
}
- #[inline]
- fn finish_force_close_channel(&self, shutdown_res: ShutdownResult) {
- let (monitor_update_option, mut failed_htlcs) = shutdown_res;
+ fn finish_close_channel(&self, 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(..) {
let (source, payment_hash, counterparty_node_id, channel_id) = htlc_source;
// ignore the result here.
let _ = self.chain_monitor.update_channel(funding_txo, &monitor_update);
}
+ let mut shutdown_results = Vec::new();
+ if let Some(txid) = 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();
+ let mut has_uncompleted_channel = None;
+ for (channel_id, counterparty_node_id, state) in affected_channels {
+ if let Some(peer_state_mutex) = per_peer_state.get(&counterparty_node_id) {
+ let mut peer_state = peer_state_mutex.lock().unwrap();
+ if let Some(mut chan) = peer_state.channel_by_id.remove(&channel_id) {
+ update_maps_on_chan_removal!(self, &chan.context());
+ self.issue_channel_close_events(&chan.context(), ClosureReason::FundingBatchClosure);
+ shutdown_results.push(chan.context_mut().force_shutdown(false));
+ }
+ }
+ has_uncompleted_channel = Some(has_uncompleted_channel.map_or(!state, |v| v || !state));
+ }
+ debug_assert!(
+ has_uncompleted_channel.unwrap_or(true),
+ "Closing a batch where all channels have completed initial monitor update",
+ );
+ }
+ for shutdown_result in shutdown_results.drain(..) {
+ self.finish_close_channel(shutdown_result);
+ }
}
/// `peer_msg` should be set when we receive a message from a peer, but not set when the
/// user closes, which will be re-exposed as the `ChannelClosed` reason.
- fn force_close_channel_with_peer(&self, channel_id: &[u8; 32], peer_node_id: &PublicKey, peer_msg: Option<&String>, broadcast: bool)
+ fn force_close_channel_with_peer(&self, channel_id: &ChannelId, peer_node_id: &PublicKey, peer_msg: Option<&String>, broadcast: bool)
-> Result<PublicKey, APIError> {
let per_peer_state = self.per_peer_state.read().unwrap();
let peer_state_mutex = per_peer_state.get(peer_node_id)
.ok_or_else(|| APIError::ChannelUnavailable { err: format!("Can't find a peer matching the passed counterparty node_id {}", peer_node_id) })?;
let (update_opt, counterparty_node_id) = {
- let mut peer_state_lock = peer_state_mutex.lock().unwrap();
- let peer_state = &mut *peer_state_lock;
+ let mut peer_state = peer_state_mutex.lock().unwrap();
let closure_reason = if let Some(peer_msg) = peer_msg {
ClosureReason::CounterpartyForceClosed { peer_msg: UntrustedString(peer_msg.to_string()) }
} else {
ClosureReason::HolderForceClosed
};
- if let hash_map::Entry::Occupied(chan) = peer_state.channel_by_id.entry(channel_id.clone()) {
- log_error!(self.logger, "Force-closing channel {}", log_bytes!(channel_id[..]));
- self.issue_channel_close_events(&chan.get().context, closure_reason);
- let mut chan = remove_channel!(self, chan);
- self.finish_force_close_channel(chan.context.force_shutdown(broadcast));
- (self.get_channel_update_for_broadcast(&chan).ok(), chan.context.get_counterparty_node_id())
- } else if let hash_map::Entry::Occupied(chan) = peer_state.outbound_v1_channel_by_id.entry(channel_id.clone()) {
- log_error!(self.logger, "Force-closing channel {}", log_bytes!(channel_id[..]));
- self.issue_channel_close_events(&chan.get().context, closure_reason);
- let mut chan = remove_channel!(self, chan);
- self.finish_force_close_channel(chan.context.force_shutdown(false));
- // Prefunded channel has no update
- (None, chan.context.get_counterparty_node_id())
- } else if let hash_map::Entry::Occupied(chan) = peer_state.inbound_v1_channel_by_id.entry(channel_id.clone()) {
- log_error!(self.logger, "Force-closing channel {}", log_bytes!(channel_id[..]));
- self.issue_channel_close_events(&chan.get().context, closure_reason);
- let mut chan = remove_channel!(self, chan);
- self.finish_force_close_channel(chan.context.force_shutdown(false));
- // Prefunded channel has no update
- (None, chan.context.get_counterparty_node_id())
+ if let hash_map::Entry::Occupied(chan_phase_entry) = peer_state.channel_by_id.entry(channel_id.clone()) {
+ log_error!(self.logger, "Force-closing channel {}", channel_id);
+ self.issue_channel_close_events(&chan_phase_entry.get().context(), closure_reason);
+ let mut chan_phase = remove_channel_phase!(self, chan_phase_entry);
+ mem::drop(peer_state);
+ mem::drop(per_peer_state);
+ match chan_phase {
+ ChannelPhase::Funded(mut chan) => {
+ self.finish_close_channel(chan.context.force_shutdown(broadcast));
+ (self.get_channel_update_for_broadcast(&chan).ok(), chan.context.get_counterparty_node_id())
+ },
+ ChannelPhase::UnfundedOutboundV1(_) | ChannelPhase::UnfundedInboundV1(_) => {
+ self.finish_close_channel(chan_phase.context_mut().force_shutdown(false));
+ // Unfunded channel has no update
+ (None, chan_phase.context().get_counterparty_node_id())
+ },
+ }
+ } else if peer_state.inbound_channel_request_by_id.remove(channel_id).is_some() {
+ log_error!(self.logger, "Force-closing channel {}", &channel_id);
+ // N.B. that we don't send any channel close event here: we
+ // don't have a user_channel_id, and we never sent any opening
+ // events anyway.
+ (None, *peer_node_id)
} else {
- return Err(APIError::ChannelUnavailable{ err: format!("Channel with id {} not found for the passed counterparty node_id {}", log_bytes!(*channel_id), peer_node_id) });
+ return Err(APIError::ChannelUnavailable{ err: format!("Channel with id {} not found for the passed counterparty node_id {}", channel_id, peer_node_id) });
}
};
if let Some(update) = update_opt {
- let mut peer_state = peer_state_mutex.lock().unwrap();
- peer_state.pending_msg_events.push(events::MessageSendEvent::BroadcastChannelUpdate {
- msg: update
- });
+ // Try to send the `BroadcastChannelUpdate` to the peer we just force-closed on, but if
+ // not try to broadcast it via whatever peer we have.
+ let per_peer_state = self.per_peer_state.read().unwrap();
+ let a_peer_state_opt = per_peer_state.get(peer_node_id)
+ .ok_or(per_peer_state.values().next());
+ if let Ok(a_peer_state_mutex) = a_peer_state_opt {
+ let mut a_peer_state = a_peer_state_mutex.lock().unwrap();
+ a_peer_state.pending_msg_events.push(events::MessageSendEvent::BroadcastChannelUpdate {
+ msg: update
+ });
+ }
}
Ok(counterparty_node_id)
}
- fn force_close_sending_error(&self, channel_id: &[u8; 32], counterparty_node_id: &PublicKey, broadcast: bool) -> Result<(), APIError> {
+ fn force_close_sending_error(&self, channel_id: &ChannelId, counterparty_node_id: &PublicKey, broadcast: bool) -> Result<(), APIError> {
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
match self.force_close_channel_with_peer(channel_id, counterparty_node_id, None, broadcast) {
Ok(counterparty_node_id) => {
/// rejecting new HTLCs on the given channel. Fails if `channel_id` is unknown to
/// the manager, or if the `counterparty_node_id` isn't the counterparty of the corresponding
/// channel.
- pub fn force_close_broadcasting_latest_txn(&self, channel_id: &[u8; 32], counterparty_node_id: &PublicKey)
+ pub fn force_close_broadcasting_latest_txn(&self, channel_id: &ChannelId, counterparty_node_id: &PublicKey)
-> Result<(), APIError> {
self.force_close_sending_error(channel_id, counterparty_node_id, true)
}
///
/// You can always get the latest local transaction(s) to broadcast from
/// [`ChannelMonitor::get_latest_holder_commitment_txn`].
- pub fn force_close_without_broadcasting_txn(&self, channel_id: &[u8; 32], counterparty_node_id: &PublicKey)
+ pub fn force_close_without_broadcasting_txn(&self, channel_id: &ChannelId, counterparty_node_id: &PublicKey)
-> Result<(), APIError> {
self.force_close_sending_error(channel_id, counterparty_node_id, false)
}
}
}
+ fn construct_fwd_pending_htlc_info(
+ &self, msg: &msgs::UpdateAddHTLC, hop_data: msgs::InboundOnionPayload, hop_hmac: [u8; 32],
+ new_packet_bytes: [u8; onion_utils::ONION_DATA_LEN], shared_secret: [u8; 32],
+ next_packet_pubkey_opt: Option<Result<PublicKey, secp256k1::Error>>
+ ) -> Result<PendingHTLCInfo, InboundOnionErr> {
+ debug_assert!(next_packet_pubkey_opt.is_some());
+ let outgoing_packet = msgs::OnionPacket {
+ version: 0,
+ public_key: next_packet_pubkey_opt.unwrap_or(Err(secp256k1::Error::InvalidPublicKey)),
+ hop_data: new_packet_bytes,
+ hmac: hop_hmac,
+ };
+
+ let (short_channel_id, amt_to_forward, outgoing_cltv_value) = match hop_data {
+ msgs::InboundOnionPayload::Forward { short_channel_id, amt_to_forward, outgoing_cltv_value } =>
+ (short_channel_id, amt_to_forward, outgoing_cltv_value),
+ msgs::InboundOnionPayload::Receive { .. } | msgs::InboundOnionPayload::BlindedReceive { .. } =>
+ return Err(InboundOnionErr {
+ msg: "Final Node OnionHopData provided for us as an intermediary node",
+ err_code: 0x4000 | 22,
+ err_data: Vec::new(),
+ }),
+ };
+
+ Ok(PendingHTLCInfo {
+ routing: PendingHTLCRouting::Forward {
+ onion_packet: outgoing_packet,
+ short_channel_id,
+ },
+ payment_hash: msg.payment_hash,
+ incoming_shared_secret: shared_secret,
+ incoming_amt_msat: Some(msg.amount_msat),
+ outgoing_amt_msat: amt_to_forward,
+ outgoing_cltv_value,
+ skimmed_fee_msat: None,
+ })
+ }
+
fn construct_recv_pending_htlc_info(
- &self, hop_data: msgs::OnionHopData, shared_secret: [u8; 32], payment_hash: PaymentHash,
+ &self, hop_data: msgs::InboundOnionPayload, shared_secret: [u8; 32], payment_hash: PaymentHash,
amt_msat: u64, cltv_expiry: u32, phantom_shared_secret: Option<[u8; 32]>, allow_underpay: bool,
counterparty_skimmed_fee_msat: Option<u64>,
- ) -> Result<PendingHTLCInfo, ReceiveError> {
+ ) -> Result<PendingHTLCInfo, InboundOnionErr> {
+ let (payment_data, keysend_preimage, custom_tlvs, onion_amt_msat, outgoing_cltv_value, payment_metadata) = match hop_data {
+ msgs::InboundOnionPayload::Receive {
+ payment_data, keysend_preimage, custom_tlvs, amt_msat, outgoing_cltv_value, payment_metadata, ..
+ } =>
+ (payment_data, keysend_preimage, custom_tlvs, amt_msat, outgoing_cltv_value, payment_metadata),
+ msgs::InboundOnionPayload::BlindedReceive {
+ amt_msat, total_msat, outgoing_cltv_value, payment_secret, ..
+ } => {
+ let payment_data = msgs::FinalOnionHopData { payment_secret, total_msat };
+ (Some(payment_data), None, Vec::new(), amt_msat, outgoing_cltv_value, None)
+ }
+ msgs::InboundOnionPayload::Forward { .. } => {
+ return Err(InboundOnionErr {
+ err_code: 0x4000|22,
+ err_data: Vec::new(),
+ msg: "Got non final data with an HMAC of 0",
+ })
+ },
+ };
// final_incorrect_cltv_expiry
- if hop_data.outgoing_cltv_value > cltv_expiry {
- return Err(ReceiveError {
+ if outgoing_cltv_value > cltv_expiry {
+ return Err(InboundOnionErr {
msg: "Upstream node set CLTV to less than the CLTV set by the sender",
err_code: 18,
err_data: cltv_expiry.to_be_bytes().to_vec()
// payment logic has enough time to fail the HTLC backward before our onchain logic triggers a
// channel closure (see HTLC_FAIL_BACK_BUFFER rationale).
let current_height: u32 = self.best_block.read().unwrap().height();
- if (hop_data.outgoing_cltv_value as u64) <= current_height as u64 + HTLC_FAIL_BACK_BUFFER as u64 + 1 {
+ if (outgoing_cltv_value as u64) <= current_height as u64 + HTLC_FAIL_BACK_BUFFER as u64 + 1 {
let mut err_data = Vec::with_capacity(12);
err_data.extend_from_slice(&amt_msat.to_be_bytes());
err_data.extend_from_slice(¤t_height.to_be_bytes());
- return Err(ReceiveError {
+ return Err(InboundOnionErr {
err_code: 0x4000 | 15, err_data,
msg: "The final CLTV expiry is too soon to handle",
});
}
- if (!allow_underpay && hop_data.amt_to_forward > amt_msat) ||
- (allow_underpay && hop_data.amt_to_forward >
+ if (!allow_underpay && onion_amt_msat > amt_msat) ||
+ (allow_underpay && onion_amt_msat >
amt_msat.saturating_add(counterparty_skimmed_fee_msat.unwrap_or(0)))
{
- return Err(ReceiveError {
+ return Err(InboundOnionErr {
err_code: 19,
err_data: amt_msat.to_be_bytes().to_vec(),
msg: "Upstream node sent less than we were supposed to receive in payment",
});
}
- let routing = match hop_data.format {
- msgs::OnionHopDataFormat::NonFinalNode { .. } => {
- return Err(ReceiveError {
+ let routing = if let Some(payment_preimage) = keysend_preimage {
+ // We need to check that the sender knows the keysend preimage before processing this
+ // payment further. Otherwise, an intermediary routing hop forwarding non-keysend-HTLC X
+ // could discover the final destination of X, by probing the adjacent nodes on the route
+ // with a keysend payment of identical payment hash to X and observing the processing
+ // time discrepancies due to a hash collision with X.
+ let hashed_preimage = PaymentHash(Sha256::hash(&payment_preimage.0).into_inner());
+ if hashed_preimage != payment_hash {
+ return Err(InboundOnionErr {
err_code: 0x4000|22,
err_data: Vec::new(),
- msg: "Got non final data with an HMAC of 0",
+ msg: "Payment preimage didn't match payment hash",
});
- },
- msgs::OnionHopDataFormat::FinalNode { payment_data, keysend_preimage, payment_metadata } => {
- if let Some(payment_preimage) = keysend_preimage {
- // We need to check that the sender knows the keysend preimage before processing this
- // payment further. Otherwise, an intermediary routing hop forwarding non-keysend-HTLC X
- // could discover the final destination of X, by probing the adjacent nodes on the route
- // with a keysend payment of identical payment hash to X and observing the processing
- // time discrepancies due to a hash collision with X.
- let hashed_preimage = PaymentHash(Sha256::hash(&payment_preimage.0).into_inner());
- if hashed_preimage != payment_hash {
- return Err(ReceiveError {
- err_code: 0x4000|22,
- err_data: Vec::new(),
- msg: "Payment preimage didn't match payment hash",
- });
- }
- if !self.default_configuration.accept_mpp_keysend && payment_data.is_some() {
- return Err(ReceiveError {
- err_code: 0x4000|22,
- err_data: Vec::new(),
- msg: "We don't support MPP keysend payments",
- });
- }
- PendingHTLCRouting::ReceiveKeysend {
- payment_data,
- payment_preimage,
- payment_metadata,
- incoming_cltv_expiry: hop_data.outgoing_cltv_value,
- }
- } else if let Some(data) = payment_data {
- PendingHTLCRouting::Receive {
- payment_data: data,
- payment_metadata,
- incoming_cltv_expiry: hop_data.outgoing_cltv_value,
- phantom_shared_secret,
- }
- } else {
- return Err(ReceiveError {
- err_code: 0x4000|0x2000|3,
- err_data: Vec::new(),
- msg: "We require payment_secrets",
- });
- }
- },
+ }
+ if !self.default_configuration.accept_mpp_keysend && payment_data.is_some() {
+ return Err(InboundOnionErr {
+ err_code: 0x4000|22,
+ err_data: Vec::new(),
+ msg: "We don't support MPP keysend payments",
+ });
+ }
+ PendingHTLCRouting::ReceiveKeysend {
+ payment_data,
+ payment_preimage,
+ payment_metadata,
+ incoming_cltv_expiry: outgoing_cltv_value,
+ custom_tlvs,
+ }
+ } else if let Some(data) = payment_data {
+ PendingHTLCRouting::Receive {
+ payment_data: data,
+ payment_metadata,
+ incoming_cltv_expiry: outgoing_cltv_value,
+ phantom_shared_secret,
+ custom_tlvs,
+ }
+ } else {
+ return Err(InboundOnionErr {
+ err_code: 0x4000|0x2000|3,
+ err_data: Vec::new(),
+ msg: "We require payment_secrets",
+ });
};
Ok(PendingHTLCInfo {
routing,
payment_hash,
incoming_shared_secret: shared_secret,
incoming_amt_msat: Some(amt_msat),
- outgoing_amt_msat: hop_data.amt_to_forward,
- outgoing_cltv_value: hop_data.outgoing_cltv_value,
+ outgoing_amt_msat: onion_amt_msat,
+ outgoing_cltv_value,
skimmed_fee_msat: counterparty_skimmed_fee_msat,
})
}
}
}
- let next_hop = match onion_utils::decode_next_payment_hop(shared_secret, &msg.onion_routing_packet.hop_data[..], msg.onion_routing_packet.hmac, msg.payment_hash) {
+ let next_hop = match onion_utils::decode_next_payment_hop(
+ shared_secret, &msg.onion_routing_packet.hop_data[..], msg.onion_routing_packet.hmac,
+ msg.payment_hash, &self.node_signer
+ ) {
Ok(res) => res,
Err(onion_utils::OnionDecodeErr::Malformed { err_msg, err_code }) => {
return_malformed_err!(err_msg, err_code);
};
let (outgoing_scid, outgoing_amt_msat, outgoing_cltv_value, next_packet_pk_opt) = match next_hop {
onion_utils::Hop::Forward {
- next_hop_data: msgs::OnionHopData {
- format: msgs::OnionHopDataFormat::NonFinalNode { short_channel_id }, amt_to_forward,
- outgoing_cltv_value,
+ next_hop_data: msgs::InboundOnionPayload::Forward {
+ short_channel_id, amt_to_forward, outgoing_cltv_value
}, ..
} => {
- let next_pk = onion_utils::next_hop_packet_pubkey(&self.secp_ctx,
+ let next_packet_pk = onion_utils::next_hop_pubkey(&self.secp_ctx,
msg.onion_routing_packet.public_key.unwrap(), &shared_secret);
- (short_channel_id, amt_to_forward, outgoing_cltv_value, Some(next_pk))
+ (short_channel_id, amt_to_forward, outgoing_cltv_value, Some(next_packet_pk))
},
// We'll do receive checks in [`Self::construct_pending_htlc_info`] so we have access to the
// inbound channel's state.
onion_utils::Hop::Receive { .. } => return Ok((next_hop, shared_secret, None)),
- onion_utils::Hop::Forward {
- next_hop_data: msgs::OnionHopData { format: msgs::OnionHopDataFormat::FinalNode { .. }, .. }, ..
- } => {
+ onion_utils::Hop::Forward { next_hop_data: msgs::InboundOnionPayload::Receive { .. }, .. } |
+ onion_utils::Hop::Forward { next_hop_data: msgs::InboundOnionPayload::BlindedReceive { .. }, .. } =>
+ {
return_err!("Final Node OnionHopData provided for us as an intermediary node", 0x4000 | 22, &[0; 0]);
}
};
}
let mut peer_state_lock = peer_state_mutex_opt.unwrap().lock().unwrap();
let peer_state = &mut *peer_state_lock;
- let chan = match peer_state.channel_by_id.get_mut(&forwarding_id) {
+ let chan = match peer_state.channel_by_id.get_mut(&forwarding_id).map(
+ |chan_phase| if let ChannelPhase::Funded(chan) = chan_phase { Some(chan) } else { None }
+ ).flatten() {
None => {
// Channel was removed. The short_to_chan_info and channel_by_id maps
// have no consistency guarantees.
// delay) once they've send us a commitment_signed!
PendingHTLCStatus::Forward(info)
},
- Err(ReceiveError { err_code, err_data, msg }) => return_err!(msg, err_code, &err_data)
+ Err(InboundOnionErr { err_code, err_data, msg }) => return_err!(msg, err_code, &err_data)
}
},
onion_utils::Hop::Forward { next_hop_data, next_hop_hmac, new_packet_bytes } => {
- debug_assert!(next_packet_pubkey_opt.is_some());
- let outgoing_packet = msgs::OnionPacket {
- version: 0,
- public_key: next_packet_pubkey_opt.unwrap_or(Err(secp256k1::Error::InvalidPublicKey)),
- hop_data: new_packet_bytes,
- hmac: next_hop_hmac.clone(),
- };
-
- let short_channel_id = match next_hop_data.format {
- msgs::OnionHopDataFormat::NonFinalNode { short_channel_id } => short_channel_id,
- msgs::OnionHopDataFormat::FinalNode { .. } => {
- return_err!("Final Node OnionHopData provided for us as an intermediary node", 0x4000 | 22, &[0;0]);
- },
- };
-
- PendingHTLCStatus::Forward(PendingHTLCInfo {
- routing: PendingHTLCRouting::Forward {
- onion_packet: outgoing_packet,
- short_channel_id,
- },
- payment_hash: msg.payment_hash.clone(),
- incoming_shared_secret: shared_secret,
- incoming_amt_msat: Some(msg.amount_msat),
- outgoing_amt_msat: next_hop_data.amt_to_forward,
- outgoing_cltv_value: next_hop_data.outgoing_cltv_value,
- skimmed_fee_msat: None,
- })
+ match self.construct_fwd_pending_htlc_info(msg, next_hop_data, next_hop_hmac,
+ new_packet_bytes, shared_secret, next_packet_pubkey_opt) {
+ Ok(info) => PendingHTLCStatus::Forward(info),
+ Err(InboundOnionErr { err_code, err_data, msg }) => return_err!(msg, err_code, &err_data)
+ }
}
}
}
///
/// [`channel_update`]: msgs::ChannelUpdate
/// [`internal_closing_signed`]: Self::internal_closing_signed
- fn get_channel_update_for_broadcast(&self, chan: &Channel<<SP::Target as SignerProvider>::Signer>) -> Result<msgs::ChannelUpdate, LightningError> {
+ fn get_channel_update_for_broadcast(&self, chan: &Channel<SP>) -> Result<msgs::ChannelUpdate, LightningError> {
if !chan.context.should_announce() {
return Err(LightningError {
err: "Cannot broadcast a channel_update for a private channel".to_owned(),
if chan.context.get_short_channel_id().is_none() {
return Err(LightningError{err: "Channel not yet established".to_owned(), action: msgs::ErrorAction::IgnoreError});
}
- log_trace!(self.logger, "Attempting to generate broadcast channel update for channel {}", log_bytes!(chan.context.channel_id()));
+ log_trace!(self.logger, "Attempting to generate broadcast channel update for channel {}", &chan.context.channel_id());
self.get_channel_update_for_unicast(chan)
}
///
/// [`channel_update`]: msgs::ChannelUpdate
/// [`internal_closing_signed`]: Self::internal_closing_signed
- fn get_channel_update_for_unicast(&self, chan: &Channel<<SP::Target as SignerProvider>::Signer>) -> Result<msgs::ChannelUpdate, LightningError> {
- log_trace!(self.logger, "Attempting to generate channel update for channel {}", log_bytes!(chan.context.channel_id()));
+ fn get_channel_update_for_unicast(&self, chan: &Channel<SP>) -> Result<msgs::ChannelUpdate, LightningError> {
+ log_trace!(self.logger, "Attempting to generate channel update for channel {}", &chan.context.channel_id());
let short_channel_id = match chan.context.get_short_channel_id().or(chan.context.latest_inbound_scid_alias()) {
None => return Err(LightningError{err: "Channel not yet established".to_owned(), action: msgs::ErrorAction::IgnoreError}),
Some(id) => id,
self.get_channel_update_for_onion(short_channel_id, chan)
}
- fn get_channel_update_for_onion(&self, short_channel_id: u64, chan: &Channel<<SP::Target as SignerProvider>::Signer>) -> Result<msgs::ChannelUpdate, LightningError> {
- log_trace!(self.logger, "Generating channel update for channel {}", log_bytes!(chan.context.channel_id()));
+ fn get_channel_update_for_onion(&self, short_channel_id: u64, chan: &Channel<SP>) -> Result<msgs::ChannelUpdate, LightningError> {
+ log_trace!(self.logger, "Generating channel update for channel {}", &chan.context.channel_id());
let were_node_one = self.our_network_pubkey.serialize()[..] < chan.context.get_counterparty_node_id().serialize()[..];
let enabled = chan.context.is_usable() && match chan.channel_update_status() {
#[cfg(test)]
pub(crate) fn test_send_payment_along_path(&self, path: &Path, payment_hash: &PaymentHash, recipient_onion: RecipientOnionFields, total_value: u64, cur_height: u32, payment_id: PaymentId, keysend_preimage: &Option<PaymentPreimage>, session_priv_bytes: [u8; 32]) -> Result<(), APIError> {
let _lck = self.total_consistency_lock.read().unwrap();
- self.send_payment_along_path(path, payment_hash, recipient_onion, total_value, cur_height, payment_id, keysend_preimage, session_priv_bytes)
+ self.send_payment_along_path(SendAlongPathArgs {
+ path, payment_hash, recipient_onion, total_value, cur_height, payment_id, keysend_preimage,
+ session_priv_bytes
+ })
}
- fn send_payment_along_path(&self, path: &Path, payment_hash: &PaymentHash, recipient_onion: RecipientOnionFields, total_value: u64, cur_height: u32, payment_id: PaymentId, keysend_preimage: &Option<PaymentPreimage>, session_priv_bytes: [u8; 32]) -> Result<(), APIError> {
+ fn send_payment_along_path(&self, args: SendAlongPathArgs) -> Result<(), APIError> {
+ let SendAlongPathArgs {
+ path, payment_hash, recipient_onion, total_value, cur_height, payment_id, keysend_preimage,
+ session_priv_bytes
+ } = args;
// The top-level caller should hold the total_consistency_lock read lock.
debug_assert!(self.total_consistency_lock.try_write().is_err());
- log_trace!(self.logger, "Attempting to send payment for path with next hop {}", path.hops.first().unwrap().short_channel_id);
+ log_trace!(self.logger,
+ "Attempting to send payment with payment hash {} along path with next hop {}",
+ payment_hash, path.hops.first().unwrap().short_channel_id);
let prng_seed = self.entropy_source.get_secure_random_bytes();
let session_priv = SecretKey::from_slice(&session_priv_bytes[..]).expect("RNG is busted");
.ok_or_else(|| APIError::ChannelUnavailable{err: "No peer matching the path's first hop found!".to_owned() })?;
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
let peer_state = &mut *peer_state_lock;
- if let hash_map::Entry::Occupied(mut chan) = peer_state.channel_by_id.entry(id) {
- if !chan.get().context.is_live() {
- return Err(APIError::ChannelUnavailable{err: "Peer for first hop currently disconnected".to_owned()});
- }
- let funding_txo = chan.get().context.get_funding_txo().unwrap();
- let send_res = chan.get_mut().send_htlc_and_commit(htlc_msat, payment_hash.clone(),
- htlc_cltv, HTLCSource::OutboundRoute {
- path: path.clone(),
- session_priv: session_priv.clone(),
- first_hop_htlc_msat: htlc_msat,
- payment_id,
- }, onion_packet, None, &self.logger);
- match break_chan_entry!(self, send_res, chan) {
- Some(monitor_update) => {
- match handle_new_monitor_update!(self, funding_txo, monitor_update, peer_state_lock, peer_state, per_peer_state, chan) {
- Err(e) => break Err(e),
- Ok(false) => {
- // Note that MonitorUpdateInProgress here indicates (per function
- // docs) that we will resend the commitment update once monitor
- // updating completes. Therefore, we must return an error
- // indicating that it is unsafe to retry the payment wholesale,
- // which we do in the send_payment check for
- // MonitorUpdateInProgress, below.
- return Err(APIError::MonitorUpdateInProgress);
+ if let hash_map::Entry::Occupied(mut chan_phase_entry) = peer_state.channel_by_id.entry(id) {
+ match chan_phase_entry.get_mut() {
+ ChannelPhase::Funded(chan) => {
+ if !chan.context.is_live() {
+ return Err(APIError::ChannelUnavailable{err: "Peer for first hop currently disconnected".to_owned()});
+ }
+ let funding_txo = chan.context.get_funding_txo().unwrap();
+ let send_res = chan.send_htlc_and_commit(htlc_msat, payment_hash.clone(),
+ htlc_cltv, HTLCSource::OutboundRoute {
+ path: path.clone(),
+ session_priv: session_priv.clone(),
+ first_hop_htlc_msat: htlc_msat,
+ payment_id,
+ }, onion_packet, None, &self.fee_estimator, &self.logger);
+ match break_chan_phase_entry!(self, send_res, chan_phase_entry) {
+ Some(monitor_update) => {
+ match handle_new_monitor_update!(self, funding_txo, monitor_update, peer_state_lock, peer_state, per_peer_state, chan) {
+ false => {
+ // Note that MonitorUpdateInProgress here indicates (per function
+ // docs) that we will resend the commitment update once monitor
+ // updating completes. Therefore, we must return an error
+ // indicating that it is unsafe to retry the payment wholesale,
+ // which we do in the send_payment check for
+ // MonitorUpdateInProgress, below.
+ return Err(APIError::MonitorUpdateInProgress);
+ },
+ true => {},
+ }
},
- Ok(true) => {},
+ None => {},
}
},
- None => { },
- }
+ _ => return Err(APIError::ChannelUnavailable{err: "Channel to first hop is unfunded".to_owned()}),
+ };
} else {
// The channel was likely removed after we fetched the id from the
// `short_to_chan_info` map, but before we successfully locked the
/// irrevocably committed to on our end. In such a case, do NOT retry the payment with a
/// different route unless you intend to pay twice!
///
+ /// [`RouteHop`]: crate::routing::router::RouteHop
/// [`Event::PaymentSent`]: events::Event::PaymentSent
/// [`Event::PaymentFailed`]: events::Event::PaymentFailed
/// [`UpdateHTLCs`]: events::MessageSendEvent::UpdateHTLCs
let best_block_height = self.best_block.read().unwrap().height();
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
self.pending_outbound_payments
- .send_payment_with_route(route, payment_hash, recipient_onion, payment_id, &self.entropy_source, &self.node_signer, best_block_height,
- |path, payment_hash, recipient_onion, total_value, cur_height, payment_id, keysend_preimage, session_priv|
- self.send_payment_along_path(path, payment_hash, recipient_onion, total_value, cur_height, payment_id, keysend_preimage, session_priv))
+ .send_payment_with_route(route, payment_hash, recipient_onion, payment_id,
+ &self.entropy_source, &self.node_signer, best_block_height,
+ |args| self.send_payment_along_path(args))
}
/// Similar to [`ChannelManager::send_payment_with_route`], but will automatically find a route based on
.send_payment(payment_hash, recipient_onion, payment_id, retry_strategy, route_params,
&self.router, self.list_usable_channels(), || self.compute_inflight_htlcs(),
&self.entropy_source, &self.node_signer, best_block_height, &self.logger,
- &self.pending_events,
- |path, payment_hash, recipient_onion, total_value, cur_height, payment_id, keysend_preimage, session_priv|
- self.send_payment_along_path(path, payment_hash, recipient_onion, total_value, cur_height, payment_id, keysend_preimage, session_priv))
+ &self.pending_events, |args| self.send_payment_along_path(args))
}
#[cfg(test)]
pub(super) fn test_send_payment_internal(&self, route: &Route, payment_hash: PaymentHash, recipient_onion: RecipientOnionFields, keysend_preimage: Option<PaymentPreimage>, payment_id: PaymentId, recv_value_msat: Option<u64>, onion_session_privs: Vec<[u8; 32]>) -> Result<(), PaymentSendFailure> {
let best_block_height = self.best_block.read().unwrap().height();
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
- self.pending_outbound_payments.test_send_payment_internal(route, payment_hash, recipient_onion, keysend_preimage, payment_id, recv_value_msat, onion_session_privs, &self.node_signer, best_block_height,
- |path, payment_hash, recipient_onion, total_value, cur_height, payment_id, keysend_preimage, session_priv|
- self.send_payment_along_path(path, payment_hash, recipient_onion, total_value, cur_height, payment_id, keysend_preimage, session_priv))
+ self.pending_outbound_payments.test_send_payment_internal(route, payment_hash, recipient_onion,
+ keysend_preimage, payment_id, recv_value_msat, onion_session_privs, &self.node_signer,
+ best_block_height, |args| self.send_payment_along_path(args))
}
#[cfg(test)]
}
- /// Signals that no further retries for the given payment should occur. Useful if you have a
+ /// 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
/// retries are exhausted.
///
+ /// # Event Generation
+ ///
/// If no [`Event::PaymentFailed`] event had been generated before, one will be generated as soon
/// as there are no remaining pending HTLCs for this payment.
///
/// wait until you receive either a [`Event::PaymentFailed`] or [`Event::PaymentSent`] event to
/// determine the ultimate status of a payment.
///
- /// If an [`Event::PaymentFailed`] event is generated and we restart without this
- /// [`ChannelManager`] having been persisted, another [`Event::PaymentFailed`] may be generated.
+ /// # Requested Invoices
///
- /// [`Event::PaymentFailed`]: events::Event::PaymentFailed
- /// [`Event::PaymentSent`]: events::Event::PaymentSent
+ /// In the case of paying a [`Bolt12Invoice`], 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; 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);
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
self.pending_outbound_payments.send_spontaneous_payment_with_route(
route, payment_preimage, recipient_onion, payment_id, &self.entropy_source,
- &self.node_signer, best_block_height,
- |path, payment_hash, recipient_onion, total_value, cur_height, payment_id, keysend_preimage, session_priv|
- self.send_payment_along_path(path, payment_hash, recipient_onion, total_value, cur_height, payment_id, keysend_preimage, session_priv))
+ &self.node_signer, best_block_height, |args| self.send_payment_along_path(args))
}
/// Similar to [`ChannelManager::send_spontaneous_payment`], but will automatically find a route
self.pending_outbound_payments.send_spontaneous_payment(payment_preimage, recipient_onion,
payment_id, retry_strategy, route_params, &self.router, self.list_usable_channels(),
|| self.compute_inflight_htlcs(), &self.entropy_source, &self.node_signer, best_block_height,
- &self.logger, &self.pending_events,
- |path, payment_hash, recipient_onion, total_value, cur_height, payment_id, keysend_preimage, session_priv|
- self.send_payment_along_path(path, payment_hash, recipient_onion, total_value, cur_height, payment_id, keysend_preimage, session_priv))
+ &self.logger, &self.pending_events, |args| self.send_payment_along_path(args))
}
/// Send a payment that is probing the given route for liquidity. We calculate the
pub fn send_probe(&self, path: Path) -> Result<(PaymentHash, PaymentId), PaymentSendFailure> {
let best_block_height = self.best_block.read().unwrap().height();
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
- self.pending_outbound_payments.send_probe(path, self.probing_cookie_secret, &self.entropy_source, &self.node_signer, best_block_height,
- |path, payment_hash, recipient_onion, total_value, cur_height, payment_id, keysend_preimage, session_priv|
- self.send_payment_along_path(path, payment_hash, recipient_onion, total_value, cur_height, payment_id, keysend_preimage, session_priv))
+ self.pending_outbound_payments.send_probe(path, self.probing_cookie_secret,
+ &self.entropy_source, &self.node_signer, best_block_height,
+ |args| self.send_payment_along_path(args))
}
/// Returns whether a payment with the given [`PaymentHash`] and [`PaymentId`] is, in fact, a
outbound_payment::payment_is_probe(payment_hash, payment_id, self.probing_cookie_secret)
}
+ /// Sends payment probes over all paths of a route that would be used to pay the given
+ /// amount to the given `node_id`.
+ ///
+ /// See [`ChannelManager::send_preflight_probes`] for more information.
+ pub fn send_spontaneous_preflight_probes(
+ &self, node_id: PublicKey, amount_msat: u64, final_cltv_expiry_delta: u32,
+ liquidity_limit_multiplier: Option<u64>,
+ ) -> Result<Vec<(PaymentHash, PaymentId)>, ProbeSendFailure> {
+ let payment_params =
+ PaymentParameters::from_node_id(node_id, final_cltv_expiry_delta);
+
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, amount_msat);
+
+ self.send_preflight_probes(route_params, liquidity_limit_multiplier)
+ }
+
+ /// Sends payment probes over all paths of a route that would be used to pay a route found
+ /// according to the given [`RouteParameters`].
+ ///
+ /// This may be used to send "pre-flight" probes, i.e., to train our scorer before conducting
+ /// the actual payment. Note this is only useful if there likely is sufficient time for the
+ /// probe to settle before sending out the actual payment, e.g., when waiting for user
+ /// confirmation in a wallet UI.
+ ///
+ /// Otherwise, there is a chance the probe could take up some liquidity needed to complete the
+ /// actual payment. Users should therefore be cautious and might avoid sending probes if
+ /// liquidity is scarce and/or they don't expect the probe to return before they send the
+ /// payment. To mitigate this issue, channels with available liquidity less than the required
+ /// amount times the given `liquidity_limit_multiplier` won't be used to send pre-flight
+ /// probes. If `None` is given as `liquidity_limit_multiplier`, it defaults to `3`.
+ pub fn send_preflight_probes(
+ &self, route_params: RouteParameters, liquidity_limit_multiplier: Option<u64>,
+ ) -> Result<Vec<(PaymentHash, PaymentId)>, ProbeSendFailure> {
+ let liquidity_limit_multiplier = liquidity_limit_multiplier.unwrap_or(3);
+
+ let payer = self.get_our_node_id();
+ let usable_channels = self.list_usable_channels();
+ let first_hops = usable_channels.iter().collect::<Vec<_>>();
+ let inflight_htlcs = self.compute_inflight_htlcs();
+
+ let route = self
+ .router
+ .find_route(&payer, &route_params, Some(&first_hops), inflight_htlcs)
+ .map_err(|e| {
+ log_error!(self.logger, "Failed to find path for payment probe: {:?}", e);
+ ProbeSendFailure::RouteNotFound
+ })?;
+
+ let mut used_liquidity_map = HashMap::with_capacity(first_hops.len());
+
+ let mut res = Vec::new();
+
+ for mut path in route.paths {
+ // If the last hop is probably an unannounced channel we refrain from probing all the
+ // way through to the end and instead probe up to the second-to-last channel.
+ while let Some(last_path_hop) = path.hops.last() {
+ if last_path_hop.maybe_announced_channel {
+ // We found a potentially announced last hop.
+ break;
+ } else {
+ // Drop the last hop, as it's likely unannounced.
+ log_debug!(
+ self.logger,
+ "Avoided sending payment probe all the way to last hop {} as it is likely unannounced.",
+ last_path_hop.short_channel_id
+ );
+ let final_value_msat = path.final_value_msat();
+ path.hops.pop();
+ if let Some(new_last) = path.hops.last_mut() {
+ new_last.fee_msat += final_value_msat;
+ }
+ }
+ }
+
+ if path.hops.len() < 2 {
+ log_debug!(
+ self.logger,
+ "Skipped sending payment probe over path with less than two hops."
+ );
+ continue;
+ }
+
+ if let Some(first_path_hop) = path.hops.first() {
+ if let Some(first_hop) = first_hops.iter().find(|h| {
+ h.get_outbound_payment_scid() == Some(first_path_hop.short_channel_id)
+ }) {
+ let path_value = path.final_value_msat() + path.fee_msat();
+ let used_liquidity =
+ used_liquidity_map.entry(first_path_hop.short_channel_id).or_insert(0);
+
+ if first_hop.next_outbound_htlc_limit_msat
+ < (*used_liquidity + path_value) * liquidity_limit_multiplier
+ {
+ log_debug!(self.logger, "Skipped sending payment probe to avoid putting channel {} under the liquidity limit.", first_path_hop.short_channel_id);
+ continue;
+ } else {
+ *used_liquidity += path_value;
+ }
+ }
+ }
+
+ res.push(self.send_probe(path).map_err(|e| {
+ log_error!(self.logger, "Failed to send pre-flight probe: {:?}", e);
+ ProbeSendFailure::SendingFailed(e)
+ })?);
+ }
+
+ Ok(res)
+ }
+
/// Handles the generation of a funding transaction, optionally (for tests) with a function
/// which checks the correctness of the funding transaction given the associated channel.
- fn funding_transaction_generated_intern<FundingOutput: Fn(&OutboundV1Channel<<SP::Target as SignerProvider>::Signer>, &Transaction) -> Result<OutPoint, APIError>>(
- &self, temporary_channel_id: &[u8; 32], counterparty_node_id: &PublicKey, funding_transaction: Transaction, find_funding_output: FundingOutput
+ fn funding_transaction_generated_intern<FundingOutput: FnMut(&OutboundV1Channel<SP>, &Transaction) -> Result<OutPoint, APIError>>(
+ &self, temporary_channel_id: &ChannelId, counterparty_node_id: &PublicKey, funding_transaction: Transaction, is_batch_funding: bool,
+ mut find_funding_output: FundingOutput,
) -> Result<(), APIError> {
let per_peer_state = self.per_peer_state.read().unwrap();
let peer_state_mutex = per_peer_state.get(counterparty_node_id)
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
let peer_state = &mut *peer_state_lock;
- let (chan, msg) = match peer_state.outbound_v1_channel_by_id.remove(temporary_channel_id) {
- Some(chan) => {
+ let (chan, msg) = match peer_state.channel_by_id.remove(temporary_channel_id) {
+ Some(ChannelPhase::UnfundedOutboundV1(chan)) => {
let funding_txo = find_funding_output(&chan, &funding_transaction)?;
- let funding_res = chan.get_outbound_funding_created(funding_transaction, funding_txo, &self.logger)
+ let funding_res = chan.get_funding_created(funding_transaction, funding_txo, is_batch_funding, &self.logger)
.map_err(|(mut chan, e)| if let ChannelError::Close(msg) = e {
let channel_id = chan.context.channel_id();
let user_id = chan.context.get_user_id();
let shutdown_res = chan.context.force_shutdown(false);
- (chan, MsgHandleErrInternal::from_finish_shutdown(msg, channel_id, user_id, shutdown_res, None))
+ let channel_capacity = chan.context.get_value_satoshis();
+ (chan, MsgHandleErrInternal::from_finish_shutdown(msg, channel_id, user_id, shutdown_res, None, channel_capacity))
} else { unreachable!(); });
match funding_res {
Ok((chan, funding_msg)) => (chan, funding_msg),
},
}
},
- None => {
- return Err(APIError::ChannelUnavailable {
+ Some(phase) => {
+ peer_state.channel_by_id.insert(*temporary_channel_id, phase);
+ return Err(APIError::APIMisuseError {
err: format!(
- "Channel with id {} not found for the passed counterparty node_id {}",
- log_bytes!(*temporary_channel_id), counterparty_node_id),
+ "Channel with id {} for the passed counterparty node_id {} is not an unfunded, outbound V1 channel",
+ temporary_channel_id, counterparty_node_id),
})
},
+ None => return Err(APIError::ChannelUnavailable {err: format!(
+ "Channel with id {} not found for the passed counterparty node_id {}",
+ temporary_channel_id, counterparty_node_id),
+ }),
};
peer_state.pending_msg_events.push(events::MessageSendEvent::SendFundingCreated {
if id_to_peer.insert(chan.context.channel_id(), chan.context.get_counterparty_node_id()).is_some() {
panic!("id_to_peer map already contained funding txid, which shouldn't be possible");
}
- e.insert(chan);
+ e.insert(ChannelPhase::Funded(chan));
}
}
Ok(())
}
#[cfg(test)]
- pub(crate) fn funding_transaction_generated_unchecked(&self, temporary_channel_id: &[u8; 32], counterparty_node_id: &PublicKey, funding_transaction: Transaction, output_index: u16) -> Result<(), APIError> {
- self.funding_transaction_generated_intern(temporary_channel_id, counterparty_node_id, funding_transaction, |_, tx| {
+ pub(crate) fn funding_transaction_generated_unchecked(&self, temporary_channel_id: &ChannelId, counterparty_node_id: &PublicKey, funding_transaction: Transaction, output_index: u16) -> Result<(), APIError> {
+ self.funding_transaction_generated_intern(temporary_channel_id, counterparty_node_id, funding_transaction, false, |_, tx| {
Ok(OutPoint { txid: tx.txid(), index: output_index })
})
}
///
/// [`Event::FundingGenerationReady`]: crate::events::Event::FundingGenerationReady
/// [`Event::ChannelClosed`]: crate::events::Event::ChannelClosed
- pub fn funding_transaction_generated(&self, temporary_channel_id: &[u8; 32], counterparty_node_id: &PublicKey, funding_transaction: Transaction) -> Result<(), APIError> {
+ pub fn funding_transaction_generated(&self, temporary_channel_id: &ChannelId, counterparty_node_id: &PublicKey, funding_transaction: Transaction) -> Result<(), APIError> {
+ self.batch_funding_transaction_generated(&[(temporary_channel_id, counterparty_node_id)], funding_transaction)
+ }
+
+ /// Call this upon creation of a batch funding transaction for the given channels.
+ ///
+ /// 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
+ /// will only be broadcast when we have safely received and persisted the counterparty's
+ /// signature for each channel.
+ ///
+ /// If there is an error, all channels in the batch are to be considered closed.
+ pub fn batch_funding_transaction_generated(&self, temporary_channels: &[(&ChannelId, &PublicKey)], funding_transaction: Transaction) -> Result<(), APIError> {
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
+ let mut result = Ok(());
- for inp in funding_transaction.input.iter() {
- if inp.witness.is_empty() {
- return Err(APIError::APIMisuseError {
- err: "Funding transaction must be fully signed and spend Segwit outputs".to_owned()
- });
+ if !funding_transaction.is_coin_base() {
+ for inp in funding_transaction.input.iter() {
+ if inp.witness.is_empty() {
+ result = result.and(Err(APIError::APIMisuseError {
+ err: "Funding transaction must be fully signed and spend Segwit outputs".to_owned()
+ }));
+ }
}
}
+ if funding_transaction.output.len() > u16::max_value() as usize {
+ result = result.and(Err(APIError::APIMisuseError {
+ err: "Transaction had more than 2^16 outputs, which is not supported".to_owned()
+ }));
+ }
{
let height = self.best_block.read().unwrap().height();
// Transactions are evaluated as final by network mempools if their locktime is strictly
// node might not have perfect sync about their blockchain views. Thus, if the wallet
// module is ahead of LDK, only allow one more block of headroom.
if !funding_transaction.input.iter().all(|input| input.sequence == Sequence::MAX) && LockTime::from(funding_transaction.lock_time).is_block_height() && funding_transaction.lock_time.0 > height + 1 {
- return Err(APIError::APIMisuseError {
+ result = result.and(Err(APIError::APIMisuseError {
err: "Funding transaction absolute timelock is non-final".to_owned()
- });
+ }));
}
}
- self.funding_transaction_generated_intern(temporary_channel_id, counterparty_node_id, funding_transaction, |chan, tx| {
- if tx.output.len() > u16::max_value() as usize {
- return Err(APIError::APIMisuseError {
- err: "Transaction had more than 2^16 outputs, which is not supported".to_owned()
- });
- }
- let mut output_index = None;
- let expected_spk = chan.context.get_funding_redeemscript().to_v0_p2wsh();
- for (idx, outp) in tx.output.iter().enumerate() {
- if outp.script_pubkey == expected_spk && outp.value == chan.context.get_value_satoshis() {
- if output_index.is_some() {
+ let txid = funding_transaction.txid();
+ let is_batch_funding = temporary_channels.len() > 1;
+ let mut funding_batch_states = if is_batch_funding {
+ Some(self.funding_batch_states.lock().unwrap())
+ } else {
+ None
+ };
+ let mut funding_batch_state = funding_batch_states.as_mut().and_then(|states| {
+ match states.entry(txid) {
+ btree_map::Entry::Occupied(_) => {
+ result = result.clone().and(Err(APIError::APIMisuseError {
+ err: "Batch funding transaction with the same txid already exists".to_owned()
+ }));
+ None
+ },
+ btree_map::Entry::Vacant(vacant) => Some(vacant.insert(Vec::new())),
+ }
+ });
+ for (channel_idx, &(temporary_channel_id, counterparty_node_id)) in temporary_channels.iter().enumerate() {
+ result = result.and_then(|_| self.funding_transaction_generated_intern(
+ temporary_channel_id,
+ counterparty_node_id,
+ funding_transaction.clone(),
+ is_batch_funding,
+ |chan, tx| {
+ let mut output_index = None;
+ let expected_spk = chan.context.get_funding_redeemscript().to_v0_p2wsh();
+ for (idx, outp) in tx.output.iter().enumerate() {
+ if outp.script_pubkey == expected_spk && outp.value == chan.context.get_value_satoshis() {
+ if output_index.is_some() {
+ return Err(APIError::APIMisuseError {
+ err: "Multiple outputs matched the expected script and value".to_owned()
+ });
+ }
+ output_index = Some(idx as u16);
+ }
+ }
+ if output_index.is_none() {
return Err(APIError::APIMisuseError {
- err: "Multiple outputs matched the expected script and value".to_owned()
+ err: "No output matched the script_pubkey and value in the FundingGenerationReady event".to_owned()
});
}
- output_index = Some(idx as u16);
+ let outpoint = OutPoint { txid: tx.txid(), index: output_index.unwrap() };
+ if let Some(funding_batch_state) = funding_batch_state.as_mut() {
+ funding_batch_state.push((outpoint.to_channel_id(), *counterparty_node_id, false));
+ }
+ Ok(outpoint)
+ })
+ );
+ }
+ if let Err(ref e) = result {
+ // Remaining channels need to be removed on any error.
+ let e = format!("Error in transaction funding: {:?}", e);
+ let mut channels_to_remove = Vec::new();
+ channels_to_remove.extend(funding_batch_states.as_mut()
+ .and_then(|states| states.remove(&txid))
+ .into_iter().flatten()
+ .map(|(chan_id, node_id, _state)| (chan_id, node_id))
+ );
+ channels_to_remove.extend(temporary_channels.iter()
+ .map(|(&chan_id, &node_id)| (chan_id, node_id))
+ );
+ let mut shutdown_results = Vec::new();
+ {
+ let per_peer_state = self.per_peer_state.read().unwrap();
+ for (channel_id, counterparty_node_id) in channels_to_remove {
+ per_peer_state.get(&counterparty_node_id)
+ .map(|peer_state_mutex| peer_state_mutex.lock().unwrap())
+ .and_then(|mut peer_state| peer_state.channel_by_id.remove(&channel_id))
+ .map(|mut chan| {
+ update_maps_on_chan_removal!(self, &chan.context());
+ self.issue_channel_close_events(&chan.context(), ClosureReason::ProcessingError { err: e.clone() });
+ shutdown_results.push(chan.context_mut().force_shutdown(false));
+ });
}
}
- if output_index.is_none() {
- return Err(APIError::APIMisuseError {
- err: "No output matched the script_pubkey and value in the FundingGenerationReady event".to_owned()
- });
+ for shutdown_result in shutdown_results.drain(..) {
+ self.finish_close_channel(shutdown_result);
}
- Ok(OutPoint { txid: tx.txid(), index: output_index.unwrap() })
- })
+ }
+ result
}
/// Atomically applies partial updates to the [`ChannelConfig`] of the given channels.
/// [`ChannelUnavailable`]: APIError::ChannelUnavailable
/// [`APIMisuseError`]: APIError::APIMisuseError
pub fn update_partial_channel_config(
- &self, counterparty_node_id: &PublicKey, channel_ids: &[[u8; 32]], config_update: &ChannelConfigUpdate,
+ &self, counterparty_node_id: &PublicKey, channel_ids: &[ChannelId], config_update: &ChannelConfigUpdate,
) -> Result<(), APIError> {
if config_update.cltv_expiry_delta.map(|delta| delta < MIN_CLTV_EXPIRY_DELTA).unwrap_or(false) {
return Err(APIError::APIMisuseError {
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
let peer_state = &mut *peer_state_lock;
for channel_id in channel_ids {
- if !peer_state.channel_by_id.contains_key(channel_id) {
+ if !peer_state.has_channel(channel_id) {
return Err(APIError::ChannelUnavailable {
- err: format!("Channel with ID {} was not found for the passed counterparty_node_id {}", log_bytes!(*channel_id), counterparty_node_id),
+ err: format!("Channel with ID {} was not found for the passed counterparty_node_id {}", channel_id, counterparty_node_id),
});
- }
+ };
}
for channel_id in channel_ids {
- let channel = peer_state.channel_by_id.get_mut(channel_id).unwrap();
- let mut config = channel.context.config();
- config.apply(config_update);
- if !channel.context.update_config(&config) {
+ if let Some(channel_phase) = peer_state.channel_by_id.get_mut(channel_id) {
+ let mut config = channel_phase.context().config();
+ config.apply(config_update);
+ if !channel_phase.context_mut().update_config(&config) {
+ continue;
+ }
+ if let ChannelPhase::Funded(channel) = channel_phase {
+ if let Ok(msg) = self.get_channel_update_for_broadcast(channel) {
+ peer_state.pending_msg_events.push(events::MessageSendEvent::BroadcastChannelUpdate { msg });
+ } else if let Ok(msg) = self.get_channel_update_for_unicast(channel) {
+ peer_state.pending_msg_events.push(events::MessageSendEvent::SendChannelUpdate {
+ node_id: channel.context.get_counterparty_node_id(),
+ msg,
+ });
+ }
+ }
continue;
- }
- if let Ok(msg) = self.get_channel_update_for_broadcast(channel) {
- peer_state.pending_msg_events.push(events::MessageSendEvent::BroadcastChannelUpdate { msg });
- } else if let Ok(msg) = self.get_channel_update_for_unicast(channel) {
- peer_state.pending_msg_events.push(events::MessageSendEvent::SendChannelUpdate {
- node_id: channel.context.get_counterparty_node_id(),
- msg,
+ } else {
+ // This should not be reachable as we've already checked for non-existence in the previous channel_id loop.
+ debug_assert!(false);
+ return Err(APIError::ChannelUnavailable {
+ err: format!(
+ "Channel with ID {} for passed counterparty_node_id {} disappeared after we confirmed its existence - this should not be reachable!",
+ channel_id, counterparty_node_id),
});
- }
+ };
}
Ok(())
}
/// [`ChannelUnavailable`]: APIError::ChannelUnavailable
/// [`APIMisuseError`]: APIError::APIMisuseError
pub fn update_channel_config(
- &self, counterparty_node_id: &PublicKey, channel_ids: &[[u8; 32]], config: &ChannelConfig,
+ &self, counterparty_node_id: &PublicKey, channel_ids: &[ChannelId], config: &ChannelConfig,
) -> Result<(), APIError> {
return self.update_partial_channel_config(counterparty_node_id, channel_ids, &(*config).into());
}
/// [`HTLCIntercepted::expected_outbound_amount_msat`]: events::Event::HTLCIntercepted::expected_outbound_amount_msat
// TODO: when we move to deciding the best outbound channel at forward time, only take
// `next_node_id` and not `next_hop_channel_id`
- pub fn forward_intercepted_htlc(&self, intercept_id: InterceptId, next_hop_channel_id: &[u8; 32], next_node_id: PublicKey, amt_to_forward_msat: u64) -> Result<(), APIError> {
+ pub fn forward_intercepted_htlc(&self, intercept_id: InterceptId, next_hop_channel_id: &ChannelId, next_node_id: PublicKey, amt_to_forward_msat: u64) -> Result<(), APIError> {
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
let next_hop_scid = {
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
let peer_state = &mut *peer_state_lock;
match peer_state.channel_by_id.get(next_hop_channel_id) {
- Some(chan) => {
+ Some(ChannelPhase::Funded(chan)) => {
if !chan.context.is_usable() {
return Err(APIError::ChannelUnavailable {
- err: format!("Channel with id {} not fully established", log_bytes!(*next_hop_channel_id))
+ err: format!("Channel with id {} not fully established", next_hop_channel_id)
})
}
chan.context.get_short_channel_id().unwrap_or(chan.context.outbound_scid_alias())
},
+ Some(_) => return Err(APIError::ChannelUnavailable {
+ err: format!("Channel with id {} for the passed counterparty node_id {} is still opening.",
+ next_hop_channel_id, next_node_id)
+ }),
None => return Err(APIError::ChannelUnavailable {
- err: format!("Funded channel with id {} not found for the passed counterparty node_id {}. Channel may still be opening.",
- log_bytes!(*next_hop_channel_id), next_node_id)
+ err: format!("Channel with id {} not found for the passed counterparty node_id {}.",
+ next_hop_channel_id, next_node_id)
})
}
};
if let PendingHTLCRouting::Forward { short_channel_id, .. } = payment.forward_info.routing {
let htlc_source = HTLCSource::PreviousHopData(HTLCPreviousHopData {
short_channel_id: payment.prev_short_channel_id,
+ user_channel_id: Some(payment.prev_user_channel_id),
outpoint: payment.prev_funding_outpoint,
htlc_id: payment.prev_htlc_id,
incoming_packet_shared_secret: payment.forward_info.incoming_shared_secret,
let htlc_source = HTLCSource::PreviousHopData(HTLCPreviousHopData {
short_channel_id: prev_short_channel_id,
+ user_channel_id: Some(prev_user_channel_id),
outpoint: prev_funding_outpoint,
htlc_id: prev_htlc_id,
incoming_packet_shared_secret: incoming_shared_secret,
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) {
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, payment_hash) {
+ let next_hop = match onion_utils::decode_next_payment_hop(
+ phantom_shared_secret, &onion_packet.hop_data, onion_packet.hmac,
+ payment_hash, &self.node_signer
+ ) {
Ok(res) => res,
Err(onion_utils::OnionDecodeErr::Malformed { err_msg, err_code }) => {
let sha256_of_onion = Sha256::hash(&onion_packet.hop_data).into_inner();
outgoing_cltv_value, Some(phantom_shared_secret), false, None)
{
Ok(info) => phantom_receives.push((prev_short_channel_id, prev_funding_outpoint, prev_user_channel_id, vec![(info, prev_htlc_id)])),
- Err(ReceiveError { err_code, err_data, msg }) => failed_payment!(msg, err_code, err_data, Some(phantom_shared_secret))
+ Err(InboundOnionErr { err_code, err_data, msg }) => failed_payment!(msg, err_code, err_data, Some(phantom_shared_secret))
}
},
_ => panic!(),
}
let mut peer_state_lock = peer_state_mutex_opt.unwrap().lock().unwrap();
let peer_state = &mut *peer_state_lock;
- match peer_state.channel_by_id.entry(forward_chan_id) {
- hash_map::Entry::Vacant(_) => {
- forwarding_channel_not_found!();
- continue;
- },
- hash_map::Entry::Occupied(mut chan) => {
- for forward_info in pending_forwards.drain(..) {
- match forward_info {
- HTLCForwardInfo::AddHTLC(PendingAddHTLCInfo {
- prev_short_channel_id, prev_htlc_id, prev_funding_outpoint, prev_user_channel_id: _,
- forward_info: PendingHTLCInfo {
- incoming_shared_secret, payment_hash, outgoing_amt_msat, outgoing_cltv_value,
- routing: PendingHTLCRouting::Forward { onion_packet, .. }, skimmed_fee_msat, ..
- },
- }) => {
- log_trace!(self.logger, "Adding HTLC from short id {} with payment_hash {} to channel with short id {} after delay", prev_short_channel_id, log_bytes!(payment_hash.0), short_chan_id);
- let htlc_source = HTLCSource::PreviousHopData(HTLCPreviousHopData {
- short_channel_id: prev_short_channel_id,
- outpoint: prev_funding_outpoint,
- htlc_id: prev_htlc_id,
- incoming_packet_shared_secret: incoming_shared_secret,
- // Phantom payments are only PendingHTLCRouting::Receive.
- phantom_shared_secret: None,
- });
- if let Err(e) = chan.get_mut().queue_add_htlc(outgoing_amt_msat,
- payment_hash, outgoing_cltv_value, htlc_source.clone(),
- onion_packet, skimmed_fee_msat, &self.logger)
- {
- if let ChannelError::Ignore(msg) = e {
- log_trace!(self.logger, "Failed to forward HTLC with payment_hash {}: {}", log_bytes!(payment_hash.0), msg);
- } else {
- panic!("Stated return value requirements in send_htlc() were not met");
- }
- let (failure_code, data) = self.get_htlc_temp_fail_err_and_data(0x1000|7, short_chan_id, chan.get());
- failed_forwards.push((htlc_source, payment_hash,
- HTLCFailReason::reason(failure_code, data),
- HTLCDestination::NextHopChannel { node_id: Some(chan.get().context.get_counterparty_node_id()), channel_id: forward_chan_id }
- ));
- continue;
- }
- },
- HTLCForwardInfo::AddHTLC { .. } => {
- panic!("short_channel_id != 0 should imply any pending_forward entries are of type Forward");
+ if let Some(ChannelPhase::Funded(ref mut chan)) = peer_state.channel_by_id.get_mut(&forward_chan_id) {
+ for forward_info in pending_forwards.drain(..) {
+ match forward_info {
+ HTLCForwardInfo::AddHTLC(PendingAddHTLCInfo {
+ prev_short_channel_id, prev_htlc_id, prev_funding_outpoint, prev_user_channel_id,
+ forward_info: PendingHTLCInfo {
+ incoming_shared_secret, payment_hash, outgoing_amt_msat, outgoing_cltv_value,
+ routing: PendingHTLCRouting::Forward { onion_packet, .. }, skimmed_fee_msat, ..
},
- HTLCForwardInfo::FailHTLC { htlc_id, err_packet } => {
- log_trace!(self.logger, "Failing HTLC back to channel with short id {} (backward HTLC ID {}) after delay", short_chan_id, htlc_id);
- if let Err(e) = chan.get_mut().queue_fail_htlc(
- htlc_id, err_packet, &self.logger
- ) {
- if let ChannelError::Ignore(msg) = e {
- log_trace!(self.logger, "Failed to fail HTLC with ID {} backwards to short_id {}: {}", htlc_id, short_chan_id, msg);
- } else {
- panic!("Stated return value requirements in queue_fail_htlc() were not met");
- }
- // fail-backs are best-effort, we probably already have one
- // pending, and if not that's OK, if not, the channel is on
- // the chain and sending the HTLC-Timeout is their problem.
- continue;
+ }) => {
+ log_trace!(self.logger, "Adding HTLC from short id {} with payment_hash {} to channel with short id {} after delay", prev_short_channel_id, &payment_hash, short_chan_id);
+ let htlc_source = HTLCSource::PreviousHopData(HTLCPreviousHopData {
+ short_channel_id: prev_short_channel_id,
+ user_channel_id: Some(prev_user_channel_id),
+ outpoint: prev_funding_outpoint,
+ htlc_id: prev_htlc_id,
+ incoming_packet_shared_secret: incoming_shared_secret,
+ // Phantom payments are only PendingHTLCRouting::Receive.
+ phantom_shared_secret: None,
+ });
+ if let Err(e) = chan.queue_add_htlc(outgoing_amt_msat,
+ payment_hash, outgoing_cltv_value, htlc_source.clone(),
+ onion_packet, skimmed_fee_msat, &self.fee_estimator,
+ &self.logger)
+ {
+ if let ChannelError::Ignore(msg) = e {
+ log_trace!(self.logger, "Failed to forward HTLC with payment_hash {}: {}", &payment_hash, msg);
+ } else {
+ panic!("Stated return value requirements in send_htlc() were not met");
}
- },
- }
+ let (failure_code, data) = self.get_htlc_temp_fail_err_and_data(0x1000|7, short_chan_id, chan);
+ failed_forwards.push((htlc_source, payment_hash,
+ HTLCFailReason::reason(failure_code, data),
+ HTLCDestination::NextHopChannel { node_id: Some(chan.context.get_counterparty_node_id()), channel_id: forward_chan_id }
+ ));
+ continue;
+ }
+ },
+ HTLCForwardInfo::AddHTLC { .. } => {
+ panic!("short_channel_id != 0 should imply any pending_forward entries are of type Forward");
+ },
+ HTLCForwardInfo::FailHTLC { htlc_id, err_packet } => {
+ log_trace!(self.logger, "Failing HTLC back to channel with short id {} (backward HTLC ID {}) after delay", short_chan_id, htlc_id);
+ if let Err(e) = chan.queue_fail_htlc(
+ htlc_id, err_packet, &self.logger
+ ) {
+ if let ChannelError::Ignore(msg) = e {
+ log_trace!(self.logger, "Failed to fail HTLC with ID {} backwards to short_id {}: {}", htlc_id, short_chan_id, msg);
+ } else {
+ panic!("Stated return value requirements in queue_fail_htlc() were not met");
+ }
+ // fail-backs are best-effort, we probably already have one
+ // pending, and if not that's OK, if not, the channel is on
+ // the chain and sending the HTLC-Timeout is their problem.
+ continue;
+ }
+ },
}
}
+ } else {
+ forwarding_channel_not_found!();
+ continue;
}
} else {
'next_forwardable_htlc: for forward_info in pending_forwards.drain(..) {
}
}) => {
let (cltv_expiry, onion_payload, payment_data, phantom_shared_secret, mut onion_fields) = match routing {
- PendingHTLCRouting::Receive { payment_data, payment_metadata, incoming_cltv_expiry, phantom_shared_secret } => {
+ PendingHTLCRouting::Receive { payment_data, payment_metadata, incoming_cltv_expiry, phantom_shared_secret, custom_tlvs } => {
let _legacy_hop_data = Some(payment_data.clone());
- let onion_fields =
- RecipientOnionFields { payment_secret: Some(payment_data.payment_secret), payment_metadata };
+ let onion_fields = RecipientOnionFields { payment_secret: Some(payment_data.payment_secret),
+ payment_metadata, custom_tlvs };
(incoming_cltv_expiry, OnionPayload::Invoice { _legacy_hop_data },
Some(payment_data), phantom_shared_secret, onion_fields)
},
- PendingHTLCRouting::ReceiveKeysend { payment_data, payment_preimage, payment_metadata, incoming_cltv_expiry } => {
+ PendingHTLCRouting::ReceiveKeysend { payment_data, payment_preimage, payment_metadata, incoming_cltv_expiry, custom_tlvs } => {
let onion_fields = RecipientOnionFields {
payment_secret: payment_data.as_ref().map(|data| data.payment_secret),
- payment_metadata
+ payment_metadata,
+ custom_tlvs,
};
(incoming_cltv_expiry, OnionPayload::Spontaneous(payment_preimage),
payment_data, None, onion_fields)
let claimable_htlc = ClaimableHTLC {
prev_hop: HTLCPreviousHopData {
short_channel_id: prev_short_channel_id,
+ user_channel_id: Some(prev_user_channel_id),
outpoint: prev_funding_outpoint,
htlc_id: prev_htlc_id,
incoming_packet_shared_secret: incoming_shared_secret,
);
failed_forwards.push((HTLCSource::PreviousHopData(HTLCPreviousHopData {
short_channel_id: $htlc.prev_hop.short_channel_id,
+ user_channel_id: $htlc.prev_hop.user_channel_id,
outpoint: prev_funding_outpoint,
htlc_id: $htlc.prev_hop.htlc_id,
incoming_packet_shared_secret: $htlc.prev_hop.incoming_packet_shared_secret,
});
if $purpose != claimable_payment.purpose {
let log_keysend = |keysend| if keysend { "keysend" } else { "non-keysend" };
- log_trace!(self.logger, "Failing new {} HTLC with payment_hash {} as we already had an existing {} HTLC with the same payment hash", log_keysend(is_keysend), log_bytes!(payment_hash.0), log_keysend(!is_keysend));
+ log_trace!(self.logger, "Failing new {} HTLC with payment_hash {} as we already had an existing {} HTLC with the same payment hash", log_keysend(is_keysend), &payment_hash, log_keysend(!is_keysend));
fail_htlc!(claimable_htlc, payment_hash);
}
if !self.default_configuration.accept_mpp_keysend && is_keysend && !claimable_payment.htlcs.is_empty() {
- log_trace!(self.logger, "Failing new keysend HTLC with payment_hash {} as we already had an existing keysend HTLC with the same payment hash and our config states we don't accept MPP keysend", log_bytes!(payment_hash.0));
+ log_trace!(self.logger, "Failing new keysend HTLC with payment_hash {} as we already had an existing keysend HTLC with the same payment hash and our config states we don't accept MPP keysend", &payment_hash);
fail_htlc!(claimable_htlc, payment_hash);
}
if let Some(earlier_fields) = &mut claimable_payment.onion_fields {
earliest_expiry = cmp::min(earliest_expiry, htlc.cltv_expiry);
if htlc.total_msat != claimable_htlc.total_msat {
log_trace!(self.logger, "Failing HTLCs with payment_hash {} as the HTLCs had inconsistent total values (eg {} and {})",
- log_bytes!(payment_hash.0), claimable_htlc.total_msat, htlc.total_msat);
+ &payment_hash, claimable_htlc.total_msat, htlc.total_msat);
total_value = msgs::MAX_VALUE_MSAT;
}
if total_value >= msgs::MAX_VALUE_MSAT { break; }
fail_htlc!(claimable_htlc, payment_hash);
} else if total_value - claimable_htlc.sender_intended_value >= claimable_htlc.total_msat {
log_trace!(self.logger, "Failing HTLC with payment_hash {} as payment is already claimable",
- log_bytes!(payment_hash.0));
+ &payment_hash);
fail_htlc!(claimable_htlc, payment_hash);
} else if total_value >= claimable_htlc.total_msat {
#[allow(unused_assignments)] {
let (payment_preimage, min_final_cltv_expiry_delta) = match inbound_payment::verify(payment_hash, &payment_data, self.highest_seen_timestamp.load(Ordering::Acquire) as u64, &self.inbound_payment_key, &self.logger) {
Ok(result) => result,
Err(()) => {
- log_trace!(self.logger, "Failing new HTLC with payment_hash {} as payment verification failed", log_bytes!(payment_hash.0));
+ log_trace!(self.logger, "Failing new HTLC with payment_hash {} as payment verification failed", &payment_hash);
fail_htlc!(claimable_htlc, payment_hash);
}
};
let expected_min_expiry_height = (self.current_best_block().height() + min_final_cltv_expiry_delta as u32) as u64;
if (cltv_expiry as u64) < expected_min_expiry_height {
log_trace!(self.logger, "Failing new HTLC with payment_hash {} as its CLTV expiry was too soon (had {}, earliest expected {})",
- log_bytes!(payment_hash.0), cltv_expiry, expected_min_expiry_height);
+ &payment_hash, cltv_expiry, expected_min_expiry_height);
fail_htlc!(claimable_htlc, payment_hash);
}
}
},
hash_map::Entry::Occupied(inbound_payment) => {
if let OnionPayload::Spontaneous(_) = claimable_htlc.onion_payload {
- log_trace!(self.logger, "Failing new keysend HTLC with payment_hash {} because we already have an inbound payment with the same payment hash", log_bytes!(payment_hash.0));
+ log_trace!(self.logger, "Failing new keysend HTLC with payment_hash {} because we already have an inbound payment with the same payment hash", &payment_hash);
fail_htlc!(claimable_htlc, payment_hash);
}
let payment_data = payment_data.unwrap();
if inbound_payment.get().payment_secret != payment_data.payment_secret {
- log_trace!(self.logger, "Failing new HTLC with payment_hash {} as it didn't match our expected payment secret.", log_bytes!(payment_hash.0));
+ log_trace!(self.logger, "Failing new HTLC with payment_hash {} as it didn't match our expected payment secret.", &payment_hash);
fail_htlc!(claimable_htlc, payment_hash);
} else if inbound_payment.get().min_value_msat.is_some() && payment_data.total_msat < inbound_payment.get().min_value_msat.unwrap() {
log_trace!(self.logger, "Failing new HTLC with payment_hash {} as it didn't match our minimum value (had {}, needed {}).",
- log_bytes!(payment_hash.0), payment_data.total_msat, inbound_payment.get().min_value_msat.unwrap());
+ &payment_hash, payment_data.total_msat, inbound_payment.get().min_value_msat.unwrap());
fail_htlc!(claimable_htlc, payment_hash);
} else {
let purpose = events::PaymentPurpose::InvoicePayment {
let best_block_height = self.best_block.read().unwrap().height();
self.pending_outbound_payments.check_retry_payments(&self.router, || self.list_usable_channels(),
|| self.compute_inflight_htlcs(), &self.entropy_source, &self.node_signer, best_block_height,
- &self.pending_events, &self.logger,
- |path, payment_hash, recipient_onion, total_value, cur_height, payment_id, keysend_preimage, session_priv|
- self.send_payment_along_path(path, payment_hash, recipient_onion, total_value, cur_height, payment_id, keysend_preimage, session_priv));
+ &self.pending_events, &self.logger, |args| self.send_payment_along_path(args));
for (htlc_source, payment_hash, failure_reason, destination) in failed_forwards.drain(..) {
self.fail_htlc_backwards_internal(&htlc_source, &payment_hash, &failure_reason, destination);
fn process_background_events(&self) -> NotifyOption {
debug_assert_ne!(self.total_consistency_lock.held_by_thread(), LockHeldState::NotHeldByThread);
- #[cfg(debug_assertions)]
self.background_events_processed_since_startup.store(true, Ordering::Release);
let mut background_events = Vec::new();
mem::swap(&mut *self.pending_background_events.lock().unwrap(), &mut background_events);
if background_events.is_empty() {
- return NotifyOption::SkipPersist;
+ return NotifyOption::SkipPersistNoEvents;
}
for event in background_events.drain(..) {
match event {
- BackgroundEvent::ClosingMonitorUpdateRegeneratedOnStartup((funding_txo, update)) => {
+ BackgroundEvent::ClosedMonitorUpdateRegeneratedOnStartup((funding_txo, update)) => {
// The channel has already been closed, so no use bothering to care about the
// monitor updating completing.
let _ = self.chain_monitor.update_channel(funding_txo, &update);
},
BackgroundEvent::MonitorUpdateRegeneratedOnStartup { counterparty_node_id, funding_txo, update } => {
let mut updated_chan = false;
- let res = {
+ {
let per_peer_state = self.per_peer_state.read().unwrap();
if let Some(peer_state_mutex) = per_peer_state.get(&counterparty_node_id) {
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
let peer_state = &mut *peer_state_lock;
match peer_state.channel_by_id.entry(funding_txo.to_channel_id()) {
- hash_map::Entry::Occupied(mut chan) => {
- updated_chan = true;
- handle_new_monitor_update!(self, funding_txo, update.clone(),
- peer_state_lock, peer_state, per_peer_state, chan).map(|_| ())
+ hash_map::Entry::Occupied(mut chan_phase) => {
+ if let ChannelPhase::Funded(chan) = chan_phase.get_mut() {
+ updated_chan = true;
+ handle_new_monitor_update!(self, funding_txo, update.clone(),
+ peer_state_lock, peer_state, per_peer_state, chan);
+ } else {
+ debug_assert!(false, "We shouldn't have an update for a non-funded channel");
+ }
},
- hash_map::Entry::Vacant(_) => Ok(()),
+ hash_map::Entry::Vacant(_) => {},
}
- } else { Ok(()) }
- };
+ }
+ }
if !updated_chan {
// TODO: Track this as in-flight even though the channel is closed.
let _ = self.chain_monitor.update_channel(funding_txo, &update);
}
- // TODO: If this channel has since closed, we're likely providing a payment
- // preimage update, which we must ensure is durable! We currently don't,
- // however, ensure that.
- if res.is_err() {
- log_error!(self.logger,
- "Failed to provide ChannelMonitorUpdate to closed channel! This likely lost us a payment preimage!");
+ },
+ BackgroundEvent::MonitorUpdatesComplete { counterparty_node_id, channel_id } => {
+ let per_peer_state = self.per_peer_state.read().unwrap();
+ if let Some(peer_state_mutex) = per_peer_state.get(&counterparty_node_id) {
+ let mut peer_state_lock = peer_state_mutex.lock().unwrap();
+ let peer_state = &mut *peer_state_lock;
+ if let Some(ChannelPhase::Funded(chan)) = peer_state.channel_by_id.get_mut(&channel_id) {
+ handle_monitor_update_completion!(self, peer_state_lock, peer_state, per_peer_state, chan);
+ } else {
+ let update_actions = peer_state.monitor_update_blocked_actions
+ .remove(&channel_id).unwrap_or(Vec::new());
+ mem::drop(peer_state_lock);
+ mem::drop(per_peer_state);
+ self.handle_monitor_update_completion_actions(update_actions);
+ }
}
- let _ = handle_error!(self, res, counterparty_node_id);
},
}
}
let _ = self.process_background_events();
}
- fn update_channel_fee(&self, chan_id: &[u8; 32], chan: &mut Channel<<SP::Target as SignerProvider>::Signer>, new_feerate: u32) -> NotifyOption {
- if !chan.context.is_outbound() { return NotifyOption::SkipPersist; }
+ fn update_channel_fee(&self, chan_id: &ChannelId, chan: &mut Channel<SP>, new_feerate: u32) -> NotifyOption {
+ if !chan.context.is_outbound() { return NotifyOption::SkipPersistNoEvents; }
// If the feerate has decreased by less than half, don't bother
if new_feerate <= chan.context.get_feerate_sat_per_1000_weight() && new_feerate * 2 > chan.context.get_feerate_sat_per_1000_weight() {
log_trace!(self.logger, "Channel {} does not qualify for a feerate change from {} to {}.",
- log_bytes!(chan_id[..]), chan.context.get_feerate_sat_per_1000_weight(), new_feerate);
- return NotifyOption::SkipPersist;
+ chan_id, chan.context.get_feerate_sat_per_1000_weight(), new_feerate);
+ return NotifyOption::SkipPersistNoEvents;
}
if !chan.context.is_live() {
log_trace!(self.logger, "Channel {} does not qualify for a feerate change from {} to {} as it cannot currently be updated (probably the peer is disconnected).",
- log_bytes!(chan_id[..]), chan.context.get_feerate_sat_per_1000_weight(), new_feerate);
- return NotifyOption::SkipPersist;
+ chan_id, chan.context.get_feerate_sat_per_1000_weight(), new_feerate);
+ return NotifyOption::SkipPersistNoEvents;
}
log_trace!(self.logger, "Channel {} qualifies for a feerate change from {} to {}.",
- log_bytes!(chan_id[..]), chan.context.get_feerate_sat_per_1000_weight(), new_feerate);
+ &chan_id, chan.context.get_feerate_sat_per_1000_weight(), new_feerate);
- chan.queue_update_fee(new_feerate, &self.logger);
+ chan.queue_update_fee(new_feerate, &self.fee_estimator, &self.logger);
NotifyOption::DoPersist
}
/// these a fuzz failure (as they usually indicate a channel force-close, which is exactly what
/// it wants to detect). Thus, we have a variant exposed here for its benefit.
pub fn maybe_update_chan_fees(&self) {
- PersistenceNotifierGuard::optionally_notify(&self.total_consistency_lock, &self.persistence_notifier, || {
- let mut should_persist = self.process_background_events();
+ PersistenceNotifierGuard::optionally_notify(self, || {
+ let mut should_persist = NotifyOption::SkipPersistNoEvents;
- let new_feerate = self.fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::Normal);
+ 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 per_peer_state = self.per_peer_state.read().unwrap();
for (_cp_id, peer_state_mutex) in per_peer_state.iter() {
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
let peer_state = &mut *peer_state_lock;
- for (chan_id, chan) in peer_state.channel_by_id.iter_mut() {
+ for (chan_id, chan) in peer_state.channel_by_id.iter_mut().filter_map(
+ |(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
+ } else {
+ normal_feerate
+ };
let chan_needs_persist = self.update_channel_fee(chan_id, chan, new_feerate);
if chan_needs_persist == NotifyOption::DoPersist { should_persist = NotifyOption::DoPersist; }
}
/// * Expiring a channel's previous [`ChannelConfig`] if necessary to only allow forwarding HTLCs
/// 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.
///
/// Note that this may cause reentrancy through [`chain::Watch::update_channel`] calls or feerate
/// estimate fetches.
/// [`ChannelUpdate`]: msgs::ChannelUpdate
/// [`ChannelConfig`]: crate::util::config::ChannelConfig
pub fn timer_tick_occurred(&self) {
- PersistenceNotifierGuard::optionally_notify(&self.total_consistency_lock, &self.persistence_notifier, || {
- let mut should_persist = self.process_background_events();
+ PersistenceNotifierGuard::optionally_notify(self, || {
+ let mut should_persist = NotifyOption::SkipPersistNoEvents;
- let new_feerate = self.fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::Normal);
+ 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 mut handle_errors: Vec<(Result<(), _>, _)> = Vec::new();
let mut timed_out_mpp_htlcs = Vec::new();
let mut pending_peers_awaiting_removal = Vec::new();
+ let mut shutdown_channels = Vec::new();
+
+ let mut process_unfunded_channel_tick = |
+ chan_id: &ChannelId,
+ context: &mut ChannelContext<SP>,
+ unfunded_context: &mut UnfundedChannelContext,
+ pending_msg_events: &mut Vec<MessageSendEvent>,
+ counterparty_node_id: PublicKey,
+ | {
+ context.maybe_expire_prev_config();
+ if unfunded_context.should_expire_unfunded_channel() {
+ log_error!(self.logger,
+ "Force-closing pending channel with ID {} for not establishing in a timely manner", chan_id);
+ update_maps_on_chan_removal!(self, &context);
+ self.issue_channel_close_events(&context, ClosureReason::HolderForceClosed);
+ shutdown_channels.push(context.force_shutdown(false));
+ pending_msg_events.push(MessageSendEvent::HandleError {
+ node_id: counterparty_node_id,
+ action: msgs::ErrorAction::SendErrorMessage {
+ msg: msgs::ErrorMessage {
+ channel_id: *chan_id,
+ data: "Force-closing pending channel due to timeout awaiting establishment handshake".to_owned(),
+ },
+ },
+ });
+ false
+ } else {
+ true
+ }
+ };
+
{
let per_peer_state = self.per_peer_state.read().unwrap();
for (counterparty_node_id, peer_state_mutex) in per_peer_state.iter() {
let peer_state = &mut *peer_state_lock;
let pending_msg_events = &mut peer_state.pending_msg_events;
let counterparty_node_id = *counterparty_node_id;
- peer_state.channel_by_id.retain(|chan_id, chan| {
- let chan_needs_persist = self.update_channel_fee(chan_id, chan, new_feerate);
- if chan_needs_persist == NotifyOption::DoPersist { should_persist = NotifyOption::DoPersist; }
-
- if let Err(e) = chan.timer_check_closing_negotiation_progress() {
- let (needs_close, err) = convert_chan_err!(self, e, chan, chan_id);
- handle_errors.push((Err(err), counterparty_node_id));
- if needs_close { return false; }
- }
-
- match chan.channel_update_status() {
- ChannelUpdateStatus::Enabled if !chan.context.is_live() => chan.set_channel_update_status(ChannelUpdateStatus::DisabledStaged(0)),
- ChannelUpdateStatus::Disabled if chan.context.is_live() => chan.set_channel_update_status(ChannelUpdateStatus::EnabledStaged(0)),
- ChannelUpdateStatus::DisabledStaged(_) if chan.context.is_live()
- => chan.set_channel_update_status(ChannelUpdateStatus::Enabled),
- ChannelUpdateStatus::EnabledStaged(_) if !chan.context.is_live()
- => chan.set_channel_update_status(ChannelUpdateStatus::Disabled),
- ChannelUpdateStatus::DisabledStaged(mut n) if !chan.context.is_live() => {
- n += 1;
- if n >= DISABLE_GOSSIP_TICKS {
- chan.set_channel_update_status(ChannelUpdateStatus::Disabled);
- if let Ok(update) = self.get_channel_update_for_broadcast(&chan) {
- pending_msg_events.push(events::MessageSendEvent::BroadcastChannelUpdate {
- msg: update
- });
- }
- should_persist = NotifyOption::DoPersist;
+ peer_state.channel_by_id.retain(|chan_id, phase| {
+ match phase {
+ ChannelPhase::Funded(chan) => {
+ let new_feerate = if chan.context.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
+ min_mempool_feerate
} else {
- chan.set_channel_update_status(ChannelUpdateStatus::DisabledStaged(n));
+ normal_feerate
+ };
+ let chan_needs_persist = self.update_channel_fee(chan_id, chan, new_feerate);
+ if chan_needs_persist == NotifyOption::DoPersist { should_persist = NotifyOption::DoPersist; }
+
+ if let Err(e) = chan.timer_check_closing_negotiation_progress() {
+ let (needs_close, err) = convert_chan_phase_err!(self, e, chan, chan_id, FUNDED_CHANNEL);
+ handle_errors.push((Err(err), counterparty_node_id));
+ if needs_close { return false; }
}
- },
- ChannelUpdateStatus::EnabledStaged(mut n) if chan.context.is_live() => {
- n += 1;
- if n >= ENABLE_GOSSIP_TICKS {
- chan.set_channel_update_status(ChannelUpdateStatus::Enabled);
- if let Ok(update) = self.get_channel_update_for_broadcast(&chan) {
- pending_msg_events.push(events::MessageSendEvent::BroadcastChannelUpdate {
- msg: update
- });
- }
- should_persist = NotifyOption::DoPersist;
- } else {
- chan.set_channel_update_status(ChannelUpdateStatus::EnabledStaged(n));
+
+ match chan.channel_update_status() {
+ ChannelUpdateStatus::Enabled if !chan.context.is_live() => chan.set_channel_update_status(ChannelUpdateStatus::DisabledStaged(0)),
+ ChannelUpdateStatus::Disabled if chan.context.is_live() => chan.set_channel_update_status(ChannelUpdateStatus::EnabledStaged(0)),
+ ChannelUpdateStatus::DisabledStaged(_) if chan.context.is_live()
+ => chan.set_channel_update_status(ChannelUpdateStatus::Enabled),
+ ChannelUpdateStatus::EnabledStaged(_) if !chan.context.is_live()
+ => chan.set_channel_update_status(ChannelUpdateStatus::Disabled),
+ ChannelUpdateStatus::DisabledStaged(mut n) if !chan.context.is_live() => {
+ n += 1;
+ if n >= DISABLE_GOSSIP_TICKS {
+ chan.set_channel_update_status(ChannelUpdateStatus::Disabled);
+ if let Ok(update) = self.get_channel_update_for_broadcast(&chan) {
+ pending_msg_events.push(events::MessageSendEvent::BroadcastChannelUpdate {
+ msg: update
+ });
+ }
+ should_persist = NotifyOption::DoPersist;
+ } else {
+ chan.set_channel_update_status(ChannelUpdateStatus::DisabledStaged(n));
+ }
+ },
+ ChannelUpdateStatus::EnabledStaged(mut n) if chan.context.is_live() => {
+ n += 1;
+ if n >= ENABLE_GOSSIP_TICKS {
+ chan.set_channel_update_status(ChannelUpdateStatus::Enabled);
+ if let Ok(update) = self.get_channel_update_for_broadcast(&chan) {
+ pending_msg_events.push(events::MessageSendEvent::BroadcastChannelUpdate {
+ msg: update
+ });
+ }
+ should_persist = NotifyOption::DoPersist;
+ } else {
+ chan.set_channel_update_status(ChannelUpdateStatus::EnabledStaged(n));
+ }
+ },
+ _ => {},
}
+
+ chan.context.maybe_expire_prev_config();
+
+ if chan.should_disconnect_peer_awaiting_response() {
+ log_debug!(self.logger, "Disconnecting peer {} due to not making any progress on channel {}",
+ counterparty_node_id, chan_id);
+ pending_msg_events.push(MessageSendEvent::HandleError {
+ node_id: counterparty_node_id,
+ action: msgs::ErrorAction::DisconnectPeerWithWarning {
+ msg: msgs::WarningMessage {
+ channel_id: *chan_id,
+ data: "Disconnecting due to timeout awaiting response".to_owned(),
+ },
+ },
+ });
+ }
+
+ true
+ },
+ ChannelPhase::UnfundedInboundV1(chan) => {
+ process_unfunded_channel_tick(chan_id, &mut chan.context, &mut chan.unfunded_context,
+ pending_msg_events, counterparty_node_id)
+ },
+ ChannelPhase::UnfundedOutboundV1(chan) => {
+ process_unfunded_channel_tick(chan_id, &mut chan.context, &mut chan.unfunded_context,
+ pending_msg_events, counterparty_node_id)
},
- _ => {},
}
+ });
- chan.context.maybe_expire_prev_config();
-
- if chan.should_disconnect_peer_awaiting_response() {
- log_debug!(self.logger, "Disconnecting peer {} due to not making any progress on channel {}",
- counterparty_node_id, log_bytes!(*chan_id));
- pending_msg_events.push(MessageSendEvent::HandleError {
- node_id: counterparty_node_id,
- action: msgs::ErrorAction::DisconnectPeerWithWarning {
- msg: msgs::WarningMessage {
- channel_id: *chan_id,
- data: "Disconnecting due to timeout awaiting response".to_owned(),
+ for (chan_id, req) in peer_state.inbound_channel_request_by_id.iter_mut() {
+ if { req.ticks_remaining -= 1 ; req.ticks_remaining } <= 0 {
+ log_error!(self.logger, "Force-closing unaccepted inbound channel {} for not accepting in a timely manner", &chan_id);
+ peer_state.pending_msg_events.push(
+ events::MessageSendEvent::HandleError {
+ node_id: counterparty_node_id,
+ action: msgs::ErrorAction::SendErrorMessage {
+ msg: msgs::ErrorMessage { channel_id: chan_id.clone(), data: "Channel force-closed".to_owned() }
},
- },
- });
+ }
+ );
}
+ }
+ peer_state.inbound_channel_request_by_id.retain(|_, req| req.ticks_remaining > 0);
- true
- });
if peer_state.ok_to_remove(true) {
pending_peers_awaiting_removal.push(counterparty_node_id);
}
let _ = handle_error!(self, err, counterparty_node_id);
}
- self.pending_outbound_payments.remove_stale_resolved_payments(&self.pending_events);
+ for shutdown_res in shutdown_channels {
+ self.finish_close_channel(shutdown_res);
+ }
+
+ self.pending_outbound_payments.remove_stale_payments(&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
/// Gets error data to form an [`HTLCFailReason`] given a [`FailureCode`] and [`ClaimableHTLC`].
fn get_htlc_fail_reason_from_failure_code(&self, failure_code: FailureCode, htlc: &ClaimableHTLC) -> HTLCFailReason {
match failure_code {
- FailureCode::TemporaryNodeFailure => HTLCFailReason::from_failure_code(failure_code as u16),
- FailureCode::RequiredNodeFeatureMissing => HTLCFailReason::from_failure_code(failure_code as u16),
+ FailureCode::TemporaryNodeFailure => HTLCFailReason::from_failure_code(failure_code.into()),
+ FailureCode::RequiredNodeFeatureMissing => HTLCFailReason::from_failure_code(failure_code.into()),
FailureCode::IncorrectOrUnknownPaymentDetails => {
let mut htlc_msat_height_data = htlc.value.to_be_bytes().to_vec();
htlc_msat_height_data.extend_from_slice(&self.best_block.read().unwrap().height().to_be_bytes());
- HTLCFailReason::reason(failure_code as u16, htlc_msat_height_data)
+ HTLCFailReason::reason(failure_code.into(), htlc_msat_height_data)
+ },
+ FailureCode::InvalidOnionPayload(data) => {
+ let fail_data = match data {
+ Some((typ, offset)) => [BigSize(typ).encode(), offset.encode()].concat(),
+ None => Vec::new(),
+ };
+ HTLCFailReason::reason(failure_code.into(), fail_data)
}
}
}
///
/// This is for failures on the channel on which the HTLC was *received*, not failures
/// forwarding
- fn get_htlc_inbound_temp_fail_err_and_data(&self, desired_err_code: u16, chan: &Channel<<SP::Target as SignerProvider>::Signer>) -> (u16, Vec<u8>) {
+ fn get_htlc_inbound_temp_fail_err_and_data(&self, desired_err_code: u16, chan: &Channel<SP>) -> (u16, Vec<u8>) {
// We can't be sure what SCID was used when relaying inbound towards us, so we have to
// guess somewhat. If its a public channel, we figure best to just use the real SCID (as
// we're not leaking that we have a channel with the counterparty), otherwise we try to use
/// Gets an HTLC onion failure code and error data for an `UPDATE` error, given the error code
/// that we want to return and a channel.
- fn get_htlc_temp_fail_err_and_data(&self, desired_err_code: u16, scid: u64, chan: &Channel<<SP::Target as SignerProvider>::Signer>) -> (u16, Vec<u8>) {
+ fn get_htlc_temp_fail_err_and_data(&self, desired_err_code: u16, scid: u64, chan: &Channel<SP>) -> (u16, Vec<u8>) {
debug_assert_eq!(desired_err_code & 0x1000, 0x1000);
if let Ok(upd) = self.get_channel_update_for_onion(scid, chan) {
let mut enc = VecWriter(Vec::with_capacity(upd.serialized_length() + 6));
// failed backwards or, if they were one of our outgoing HTLCs, then their failure needs to
// be surfaced to the user.
fn fail_holding_cell_htlcs(
- &self, mut htlcs_to_fail: Vec<(HTLCSource, PaymentHash)>, channel_id: [u8; 32],
+ &self, mut htlcs_to_fail: Vec<(HTLCSource, PaymentHash)>, channel_id: ChannelId,
counterparty_node_id: &PublicKey
) {
let (failure_code, onion_failure_data) = {
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
let peer_state = &mut *peer_state_lock;
match peer_state.channel_by_id.entry(channel_id) {
- hash_map::Entry::Occupied(chan_entry) => {
- self.get_htlc_inbound_temp_fail_err_and_data(0x1000|7, &chan_entry.get())
+ hash_map::Entry::Occupied(chan_phase_entry) => {
+ if let ChannelPhase::Funded(chan) = chan_phase_entry.get() {
+ self.get_htlc_inbound_temp_fail_err_and_data(0x1000|7, &chan)
+ } else {
+ // We shouldn't be trying to fail holding cell HTLCs on an unfunded channel.
+ debug_assert!(false);
+ (0x4000|10, Vec::new())
+ }
},
hash_map::Entry::Vacant(_) => (0x4000|10, Vec::new())
}
// This ensures that future code doesn't introduce a lock-order requirement for
// `forward_htlcs` to be locked after the `per_peer_state` peer locks, which calling
// this function with any `per_peer_state` peer lock acquired would.
+ #[cfg(debug_assertions)]
for (_, peer) in self.per_peer_state.read().unwrap().iter() {
debug_assert_ne!(peer.held_by_thread(), LockHeldState::HeldByThread);
}
&self.pending_events, &self.logger)
{ self.push_pending_forwards_ev(); }
},
- HTLCSource::PreviousHopData(HTLCPreviousHopData { ref short_channel_id, ref htlc_id, ref incoming_packet_shared_secret, ref phantom_shared_secret, ref outpoint }) => {
- log_trace!(self.logger, "Failing HTLC with payment_hash {} backwards from us with {:?}", log_bytes!(payment_hash.0), onion_error);
+ HTLCSource::PreviousHopData(HTLCPreviousHopData { ref short_channel_id, ref htlc_id, ref incoming_packet_shared_secret, ref phantom_shared_secret, ref outpoint, .. }) => {
+ log_trace!(self.logger, "Failing HTLC with payment_hash {} backwards from us with {:?}", &payment_hash, onion_error);
let err_packet = onion_error.get_encrypted_failure_packet(incoming_packet_shared_secret, phantom_shared_secret);
let mut push_forward_ev = false;
/// event matches your expectation. If you fail to do so and call this method, you may provide
/// the sender "proof-of-payment" when they did not fulfill the full expected payment.
///
+ /// This function will fail the payment if it has custom TLVs with even type numbers, as we
+ /// will assume they are unknown. If you intend to accept even custom TLVs, you should use
+ /// [`claim_funds_with_known_custom_tlvs`].
+ ///
/// [`Event::PaymentClaimable`]: crate::events::Event::PaymentClaimable
/// [`Event::PaymentClaimable::claim_deadline`]: crate::events::Event::PaymentClaimable::claim_deadline
/// [`Event::PaymentClaimed`]: crate::events::Event::PaymentClaimed
/// [`process_pending_events`]: EventsProvider::process_pending_events
/// [`create_inbound_payment`]: Self::create_inbound_payment
/// [`create_inbound_payment_for_hash`]: Self::create_inbound_payment_for_hash
+ /// [`claim_funds_with_known_custom_tlvs`]: Self::claim_funds_with_known_custom_tlvs
pub fn claim_funds(&self, payment_preimage: PaymentPreimage) {
+ self.claim_payment_internal(payment_preimage, false);
+ }
+
+ /// This is a variant of [`claim_funds`] that allows accepting a payment with custom TLVs with
+ /// even type numbers.
+ ///
+ /// # Note
+ ///
+ /// You MUST check you've understood all even TLVs before using this to
+ /// claim, otherwise you may unintentionally agree to some protocol you do not understand.
+ ///
+ /// [`claim_funds`]: Self::claim_funds
+ pub fn claim_funds_with_known_custom_tlvs(&self, payment_preimage: PaymentPreimage) {
+ self.claim_payment_internal(payment_preimage, true);
+ }
+
+ fn claim_payment_internal(&self, payment_preimage: PaymentPreimage, custom_tlvs_known: bool) {
let payment_hash = PaymentHash(Sha256::hash(&payment_preimage.0).into_inner());
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
}
}
+ let htlcs = payment.htlcs.iter().map(events::ClaimedHTLC::from).collect();
+ let sender_intended_value = payment.htlcs.first().map(|htlc| htlc.total_msat);
let dup_purpose = claimable_payments.pending_claiming_payments.insert(payment_hash,
ClaimingPayment { amount_msat: payment.htlcs.iter().map(|source| source.value).sum(),
- payment_purpose: payment.purpose, receiver_node_id,
+ payment_purpose: payment.purpose, receiver_node_id, htlcs, sender_intended_value
});
if dup_purpose.is_some() {
debug_assert!(false, "Shouldn't get a duplicate pending claim event ever");
log_error!(self.logger, "Got a duplicate pending claimable event on payment hash {}! Please report this bug",
- log_bytes!(payment_hash.0));
+ &payment_hash);
+ }
+
+ if let Some(RecipientOnionFields { ref custom_tlvs, .. }) = payment.onion_fields {
+ if !custom_tlvs_known && custom_tlvs.iter().any(|(typ, _)| typ % 2 == 0) {
+ log_info!(self.logger, "Rejecting payment with payment hash {} as we cannot accept payment with unknown even TLVs: {}",
+ &payment_hash, log_iter!(custom_tlvs.iter().map(|(typ, _)| typ).filter(|typ| *typ % 2 == 0)));
+ claimable_payments.pending_claiming_payments.remove(&payment_hash);
+ mem::drop(claimable_payments);
+ for htlc in payment.htlcs {
+ let reason = self.get_htlc_fail_reason_from_failure_code(FailureCode::InvalidOnionPayload(None), &htlc);
+ let source = HTLCSource::PreviousHopData(htlc.prev_hop);
+ let receiver = HTLCDestination::FailedPayment { payment_hash };
+ self.fail_htlc_backwards_internal(&source, &payment_hash, &reason, receiver);
+ }
+ return;
+ }
}
+
payment.htlcs
} else { return; }
};
-> Result<(), (PublicKey, MsgHandleErrInternal)> {
//TODO: Delay the claimed_funds relaying just like we do outbound relay!
+ // If we haven't yet run background events assume we're still deserializing and shouldn't
+ // actually pass `ChannelMonitorUpdate`s to users yet. Instead, queue them up as
+ // `BackgroundEvent`s.
+ let during_init = !self.background_events_processed_since_startup.load(Ordering::Acquire);
+
{
let per_peer_state = self.per_peer_state.read().unwrap();
let chan_id = prev_hop.outpoint.to_channel_id();
if peer_state_opt.is_some() {
let mut peer_state_lock = peer_state_opt.unwrap();
let peer_state = &mut *peer_state_lock;
- if let hash_map::Entry::Occupied(mut chan) = peer_state.channel_by_id.entry(chan_id) {
- let counterparty_node_id = chan.get().context.get_counterparty_node_id();
- let fulfill_res = chan.get_mut().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 {}: {:?}",
- log_bytes!(chan_id), action);
- peer_state.monitor_update_blocked_actions.entry(chan_id).or_insert(Vec::new()).push(action);
- }
- let res = handle_new_monitor_update!(self, prev_hop.outpoint, monitor_update, peer_state_lock,
- peer_state, per_peer_state, chan);
- if let Err(e) = res {
- // TODO: This is a *critical* error - we probably updated the outbound edge
- // of the HTLC's monitor with a preimage. We should retry this monitor
- // update over and over again until morale improves.
- log_error!(self.logger, "Failed to update channel monitor with preimage {:?}", payment_preimage);
- return Err((counterparty_node_id, e));
+ if let hash_map::Entry::Occupied(mut chan_phase_entry) = peer_state.channel_by_id.entry(chan_id) {
+ if let ChannelPhase::Funded(chan) = chan_phase_entry.get_mut() {
+ 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);
+ }
+ 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(),
+ });
+ }
}
}
return Ok(());
payment_preimage,
}],
};
- // We update the ChannelMonitor on the backward link, after
- // receiving an `update_fulfill_htlc` from the forward link.
- let update_res = self.chain_monitor.update_channel(prev_hop.outpoint, &preimage_update);
- if update_res != ChannelMonitorUpdateStatus::Completed {
- // TODO: This needs to be handled somehow - if we receive a monitor update
- // with a preimage we *must* somehow manage to propagate it to the upstream
- // channel, or we must have an ability to receive the same event and try
- // again on restart.
- log_error!(self.logger, "Critical error: failed to update channel monitor with preimage {:?}: {:?}",
- payment_preimage, update_res);
+
+ if !during_init {
+ // We update the ChannelMonitor on the backward link, after
+ // receiving an `update_fulfill_htlc` from the forward link.
+ let update_res = self.chain_monitor.update_channel(prev_hop.outpoint, &preimage_update);
+ if update_res != ChannelMonitorUpdateStatus::Completed {
+ // TODO: This needs to be handled somehow - if we receive a monitor update
+ // with a preimage we *must* somehow manage to propagate it to the upstream
+ // channel, or we must have an ability to receive the same event and try
+ // again on restart.
+ log_error!(self.logger, "Critical error: failed to update channel monitor with preimage {:?}: {:?}",
+ payment_preimage, update_res);
+ }
+ } 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.
+ // Note that while it's safe to use `ClosedMonitorUpdateRegeneratedOnStartup` here (the
+ // channel is already closed) we need to ultimately handle the monitor update
+ // completion action only after we've completed the monitor update. This is the only
+ // way to guarantee this update *will* be regenerated on startup (otherwise if this was
+ // from a forwarded HTLC the downstream preimage may be deleted before we claim
+ // upstream). Thus, we need to transition to some new `BackgroundEvent` type which will
+ // complete the monitor update completion action from `completion_action`.
+ self.pending_background_events.lock().unwrap().push(
+ BackgroundEvent::ClosedMonitorUpdateRegeneratedOnStartup((
+ prev_hop.outpoint, preimage_update,
+ )));
}
// Note that we do process the completion action here. This totally could be a
// duplicate claim, but we have no way of knowing without interrogating the
self.pending_outbound_payments.finalize_claims(sources, &self.pending_events);
}
- fn claim_funds_internal(&self, source: HTLCSource, payment_preimage: PaymentPreimage, forwarded_htlc_value_msat: Option<u64>, from_onchain: bool, next_channel_id: [u8; 32]) {
+ fn claim_funds_internal(&self, source: HTLCSource, payment_preimage: PaymentPreimage,
+ forwarded_htlc_value_msat: Option<u64>, from_onchain: bool,
+ next_channel_counterparty_node_id: Option<PublicKey>, next_channel_outpoint: OutPoint
+ ) {
match source {
HTLCSource::OutboundRoute { session_priv, payment_id, path, .. } => {
- self.pending_outbound_payments.claim_htlc(payment_id, payment_preimage, session_priv, path, from_onchain, &self.pending_events, &self.logger);
+ debug_assert!(self.background_events_processed_since_startup.load(Ordering::Acquire),
+ "We don't support claim_htlc claims during startup - monitors may not be available yet");
+ if let Some(pubkey) = next_channel_counterparty_node_id {
+ debug_assert_eq!(pubkey, path.hops[0].pubkey);
+ }
+ let ev_completion_action = EventCompletionAction::ReleaseRAAChannelMonitorUpdate {
+ channel_funding_outpoint: next_channel_outpoint,
+ counterparty_node_id: path.hops[0].pubkey,
+ };
+ self.pending_outbound_payments.claim_htlc(payment_id, payment_preimage,
+ session_priv, path, from_onchain, ev_completion_action, &self.pending_events,
+ &self.logger);
},
HTLCSource::PreviousHopData(hop_data) => {
let prev_outpoint = hop_data.outpoint;
+ let completed_blocker = RAAMonitorUpdateBlockingAction::from_prev_hop_data(&hop_data);
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 {
fee_earned_msat,
claim_from_onchain_tx: from_onchain,
prev_channel_id: Some(prev_outpoint.to_channel_id()),
- next_channel_id: Some(next_channel_id),
+ next_channel_id: Some(next_channel_outpoint.to_channel_id()),
outbound_amount_forwarded_msat: forwarded_htlc_value_msat,
},
- downstream_counterparty_and_funding_outpoint: None,
+ 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
+ },
})
} else { None }
});
match action {
MonitorUpdateCompletionAction::PaymentClaimed { payment_hash } => {
let payment = self.claimable_payments.lock().unwrap().pending_claiming_payments.remove(&payment_hash);
- if let Some(ClaimingPayment { amount_msat, payment_purpose: purpose, receiver_node_id }) = payment {
+ if let Some(ClaimingPayment {
+ amount_msat,
+ payment_purpose: purpose,
+ receiver_node_id,
+ htlcs,
+ sender_intended_value: sender_intended_total_msat,
+ }) = payment {
self.pending_events.lock().unwrap().push_back((events::Event::PaymentClaimed {
- payment_hash, purpose, amount_msat, receiver_node_id: Some(receiver_node_id),
+ payment_hash,
+ purpose,
+ amount_msat,
+ receiver_node_id: Some(receiver_node_id),
+ htlcs,
+ sender_intended_total_msat,
}, None));
}
},
/// Handles a channel reentering a functional state, either due to reconnect or a monitor
/// update completion.
fn handle_channel_resumption(&self, pending_msg_events: &mut Vec<MessageSendEvent>,
- channel: &mut Channel<<SP::Target as SignerProvider>::Signer>, raa: Option<msgs::RevokeAndACK>,
+ channel: &mut Channel<SP>, raa: Option<msgs::RevokeAndACK>,
commitment_update: Option<msgs::CommitmentUpdate>, order: RAACommitmentOrder,
pending_forwards: Vec<(PendingHTLCInfo, u64)>, funding_broadcastable: Option<Transaction>,
channel_ready: Option<msgs::ChannelReady>, announcement_sigs: Option<msgs::AnnouncementSignatures>)
-> Option<(u64, OutPoint, u128, Vec<(PendingHTLCInfo, u64)>)> {
log_trace!(self.logger, "Handling channel resumption for channel {} with {} RAA, {} commitment update, {} pending forwards, {}broadcasting funding, {} channel ready, {} announcement",
- log_bytes!(channel.context.channel_id()),
+ &channel.context.channel_id(),
if raa.is_some() { "an" } else { "no" },
if commitment_update.is_some() { "a" } else { "no" }, pending_forwards.len(),
if funding_broadcastable.is_some() { "" } else { "not " },
if peer_state_mutex_opt.is_none() { return }
peer_state_lock = peer_state_mutex_opt.unwrap().lock().unwrap();
let peer_state = &mut *peer_state_lock;
- let mut channel = {
- match peer_state.channel_by_id.entry(funding_txo.to_channel_id()){
- hash_map::Entry::Occupied(chan) => chan,
- hash_map::Entry::Vacant(_) => return,
- }
- };
+ let channel =
+ if let Some(ChannelPhase::Funded(chan)) = peer_state.channel_by_id.get_mut(&funding_txo.to_channel_id()) {
+ chan
+ } else {
+ let update_actions = peer_state.monitor_update_blocked_actions
+ .remove(&funding_txo.to_channel_id()).unwrap_or(Vec::new());
+ mem::drop(peer_state_lock);
+ mem::drop(per_peer_state);
+ self.handle_monitor_update_completion_actions(update_actions);
+ return;
+ };
let remaining_in_flight =
if let Some(pending) = peer_state.in_flight_monitor_updates.get_mut(funding_txo) {
pending.retain(|upd| upd.update_id > highest_applied_update_id);
pending.len()
} else { 0 };
log_trace!(self.logger, "ChannelMonitor updated to {}. Current highest is {}. {} pending in-flight updates.",
- highest_applied_update_id, channel.get().context.get_latest_monitor_update_id(),
+ highest_applied_update_id, channel.context.get_latest_monitor_update_id(),
remaining_in_flight);
- if !channel.get().is_awaiting_monitor_update() || channel.get().context.get_latest_monitor_update_id() != highest_applied_update_id {
+ if !channel.is_awaiting_monitor_update() || channel.context.get_latest_monitor_update_id() != highest_applied_update_id {
return;
}
- handle_monitor_update_completion!(self, peer_state_lock, peer_state, per_peer_state, channel.get_mut());
+ handle_monitor_update_completion!(self, peer_state_lock, peer_state, per_peer_state, channel);
}
/// Accepts a request to open a channel after a [`Event::OpenChannelRequest`].
///
/// [`Event::OpenChannelRequest`]: events::Event::OpenChannelRequest
/// [`Event::ChannelClosed::user_channel_id`]: events::Event::ChannelClosed::user_channel_id
- pub fn accept_inbound_channel(&self, temporary_channel_id: &[u8; 32], counterparty_node_id: &PublicKey, user_channel_id: u128) -> Result<(), APIError> {
+ pub fn accept_inbound_channel(&self, temporary_channel_id: &ChannelId, counterparty_node_id: &PublicKey, user_channel_id: u128) -> Result<(), APIError> {
self.do_accept_inbound_channel(temporary_channel_id, counterparty_node_id, false, user_channel_id)
}
///
/// [`Event::OpenChannelRequest`]: events::Event::OpenChannelRequest
/// [`Event::ChannelClosed::user_channel_id`]: events::Event::ChannelClosed::user_channel_id
- pub fn accept_inbound_channel_from_trusted_peer_0conf(&self, temporary_channel_id: &[u8; 32], counterparty_node_id: &PublicKey, user_channel_id: u128) -> Result<(), APIError> {
+ pub fn accept_inbound_channel_from_trusted_peer_0conf(&self, temporary_channel_id: &ChannelId, counterparty_node_id: &PublicKey, user_channel_id: u128) -> Result<(), APIError> {
self.do_accept_inbound_channel(temporary_channel_id, counterparty_node_id, true, user_channel_id)
}
- fn do_accept_inbound_channel(&self, temporary_channel_id: &[u8; 32], counterparty_node_id: &PublicKey, accept_0conf: bool, user_channel_id: u128) -> Result<(), APIError> {
+ fn do_accept_inbound_channel(&self, temporary_channel_id: &ChannelId, counterparty_node_id: &PublicKey, accept_0conf: bool, user_channel_id: u128) -> Result<(), APIError> {
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
let peers_without_funded_channels =
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
let peer_state = &mut *peer_state_lock;
let is_only_peer_channel = peer_state.total_channel_count() == 1;
- match peer_state.inbound_v1_channel_by_id.entry(temporary_channel_id.clone()) {
- hash_map::Entry::Occupied(mut channel) => {
- if !channel.get().is_awaiting_accept() {
- return Err(APIError::APIMisuseError { err: "The channel isn't currently awaiting to be accepted.".to_owned() });
+
+ // Find (and remove) the channel in the unaccepted table. If it's not there, something weird is
+ // happening and return an error. N.B. that we create channel with an outbound SCID of zero so
+ // that we can delay allocating the SCID until after we're sure that the checks below will
+ // succeed.
+ let mut channel = match peer_state.inbound_channel_request_by_id.remove(temporary_channel_id) {
+ Some(unaccepted_channel) => {
+ let best_block_height = self.best_block.read().unwrap().height();
+ InboundV1Channel::new(&self.fee_estimator, &self.entropy_source, &self.signer_provider,
+ counterparty_node_id.clone(), &self.channel_type_features(), &peer_state.latest_features,
+ &unaccepted_channel.open_channel_msg, user_channel_id, &self.default_configuration, best_block_height,
+ &self.logger, accept_0conf).map_err(|e| APIError::ChannelUnavailable { err: e.to_string() })
+ }
+ _ => Err(APIError::APIMisuseError { err: "No such channel awaiting to be accepted.".to_owned() })
+ }?;
+
+ if accept_0conf {
+ // This should have been correctly configured by the call to InboundV1Channel::new.
+ debug_assert!(channel.context.minimum_depth().unwrap() == 0);
+ } else if channel.context.get_channel_type().requires_zero_conf() {
+ let send_msg_err_event = events::MessageSendEvent::HandleError {
+ node_id: channel.context.get_counterparty_node_id(),
+ action: msgs::ErrorAction::SendErrorMessage{
+ msg: msgs::ErrorMessage { channel_id: temporary_channel_id.clone(), data: "No zero confirmation channels accepted".to_owned(), }
}
- if accept_0conf {
- channel.get_mut().set_0conf();
- } else if channel.get().context.get_channel_type().requires_zero_conf() {
- let send_msg_err_event = events::MessageSendEvent::HandleError {
- node_id: channel.get().context.get_counterparty_node_id(),
- action: msgs::ErrorAction::SendErrorMessage{
- msg: msgs::ErrorMessage { channel_id: temporary_channel_id.clone(), data: "No zero confirmation channels accepted".to_owned(), }
- }
- };
- peer_state.pending_msg_events.push(send_msg_err_event);
- let _ = remove_channel!(self, channel);
- return Err(APIError::APIMisuseError { err: "Please use accept_inbound_channel_from_trusted_peer_0conf to accept channels with zero confirmations.".to_owned() });
- } else {
- // If this peer already has some channels, a new channel won't increase our number of peers
- // with unfunded channels, so as long as we aren't over the maximum number of unfunded
- // channels per-peer we can accept channels from a peer with existing ones.
- if is_only_peer_channel && peers_without_funded_channels >= MAX_UNFUNDED_CHANNEL_PEERS {
- let send_msg_err_event = events::MessageSendEvent::HandleError {
- node_id: channel.get().context.get_counterparty_node_id(),
- action: msgs::ErrorAction::SendErrorMessage{
- msg: msgs::ErrorMessage { channel_id: temporary_channel_id.clone(), data: "Have too many peers with unfunded channels, not accepting new ones".to_owned(), }
- }
- };
- peer_state.pending_msg_events.push(send_msg_err_event);
- let _ = remove_channel!(self, channel);
- return Err(APIError::APIMisuseError { err: "Too many peers with unfunded channels, refusing to accept new ones".to_owned() });
+ };
+ peer_state.pending_msg_events.push(send_msg_err_event);
+ return Err(APIError::APIMisuseError { err: "Please use accept_inbound_channel_from_trusted_peer_0conf to accept channels with zero confirmations.".to_owned() });
+ } else {
+ // If this peer already has some channels, a new channel won't increase our number of peers
+ // with unfunded channels, so as long as we aren't over the maximum number of unfunded
+ // channels per-peer we can accept channels from a peer with existing ones.
+ if is_only_peer_channel && peers_without_funded_channels >= MAX_UNFUNDED_CHANNEL_PEERS {
+ let send_msg_err_event = events::MessageSendEvent::HandleError {
+ node_id: channel.context.get_counterparty_node_id(),
+ action: msgs::ErrorAction::SendErrorMessage{
+ msg: msgs::ErrorMessage { channel_id: temporary_channel_id.clone(), data: "Have too many peers with unfunded channels, not accepting new ones".to_owned(), }
}
- }
-
- peer_state.pending_msg_events.push(events::MessageSendEvent::SendAcceptChannel {
- node_id: channel.get().context.get_counterparty_node_id(),
- msg: channel.get_mut().accept_inbound_channel(user_channel_id),
- });
- }
- hash_map::Entry::Vacant(_) => {
- return Err(APIError::ChannelUnavailable { err: format!("Channel with id {} not found for the passed counterparty node_id {}", log_bytes!(*temporary_channel_id), counterparty_node_id) });
+ };
+ peer_state.pending_msg_events.push(send_msg_err_event);
+ return Err(APIError::APIMisuseError { err: "Too many peers with unfunded channels, refusing to accept new ones".to_owned() });
}
}
+
+ // Now that we know we have a channel, assign an outbound SCID alias.
+ let outbound_scid_alias = self.create_and_insert_outbound_scid_alias();
+ channel.context.set_outbound_scid_alias(outbound_scid_alias);
+
+ peer_state.pending_msg_events.push(events::MessageSendEvent::SendAcceptChannel {
+ node_id: channel.context.get_counterparty_node_id(),
+ msg: channel.accept_inbound_channel(),
+ });
+
+ peer_state.channel_by_id.insert(temporary_channel_id.clone(), ChannelPhase::UnfundedInboundV1(channel));
+
Ok(())
}
/// The filter is called for each peer and provided with the number of unfunded, inbound, and
/// non-0-conf channels we have with the peer.
fn peers_without_funded_channels<Filter>(&self, maybe_count_peer: Filter) -> usize
- where Filter: Fn(&PeerState<<SP::Target as SignerProvider>::Signer>) -> bool {
+ where Filter: Fn(&PeerState<SP>) -> bool {
let mut peers_without_funded_channels = 0;
let best_block_height = self.best_block.read().unwrap().height();
{
}
fn unfunded_channel_count(
- peer: &PeerState<<SP::Target as SignerProvider>::Signer>, best_block_height: u32
+ peer: &PeerState<SP>, best_block_height: u32
) -> usize {
let mut num_unfunded_channels = 0;
- for (_, chan) in peer.channel_by_id.iter() {
- // This covers non-zero-conf inbound `Channel`s that we are currently monitoring, but those
- // which have not yet had any confirmations on-chain.
- if !chan.context.is_outbound() && chan.context.minimum_depth().unwrap_or(1) != 0 &&
- chan.context.get_funding_tx_confirmations(best_block_height) == 0
- {
- num_unfunded_channels += 1;
- }
- }
- for (_, chan) in peer.inbound_v1_channel_by_id.iter() {
- if chan.context.minimum_depth().unwrap_or(1) != 0 {
- num_unfunded_channels += 1;
+ for (_, phase) in peer.channel_by_id.iter() {
+ match phase {
+ ChannelPhase::Funded(chan) => {
+ // This covers non-zero-conf inbound `Channel`s that we are currently monitoring, but those
+ // which have not yet had any confirmations on-chain.
+ if !chan.context.is_outbound() && chan.context.minimum_depth().unwrap_or(1) != 0 &&
+ chan.context.get_funding_tx_confirmations(best_block_height) == 0
+ {
+ num_unfunded_channels += 1;
+ }
+ },
+ ChannelPhase::UnfundedInboundV1(chan) => {
+ if chan.context.minimum_depth().unwrap_or(1) != 0 {
+ num_unfunded_channels += 1;
+ }
+ },
+ ChannelPhase::UnfundedOutboundV1(_) => {
+ // Outbound channels don't contribute to the unfunded count in the DoS context.
+ continue;
+ }
}
}
- num_unfunded_channels
+ num_unfunded_channels + peer.inbound_channel_request_by_id.len()
}
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 {
return Err(MsgHandleErrInternal::send_err_msg_no_close("Unknown genesis block hash".to_owned(), msg.temporary_channel_id.clone()));
}
return Err(MsgHandleErrInternal::send_err_msg_no_close("No inbound channels accepted".to_owned(), msg.temporary_channel_id.clone()));
}
- let mut random_bytes = [0u8; 16];
- random_bytes.copy_from_slice(&self.entropy_source.get_secure_random_bytes()[..16]);
- let user_channel_id = u128::from_be_bytes(random_bytes);
- let outbound_scid_alias = self.create_and_insert_outbound_scid_alias();
-
// Get the number of peers with channels, but without funded ones. We don't care too much
// about peers that never open a channel, so we filter by peers that have at least one
// channel, and then limit the number of those with unfunded channels.
msg.temporary_channel_id.clone()));
}
+ let channel_id = msg.temporary_channel_id;
+ let channel_exists = peer_state.has_channel(&channel_id);
+ if channel_exists {
+ return Err(MsgHandleErrInternal::send_err_msg_no_close("temporary_channel_id collision for the same peer!".to_owned(), msg.temporary_channel_id.clone()));
+ }
+
+ // If we're doing manual acceptance checks on the channel, then defer creation until we're sure we want to accept.
+ if self.default_configuration.manually_accept_inbound_channels {
+ let mut pending_events = self.pending_events.lock().unwrap();
+ pending_events.push_back((events::Event::OpenChannelRequest {
+ temporary_channel_id: msg.temporary_channel_id.clone(),
+ counterparty_node_id: counterparty_node_id.clone(),
+ funding_satoshis: msg.funding_satoshis,
+ push_msat: msg.push_msat,
+ channel_type: msg.channel_type.clone().unwrap(),
+ }, None));
+ peer_state.inbound_channel_request_by_id.insert(channel_id, InboundChannelRequest {
+ open_channel_msg: msg.clone(),
+ ticks_remaining: UNACCEPTED_INBOUND_CHANNEL_AGE_LIMIT_TICKS,
+ });
+ return Ok(());
+ }
+
+ // Otherwise create the channel right now.
+ let mut random_bytes = [0u8; 16];
+ random_bytes.copy_from_slice(&self.entropy_source.get_secure_random_bytes()[..16]);
+ let user_channel_id = u128::from_be_bytes(random_bytes);
let mut channel = match InboundV1Channel::new(&self.fee_estimator, &self.entropy_source, &self.signer_provider,
counterparty_node_id.clone(), &self.channel_type_features(), &peer_state.latest_features, msg, user_channel_id,
- &self.default_configuration, best_block_height, &self.logger, outbound_scid_alias)
+ &self.default_configuration, best_block_height, &self.logger, /*is_0conf=*/false)
{
Err(e) => {
- self.outbound_scid_aliases.lock().unwrap().remove(&outbound_scid_alias);
return Err(MsgHandleErrInternal::from_chan_no_close(e, msg.temporary_channel_id));
},
Ok(res) => res
};
- let channel_id = channel.context.channel_id();
- let channel_exists = peer_state.has_channel(&channel_id);
- if channel_exists {
- self.outbound_scid_aliases.lock().unwrap().remove(&outbound_scid_alias);
- return Err(MsgHandleErrInternal::send_err_msg_no_close("temporary_channel_id collision for the same peer!".to_owned(), msg.temporary_channel_id.clone()))
- } else {
- if !self.default_configuration.manually_accept_inbound_channels {
- let channel_type = channel.context.get_channel_type();
- if channel_type.requires_zero_conf() {
- return Err(MsgHandleErrInternal::send_err_msg_no_close("No zero confirmation channels accepted".to_owned(), msg.temporary_channel_id.clone()));
- }
- if channel_type.requires_anchors_zero_fee_htlc_tx() {
- return Err(MsgHandleErrInternal::send_err_msg_no_close("No channels with anchor outputs accepted".to_owned(), msg.temporary_channel_id.clone()));
- }
- peer_state.pending_msg_events.push(events::MessageSendEvent::SendAcceptChannel {
- node_id: counterparty_node_id.clone(),
- msg: channel.accept_inbound_channel(user_channel_id),
- });
- } else {
- let mut pending_events = self.pending_events.lock().unwrap();
- pending_events.push_back((events::Event::OpenChannelRequest {
- temporary_channel_id: msg.temporary_channel_id.clone(),
- counterparty_node_id: counterparty_node_id.clone(),
- funding_satoshis: msg.funding_satoshis,
- push_msat: msg.push_msat,
- channel_type: channel.context.get_channel_type().clone(),
- }, None));
- }
- peer_state.inbound_v1_channel_by_id.insert(channel_id, channel);
+
+ let channel_type = channel.context.get_channel_type();
+ if channel_type.requires_zero_conf() {
+ return Err(MsgHandleErrInternal::send_err_msg_no_close("No zero confirmation channels accepted".to_owned(), msg.temporary_channel_id.clone()));
+ }
+ if channel_type.requires_anchors_zero_fee_htlc_tx() {
+ return Err(MsgHandleErrInternal::send_err_msg_no_close("No channels with anchor outputs accepted".to_owned(), msg.temporary_channel_id.clone()));
}
+
+ let outbound_scid_alias = self.create_and_insert_outbound_scid_alias();
+ channel.context.set_outbound_scid_alias(outbound_scid_alias);
+
+ peer_state.pending_msg_events.push(events::MessageSendEvent::SendAcceptChannel {
+ node_id: counterparty_node_id.clone(),
+ msg: channel.accept_inbound_channel(),
+ });
+ peer_state.channel_by_id.insert(channel_id, ChannelPhase::UnfundedInboundV1(channel));
Ok(())
}
fn internal_accept_channel(&self, counterparty_node_id: &PublicKey, msg: &msgs::AcceptChannel) -> Result<(), MsgHandleErrInternal> {
+ // Note that the ChannelManager is NOT re-persisted on disk after this, so any changes are
+ // likely to be lost on restart!
let (value, output_script, user_id) = {
let per_peer_state = self.per_peer_state.read().unwrap();
let peer_state_mutex = per_peer_state.get(counterparty_node_id)
})?;
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
let peer_state = &mut *peer_state_lock;
- match peer_state.outbound_v1_channel_by_id.entry(msg.temporary_channel_id) {
- hash_map::Entry::Occupied(mut chan) => {
- try_v1_outbound_chan_entry!(self, chan.get_mut().accept_channel(&msg, &self.default_configuration.channel_handshake_limits, &peer_state.latest_features), chan);
- (chan.get().context.get_value_satoshis(), chan.get().context.get_funding_redeemscript().to_v0_p2wsh(), chan.get().context.get_user_id())
+ match peer_state.channel_by_id.entry(msg.temporary_channel_id) {
+ hash_map::Entry::Occupied(mut phase) => {
+ match phase.get_mut() {
+ ChannelPhase::UnfundedOutboundV1(chan) => {
+ try_chan_phase_entry!(self, chan.accept_channel(&msg, &self.default_configuration.channel_handshake_limits, &peer_state.latest_features), phase);
+ (chan.context.get_value_satoshis(), chan.context.get_funding_redeemscript().to_v0_p2wsh(), chan.context.get_user_id())
+ },
+ _ => {
+ return Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Got an unexpected accept_channel message from peer with counterparty_node_id {}", counterparty_node_id), msg.temporary_channel_id));
+ }
+ }
},
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.temporary_channel_id))
}
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
let peer_state = &mut *peer_state_lock;
let (chan, funding_msg, monitor) =
- match peer_state.inbound_v1_channel_by_id.remove(&msg.temporary_channel_id) {
- Some(inbound_chan) => {
+ match peer_state.channel_by_id.remove(&msg.temporary_channel_id) {
+ Some(ChannelPhase::UnfundedInboundV1(inbound_chan)) => {
match inbound_chan.funding_created(msg, best_block, &self.signer_provider, &self.logger) {
Ok(res) => res,
Err((mut inbound_chan, err)) => {
let user_id = inbound_chan.context.get_user_id();
let shutdown_res = inbound_chan.context.force_shutdown(false);
return Err(MsgHandleErrInternal::from_finish_shutdown(format!("{}", err),
- msg.temporary_channel_id, user_id, shutdown_res, None));
+ msg.temporary_channel_id, user_id, shutdown_res, None, inbound_chan.context.get_value_satoshis()));
},
}
},
+ Some(ChannelPhase::Funded(_)) | Some(ChannelPhase::UnfundedOutboundV1(_)) => {
+ return Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Got an unexpected funding_created message from peer with counterparty_node_id {}", counterparty_node_id), msg.temporary_channel_id));
+ },
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.temporary_channel_id))
};
Err(MsgHandleErrInternal::send_err_msg_no_close("Already had channel with the new channel_id".to_owned(), funding_msg.channel_id))
},
hash_map::Entry::Vacant(e) => {
- match self.id_to_peer.lock().unwrap().entry(chan.context.channel_id()) {
+ let mut id_to_peer_lock = self.id_to_peer.lock().unwrap();
+ match id_to_peer_lock.entry(chan.context.channel_id()) {
hash_map::Entry::Occupied(_) => {
return Err(MsgHandleErrInternal::send_err_msg_no_close(
"The funding_created message had the same funding_txid as an existing channel - funding is not possible".to_owned(),
funding_msg.channel_id))
},
hash_map::Entry::Vacant(i_e) => {
- i_e.insert(chan.context.get_counterparty_node_id());
- }
- }
-
- // There's no problem signing a counterparty's funding transaction if our monitor
- // hasn't persisted to disk yet - we can't lose money on a transaction that we haven't
- // accepted payment from yet. We do, however, need to wait to send our channel_ready
- // until we have persisted our monitor.
- let new_channel_id = funding_msg.channel_id;
- peer_state.pending_msg_events.push(events::MessageSendEvent::SendFundingSigned {
- node_id: counterparty_node_id.clone(),
- msg: funding_msg,
- });
+ let monitor_res = self.chain_monitor.watch_channel(monitor.get_funding_txo().0, monitor);
+ if let Ok(persist_state) = monitor_res {
+ i_e.insert(chan.context.get_counterparty_node_id());
+ mem::drop(id_to_peer_lock);
+
+ // There's no problem signing a counterparty's funding transaction if our monitor
+ // hasn't persisted to disk yet - we can't lose money on a transaction that we haven't
+ // accepted payment from yet. We do, however, need to wait to send our channel_ready
+ // until we have persisted our monitor.
+ peer_state.pending_msg_events.push(events::MessageSendEvent::SendFundingSigned {
+ node_id: counterparty_node_id.clone(),
+ msg: funding_msg,
+ });
- let monitor_res = self.chain_monitor.watch_channel(monitor.get_funding_txo().0, monitor);
-
- let chan = e.insert(chan);
- let mut res = handle_new_monitor_update!(self, monitor_res, peer_state_lock, peer_state,
- per_peer_state, chan, MANUALLY_REMOVING_INITIAL_MONITOR,
- { peer_state.channel_by_id.remove(&new_channel_id) });
-
- // Note that we reply with the new channel_id in error messages if we gave up on the
- // channel, not the temporary_channel_id. This is compatible with ourselves, but the
- // spec is somewhat ambiguous here. Not a huge deal since we'll send error messages for
- // any messages referencing a previously-closed channel anyway.
- // We do not propagate the monitor update to the user as it would be for a monitor
- // that we didn't manage to store (and that we don't care about - we don't respond
- // with the funding_signed so the channel can never go on chain).
- if let Err(MsgHandleErrInternal { shutdown_finish: Some((res, _)), .. }) = &mut res {
- res.0 = None;
+ if let ChannelPhase::Funded(chan) = e.insert(ChannelPhase::Funded(chan)) {
+ handle_new_monitor_update!(self, persist_state, peer_state_lock, peer_state,
+ per_peer_state, chan, INITIAL_MONITOR);
+ } else {
+ unreachable!("This must be a funded channel as we just inserted it.");
+ }
+ Ok(())
+ } else {
+ log_error!(self.logger, "Persisting initial ChannelMonitor failed, implying the funding outpoint was duplicated");
+ return Err(MsgHandleErrInternal::send_err_msg_no_close(
+ "The funding_created message had the same funding_txid as an existing channel - funding is not possible".to_owned(),
+ funding_msg.channel_id));
+ }
+ }
}
- res.map(|_| ())
}
}
}
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) {
- hash_map::Entry::Occupied(mut chan) => {
- let monitor = try_chan_entry!(self,
- chan.get_mut().funding_signed(&msg, best_block, &self.signer_provider, &self.logger), chan);
- let update_res = self.chain_monitor.watch_channel(chan.get().context.get_funding_txo().unwrap(), monitor);
- let mut res = handle_new_monitor_update!(self, update_res, peer_state_lock, peer_state, per_peer_state, chan, INITIAL_MONITOR);
- if let Err(MsgHandleErrInternal { ref mut shutdown_finish, .. }) = res {
- // We weren't able to watch the channel to begin with, so no updates should be made on
- // it. Previously, full_stack_target found an (unreachable) panic when the
- // monitor update contained within `shutdown_finish` was applied.
- if let Some((ref mut shutdown_finish, _)) = shutdown_finish {
- shutdown_finish.0.take();
- }
+ hash_map::Entry::Occupied(mut chan_phase_entry) => {
+ match chan_phase_entry.get_mut() {
+ ChannelPhase::Funded(ref mut chan) => {
+ let monitor = try_chan_phase_entry!(self,
+ chan.funding_signed(&msg, best_block, &self.signer_provider, &self.logger), chan_phase_entry);
+ if let Ok(persist_status) = self.chain_monitor.watch_channel(chan.context.get_funding_txo().unwrap(), monitor) {
+ handle_new_monitor_update!(self, persist_status, peer_state_lock, peer_state, per_peer_state, chan, INITIAL_MONITOR);
+ Ok(())
+ } else {
+ try_chan_phase_entry!(self, Err(ChannelError::Close("Channel funding outpoint was a duplicate".to_owned())), chan_phase_entry)
+ }
+ },
+ _ => {
+ return Err(MsgHandleErrInternal::send_err_msg_no_close("Failed to find corresponding channel".to_owned(), msg.channel_id));
+ },
}
- res.map(|_| ())
},
hash_map::Entry::Vacant(_) => return Err(MsgHandleErrInternal::send_err_msg_no_close("Failed to find corresponding channel".to_owned(), msg.channel_id))
}
}
fn internal_channel_ready(&self, counterparty_node_id: &PublicKey, msg: &msgs::ChannelReady) -> Result<(), MsgHandleErrInternal> {
+ // Note that the ChannelManager is NOT re-persisted on disk after this (unless we error
+ // closing a channel), so any changes are likely to be lost on restart!
let per_peer_state = self.per_peer_state.read().unwrap();
let peer_state_mutex = per_peer_state.get(counterparty_node_id)
.ok_or_else(|| {
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) {
- hash_map::Entry::Occupied(mut chan) => {
- let announcement_sigs_opt = try_chan_entry!(self, chan.get_mut().channel_ready(&msg, &self.node_signer,
- self.genesis_hash.clone(), &self.default_configuration, &self.best_block.read().unwrap(), &self.logger), chan);
- if let Some(announcement_sigs) = announcement_sigs_opt {
- log_trace!(self.logger, "Sending announcement_signatures for channel {}", log_bytes!(chan.get().context.channel_id()));
- peer_state.pending_msg_events.push(events::MessageSendEvent::SendAnnouncementSignatures {
- node_id: counterparty_node_id.clone(),
- msg: announcement_sigs,
- });
- } else if chan.get().context.is_usable() {
- // If we're sending an announcement_signatures, we'll send the (public)
- // channel_update after sending a channel_announcement when we receive our
- // counterparty's announcement_signatures. Thus, we only bother to send a
- // channel_update here if the channel is not public, i.e. we're not sending an
- // announcement_signatures.
- log_trace!(self.logger, "Sending private initial channel_update for our counterparty on channel {}", log_bytes!(chan.get().context.channel_id()));
- if let Ok(msg) = self.get_channel_update_for_unicast(chan.get()) {
- peer_state.pending_msg_events.push(events::MessageSendEvent::SendChannelUpdate {
+ 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);
+ 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 {
node_id: counterparty_node_id.clone(),
- msg,
+ msg: announcement_sigs,
});
+ } else if chan.context.is_usable() {
+ // If we're sending an announcement_signatures, we'll send the (public)
+ // channel_update after sending a channel_announcement when we receive our
+ // counterparty's announcement_signatures. Thus, we only bother to send a
+ // channel_update here if the channel is not public, i.e. we're not sending an
+ // announcement_signatures.
+ log_trace!(self.logger, "Sending private initial channel_update for our counterparty on channel {}", chan.context.channel_id());
+ if let Ok(msg) = self.get_channel_update_for_unicast(chan) {
+ peer_state.pending_msg_events.push(events::MessageSendEvent::SendChannelUpdate {
+ node_id: counterparty_node_id.clone(),
+ msg,
+ });
+ }
}
- }
- {
- let mut pending_events = self.pending_events.lock().unwrap();
- emit_channel_ready_event!(pending_events, chan.get_mut());
- }
+ {
+ let mut pending_events = self.pending_events.lock().unwrap();
+ emit_channel_ready_event!(pending_events, chan);
+ }
- Ok(())
+ Ok(())
+ } else {
+ try_chan_phase_entry!(self, Err(ChannelError::Close(
+ "Got a channel_ready message for an unfunded channel!".into())), chan_phase_entry)
+ }
},
- hash_map::Entry::Vacant(_) => 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(_) => {
+ 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))
+ }
}
}
fn internal_shutdown(&self, counterparty_node_id: &PublicKey, msg: &msgs::Shutdown) -> Result<(), MsgHandleErrInternal> {
- let mut dropped_htlcs: Vec<(HTLCSource, PaymentHash)>;
- let result: Result<(), _> = loop {
+ let mut dropped_htlcs: Vec<(HTLCSource, PaymentHash)> = Vec::new();
+ let mut finish_shutdown = None;
+ {
let per_peer_state = self.per_peer_state.read().unwrap();
let peer_state_mutex = per_peer_state.get(counterparty_node_id)
.ok_or_else(|| {
})?;
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_entry) => {
-
- if !chan_entry.get().received_shutdown() {
- log_info!(self.logger, "Received a shutdown message from our counterparty for channel {}{}.",
- log_bytes!(msg.channel_id),
- if chan_entry.get().sent_shutdown() { " after we initiated shutdown" } else { "" });
- }
-
- let funding_txo_opt = chan_entry.get().context.get_funding_txo();
- let (shutdown, monitor_update_opt, htlcs) = try_chan_entry!(self,
- chan_entry.get_mut().shutdown(&self.signer_provider, &peer_state.latest_features, &msg), chan_entry);
- dropped_htlcs = htlcs;
+ if let hash_map::Entry::Occupied(mut chan_phase_entry) = peer_state.channel_by_id.entry(msg.channel_id.clone()) {
+ let phase = chan_phase_entry.get_mut();
+ match phase {
+ ChannelPhase::Funded(chan) => {
+ if !chan.received_shutdown() {
+ log_info!(self.logger, "Received a shutdown message from our counterparty for channel {}{}.",
+ msg.channel_id,
+ if chan.sent_shutdown() { " after we initiated shutdown" } else { "" });
+ }
- if let Some(msg) = 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
- // `shutdown_signed`, which we'll delay if we're pending a monitor update.
- peer_state.pending_msg_events.push(events::MessageSendEvent::SendShutdown {
- node_id: *counterparty_node_id,
- msg,
- });
- }
+ let funding_txo_opt = chan.context.get_funding_txo();
+ let (shutdown, monitor_update_opt, htlcs) = try_chan_phase_entry!(self,
+ chan.shutdown(&self.signer_provider, &peer_state.latest_features, &msg), chan_phase_entry);
+ dropped_htlcs = htlcs;
- // Update the monitor with the shutdown script if necessary.
- if let Some(monitor_update) = monitor_update_opt {
- break handle_new_monitor_update!(self, funding_txo_opt.unwrap(), monitor_update,
- peer_state_lock, peer_state, per_peer_state, chan_entry).map(|_| ());
- }
- break Ok(());
- },
- 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))
+ if let Some(msg) = 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
+ // `shutdown_signed`, which we'll delay if we're pending a monitor update.
+ peer_state.pending_msg_events.push(events::MessageSendEvent::SendShutdown {
+ node_id: *counterparty_node_id,
+ msg,
+ });
+ }
+ // Update the monitor with the shutdown script if necessary.
+ if let Some(monitor_update) = monitor_update_opt {
+ handle_new_monitor_update!(self, funding_txo_opt.unwrap(), monitor_update,
+ peer_state_lock, peer_state, per_peer_state, chan);
+ }
+ },
+ ChannelPhase::UnfundedInboundV1(_) | ChannelPhase::UnfundedOutboundV1(_) => {
+ let context = phase.context_mut();
+ log_error!(self.logger, "Immediately closing unfunded channel {} as peer asked to cooperatively shut it down (which is unnecessary)", &msg.channel_id);
+ self.issue_channel_close_events(&context, ClosureReason::CounterpartyCoopClosedUnfundedChannel);
+ let mut chan = remove_channel_phase!(self, chan_phase_entry);
+ finish_shutdown = Some(chan.context_mut().force_shutdown(false));
+ },
+ }
+ } else {
+ 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))
}
- };
+ }
for htlc_source in dropped_htlcs.drain(..) {
let receiver = HTLCDestination::NextHopChannel { node_id: Some(counterparty_node_id.clone()), channel_id: msg.channel_id };
let reason = HTLCFailReason::from_failure_code(0x4000 | 8);
self.fail_htlc_backwards_internal(&htlc_source.0, &htlc_source.1, &reason, receiver);
}
+ if let Some(shutdown_res) = finish_shutdown {
+ self.finish_close_channel(shutdown_res);
+ }
- result
+ Ok(())
}
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(|| {
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_entry) => {
- let (closing_signed, tx) = try_chan_entry!(self, chan_entry.get_mut().closing_signed(&self.fee_estimator, &msg), chan_entry);
- if let Some(msg) = closing_signed {
- peer_state.pending_msg_events.push(events::MessageSendEvent::SendClosingSigned {
- node_id: counterparty_node_id.clone(),
- msg,
- });
+ 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);
+ if let Some(msg) = closing_signed {
+ peer_state.pending_msg_events.push(events::MessageSendEvent::SendClosingSigned {
+ node_id: counterparty_node_id.clone(),
+ msg,
+ });
+ }
+ if tx.is_some() {
+ // We're done with this channel, we've got a signed closing transaction and
+ // will send the closing_signed back to the remote peer upon return. This
+ // 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) }
+ } else {
+ return try_chan_phase_entry!(self, Err(ChannelError::Close(
+ "Got a closing_signed message for an unfunded channel!".into())), chan_phase_entry);
}
- if tx.is_some() {
- // We're done with this channel, we've got a signed closing transaction and
- // will send the closing_signed back to the remote peer upon return. This
- // 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!(self, chan_entry)))
- } else { (tx, None) }
},
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))
}
log_info!(self.logger, "Broadcasting {}", log_tx!(broadcast_tx));
self.tx_broadcaster.broadcast_transactions(&[&broadcast_tx]);
}
- if let Some(chan) = chan_option {
+ if let Some(ChannelPhase::Funded(chan)) = chan_option {
if let Ok(update) = self.get_channel_update_for_broadcast(&chan) {
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
let peer_state = &mut *peer_state_lock;
});
}
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 {
+ self.finish_close_channel(shutdown_result);
}
Ok(())
}
//encrypted with the same key. It's not immediately obvious how to usefully exploit that,
//but we should prevent it anyway.
+ // Note that the ChannelManager is NOT re-persisted on disk after this (unless we error
+ // closing a channel), so any changes are likely to be lost on restart!
+
let decoded_hop_res = self.decode_update_add_htlc_onion(msg);
let per_peer_state = self.per_peer_state.read().unwrap();
let peer_state_mutex = per_peer_state.get(counterparty_node_id)
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
let peer_state = &mut *peer_state_lock;
match peer_state.channel_by_id.entry(msg.channel_id) {
- hash_map::Entry::Occupied(mut chan) => {
-
- let pending_forward_info = match decoded_hop_res {
- Ok((next_hop, shared_secret, next_packet_pk_opt)) =>
- self.construct_pending_htlc_status(msg, shared_secret, next_hop,
- chan.get().context.config().accept_underpaying_htlcs, next_packet_pk_opt),
- Err(e) => PendingHTLCStatus::Fail(e)
- };
- let create_pending_htlc_status = |chan: &Channel<<SP::Target as SignerProvider>::Signer>, pending_forward_info: PendingHTLCStatus, error_code: u16| {
- // If the update_add is completely bogus, the call will Err and we will close,
- // but if we've sent a shutdown and they haven't acknowledged it yet, we just
- // want to reject the new HTLC and fail it backwards instead of forwarding.
- match pending_forward_info {
- PendingHTLCStatus::Forward(PendingHTLCInfo { ref incoming_shared_secret, .. }) => {
- let reason = if (error_code & 0x1000) != 0 {
- let (real_code, error_data) = self.get_htlc_inbound_temp_fail_err_and_data(error_code, chan);
- HTLCFailReason::reason(real_code, error_data)
- } else {
- HTLCFailReason::from_failure_code(error_code)
- }.get_encrypted_failure_packet(incoming_shared_secret, &None);
- let msg = msgs::UpdateFailHTLC {
- channel_id: msg.channel_id,
- htlc_id: msg.htlc_id,
- reason
- };
- PendingHTLCStatus::Fail(HTLCFailureMsg::Relay(msg))
- },
- _ => pending_forward_info
- }
- };
- try_chan_entry!(self, chan.get_mut().update_add_htlc(&msg, pending_forward_info, create_pending_htlc_status, &self.logger), chan);
+ hash_map::Entry::Occupied(mut chan_phase_entry) => {
+ if let ChannelPhase::Funded(chan) = chan_phase_entry.get_mut() {
+ let pending_forward_info = match decoded_hop_res {
+ Ok((next_hop, shared_secret, next_packet_pk_opt)) =>
+ self.construct_pending_htlc_status(msg, shared_secret, next_hop,
+ chan.context.config().accept_underpaying_htlcs, next_packet_pk_opt),
+ Err(e) => PendingHTLCStatus::Fail(e)
+ };
+ let create_pending_htlc_status = |chan: &Channel<SP>, pending_forward_info: PendingHTLCStatus, error_code: u16| {
+ // If the update_add is completely bogus, the call will Err and we will close,
+ // but if we've sent a shutdown and they haven't acknowledged it yet, we just
+ // want to reject the new HTLC and fail it backwards instead of forwarding.
+ match pending_forward_info {
+ PendingHTLCStatus::Forward(PendingHTLCInfo { ref incoming_shared_secret, .. }) => {
+ let reason = if (error_code & 0x1000) != 0 {
+ let (real_code, error_data) = self.get_htlc_inbound_temp_fail_err_and_data(error_code, chan);
+ HTLCFailReason::reason(real_code, error_data)
+ } else {
+ HTLCFailReason::from_failure_code(error_code)
+ }.get_encrypted_failure_packet(incoming_shared_secret, &None);
+ let msg = msgs::UpdateFailHTLC {
+ channel_id: msg.channel_id,
+ htlc_id: msg.htlc_id,
+ reason
+ };
+ PendingHTLCStatus::Fail(HTLCFailureMsg::Relay(msg))
+ },
+ _ => pending_forward_info
+ }
+ };
+ try_chan_phase_entry!(self, chan.update_add_htlc(&msg, pending_forward_info, create_pending_htlc_status, &self.fee_estimator, &self.logger), chan_phase_entry);
+ } else {
+ return try_chan_phase_entry!(self, Err(ChannelError::Close(
+ "Got an update_add_htlc 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))
}
}
fn internal_update_fulfill_htlc(&self, counterparty_node_id: &PublicKey, msg: &msgs::UpdateFulfillHTLC) -> Result<(), MsgHandleErrInternal> {
+ let funding_txo;
let (htlc_source, forwarded_htlc_value) = {
let per_peer_state = self.per_peer_state.read().unwrap();
let peer_state_mutex = per_peer_state.get(counterparty_node_id)
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
let peer_state = &mut *peer_state_lock;
match peer_state.channel_by_id.entry(msg.channel_id) {
- hash_map::Entry::Occupied(mut chan) => {
- try_chan_entry!(self, chan.get_mut().update_fulfill_htlc(&msg), chan)
+ hash_map::Entry::Occupied(mut chan_phase_entry) => {
+ 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 {
+ peer_state.actions_blocking_raa_monitor_updates.entry(msg.channel_id)
+ .or_insert_with(Vec::new)
+ .push(RAAMonitorUpdateBlockingAction::from_prev_hop_data(&prev_hop));
+ }
+ // Note that we do not need to push an `actions_blocking_raa_monitor_updates`
+ // entry here, even though we *do* need to block the next RAA monitor update.
+ // We do this instead in the `claim_funds_internal` by attaching a
+ // `ReleaseRAAChannelMonitorUpdate` action to the event generated when the
+ // outbound HTLC is claimed. This is guaranteed to all complete before we
+ // process the RAA as messages are processed from single peers serially.
+ funding_txo = chan.context.get_funding_txo().expect("We won't accept a fulfill until funded");
+ res
+ } else {
+ return try_chan_phase_entry!(self, Err(ChannelError::Close(
+ "Got an update_fulfill_htlc 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))
}
};
- self.claim_funds_internal(htlc_source, msg.payment_preimage.clone(), Some(forwarded_htlc_value), false, msg.channel_id);
+ self.claim_funds_internal(htlc_source, msg.payment_preimage.clone(), Some(forwarded_htlc_value), false, Some(*counterparty_node_id), funding_txo);
Ok(())
}
fn internal_update_fail_htlc(&self, counterparty_node_id: &PublicKey, msg: &msgs::UpdateFailHTLC) -> Result<(), MsgHandleErrInternal> {
+ // Note that the ChannelManager is NOT re-persisted on disk after this (unless we error
+ // closing a channel), so any changes are likely to be lost on restart!
let per_peer_state = self.per_peer_state.read().unwrap();
let peer_state_mutex = per_peer_state.get(counterparty_node_id)
.ok_or_else(|| {
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) {
- hash_map::Entry::Occupied(mut chan) => {
- try_chan_entry!(self, chan.get_mut().update_fail_htlc(&msg, HTLCFailReason::from_msg(msg)), chan);
+ hash_map::Entry::Occupied(mut chan_phase_entry) => {
+ if let ChannelPhase::Funded(chan) = chan_phase_entry.get_mut() {
+ try_chan_phase_entry!(self, chan.update_fail_htlc(&msg, HTLCFailReason::from_msg(msg)), chan_phase_entry);
+ } else {
+ return try_chan_phase_entry!(self, Err(ChannelError::Close(
+ "Got an update_fail_htlc 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))
}
}
fn internal_update_fail_malformed_htlc(&self, counterparty_node_id: &PublicKey, msg: &msgs::UpdateFailMalformedHTLC) -> Result<(), MsgHandleErrInternal> {
+ // Note that the ChannelManager is NOT re-persisted on disk after this (unless we error
+ // closing a channel), so any changes are likely to be lost on restart!
let per_peer_state = self.per_peer_state.read().unwrap();
let peer_state_mutex = per_peer_state.get(counterparty_node_id)
.ok_or_else(|| {
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) {
- hash_map::Entry::Occupied(mut chan) => {
+ hash_map::Entry::Occupied(mut chan_phase_entry) => {
if (msg.failure_code & 0x8000) == 0 {
let chan_err: ChannelError = ChannelError::Close("Got update_fail_malformed_htlc with BADONION not set".to_owned());
- try_chan_entry!(self, Err(chan_err), chan);
+ try_chan_phase_entry!(self, Err(chan_err), chan_phase_entry);
+ }
+ if let ChannelPhase::Funded(chan) = chan_phase_entry.get_mut() {
+ try_chan_phase_entry!(self, chan.update_fail_malformed_htlc(&msg, HTLCFailReason::reason(msg.failure_code, msg.sha256_of_onion.to_vec())), chan_phase_entry);
+ } else {
+ return try_chan_phase_entry!(self, Err(ChannelError::Close(
+ "Got an update_fail_malformed_htlc message for an unfunded channel!".into())), chan_phase_entry);
}
- try_chan_entry!(self, chan.get_mut().update_fail_malformed_htlc(&msg, HTLCFailReason::reason(msg.failure_code, msg.sha256_of_onion.to_vec())), chan);
Ok(())
},
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))
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) {
- hash_map::Entry::Occupied(mut chan) => {
- let funding_txo = chan.get().context.get_funding_txo();
- let monitor_update_opt = try_chan_entry!(self, chan.get_mut().commitment_signed(&msg, &self.logger), chan);
- if let Some(monitor_update) = monitor_update_opt {
- handle_new_monitor_update!(self, funding_txo.unwrap(), monitor_update, peer_state_lock,
- peer_state, per_peer_state, chan).map(|_| ())
- } else { Ok(()) }
+ hash_map::Entry::Occupied(mut chan_phase_entry) => {
+ if let ChannelPhase::Funded(chan) = chan_phase_entry.get_mut() {
+ let funding_txo = chan.context.get_funding_txo();
+ let monitor_update_opt = try_chan_phase_entry!(self, chan.commitment_signed(&msg, &self.logger), chan_phase_entry);
+ if let Some(monitor_update) = monitor_update_opt {
+ handle_new_monitor_update!(self, funding_txo.unwrap(), monitor_update, peer_state_lock,
+ peer_state, per_peer_state, chan);
+ }
+ Ok(())
+ } else {
+ return try_chan_phase_entry!(self, Err(ChannelError::Close(
+ "Got a commitment_signed 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))
}
log_info!(self.logger, "Failed to forward incoming HTLC: detected duplicate intercepted payment over short channel id {}", scid);
let htlc_source = HTLCSource::PreviousHopData(HTLCPreviousHopData {
short_channel_id: prev_short_channel_id,
+ user_channel_id: Some(prev_user_channel_id),
outpoint: prev_funding_outpoint,
htlc_id: prev_htlc_id,
incoming_packet_shared_secret: forward_info.incoming_shared_secret,
}
}
- // We only want to push a PendingHTLCsForwardable event if no others are queued.
fn push_pending_forwards_ev(&self) {
let mut pending_events = self.pending_events.lock().unwrap();
- let forward_ev_exists = pending_events.iter()
- .find(|(ev, _)| if let events::Event::PendingHTLCsForwardable { .. } = ev { true } else { false })
- .is_some();
- if !forward_ev_exists {
- pending_events.push_back((events::Event::PendingHTLCsForwardable {
- time_forwardable:
- Duration::from_millis(MIN_HTLC_RELAY_HOLDING_CELL_MILLIS),
+ let is_processing_events = self.pending_events_processor.load(Ordering::Acquire);
+ let num_forward_events = pending_events.iter().filter(|(ev, _)|
+ if let events::Event::PendingHTLCsForwardable { .. } = ev { true } else { false }
+ ).count();
+ // We only want to push a PendingHTLCsForwardable event if no others are queued. Processing
+ // events is done in batches and they are not removed until we're done processing each
+ // batch. Since handling a `PendingHTLCsForwardable` event will call back into the
+ // `ChannelManager`, we'll still see the original forwarding event not removed. Phantom
+ // payments will need an additional forwarding event before being claimed to make them look
+ // real by taking more time.
+ if (is_processing_events && num_forward_events <= 1) || num_forward_events < 1 {
+ pending_events.push_back((Event::PendingHTLCsForwardable {
+ time_forwardable: Duration::from_millis(MIN_HTLC_RELAY_HOLDING_CELL_MILLIS),
}, None));
}
}
/// Checks whether [`ChannelMonitorUpdate`]s generated by the receipt of a remote
- /// [`msgs::RevokeAndACK`] should be held for the given channel until some other event
+ /// [`msgs::RevokeAndACK`] should be held for the given channel until some other action
/// completes. Note that this needs to happen in the same [`PeerState`] mutex as any release of
/// the [`ChannelMonitorUpdate`] in question.
fn raa_monitor_updates_held(&self,
- actions_blocking_raa_monitor_updates: &BTreeMap<[u8; 32], Vec<RAAMonitorUpdateBlockingAction>>,
+ actions_blocking_raa_monitor_updates: &BTreeMap<ChannelId, Vec<RAAMonitorUpdateBlockingAction>>,
channel_funding_outpoint: OutPoint, counterparty_node_id: PublicKey
) -> bool {
actions_blocking_raa_monitor_updates
})
}
+ #[cfg(any(test, feature = "_test_utils"))]
+ pub(crate) fn test_raa_monitor_updates_held(&self,
+ counterparty_node_id: PublicKey, channel_id: ChannelId
+ ) -> bool {
+ let per_peer_state = self.per_peer_state.read().unwrap();
+ if let Some(peer_state_mtx) = per_peer_state.get(&counterparty_node_id) {
+ let mut peer_state_lck = peer_state_mtx.lock().unwrap();
+ let peer_state = &mut *peer_state_lck;
+
+ if let Some(chan) = peer_state.channel_by_id.get(&channel_id) {
+ return self.raa_monitor_updates_held(&peer_state.actions_blocking_raa_monitor_updates,
+ chan.context().get_funding_txo().unwrap(), counterparty_node_id);
+ }
+ }
+ false
+ }
+
fn internal_revoke_and_ack(&self, counterparty_node_id: &PublicKey, msg: &msgs::RevokeAndACK) -> Result<(), MsgHandleErrInternal> {
- let (htlcs_to_fail, res) = {
+ let htlcs_to_fail = {
let per_peer_state = self.per_peer_state.read().unwrap();
let mut peer_state_lock = per_peer_state.get(counterparty_node_id)
.ok_or_else(|| {
}).map(|mtx| mtx.lock().unwrap())?;
let peer_state = &mut *peer_state_lock;
match peer_state.channel_by_id.entry(msg.channel_id) {
- hash_map::Entry::Occupied(mut chan) => {
- let funding_txo = chan.get().context.get_funding_txo();
- let (htlcs_to_fail, monitor_update_opt) = try_chan_entry!(self, chan.get_mut().revoke_and_ack(&msg, &self.logger), chan);
- let res = if let Some(monitor_update) = monitor_update_opt {
- handle_new_monitor_update!(self, funding_txo.unwrap(), monitor_update,
- peer_state_lock, peer_state, per_peer_state, chan).map(|_| ())
- } else { Ok(()) };
- (htlcs_to_fail, res)
+ hash_map::Entry::Occupied(mut chan_phase_entry) => {
+ if let ChannelPhase::Funded(chan) = chan_phase_entry.get_mut() {
+ let funding_txo_opt = chan.context.get_funding_txo();
+ let mon_update_blocked = if let Some(funding_txo) = funding_txo_opt {
+ self.raa_monitor_updates_held(
+ &peer_state.actions_blocking_raa_monitor_updates, funding_txo,
+ *counterparty_node_id)
+ } else { false };
+ let (htlcs_to_fail, monitor_update_opt) = try_chan_phase_entry!(self,
+ chan.revoke_and_ack(&msg, &self.fee_estimator, &self.logger, mon_update_blocked), chan_phase_entry);
+ if let Some(monitor_update) = monitor_update_opt {
+ let funding_txo = funding_txo_opt
+ .expect("Funding outpoint must have been set for RAA handling to succeed");
+ handle_new_monitor_update!(self, funding_txo, monitor_update,
+ peer_state_lock, peer_state, per_peer_state, chan);
+ }
+ htlcs_to_fail
+ } else {
+ return try_chan_phase_entry!(self, Err(ChannelError::Close(
+ "Got a revoke_and_ack 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))
}
};
self.fail_holding_cell_htlcs(htlcs_to_fail, msg.channel_id, counterparty_node_id);
- res
+ Ok(())
}
fn internal_update_fee(&self, counterparty_node_id: &PublicKey, msg: &msgs::UpdateFee) -> Result<(), MsgHandleErrInternal> {
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) {
- hash_map::Entry::Occupied(mut chan) => {
- try_chan_entry!(self, chan.get_mut().update_fee(&self.fee_estimator, &msg, &self.logger), chan);
+ hash_map::Entry::Occupied(mut chan_phase_entry) => {
+ if let ChannelPhase::Funded(chan) = chan_phase_entry.get_mut() {
+ try_chan_phase_entry!(self, chan.update_fee(&self.fee_estimator, &msg, &self.logger), chan_phase_entry);
+ } else {
+ return try_chan_phase_entry!(self, Err(ChannelError::Close(
+ "Got an update_fee 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))
}
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) {
- hash_map::Entry::Occupied(mut chan) => {
- if !chan.get().context.is_usable() {
- return Err(MsgHandleErrInternal::from_no_close(LightningError{err: "Got an announcement_signatures before we were ready for it".to_owned(), action: msgs::ErrorAction::IgnoreError}));
- }
+ hash_map::Entry::Occupied(mut chan_phase_entry) => {
+ if let ChannelPhase::Funded(chan) = chan_phase_entry.get_mut() {
+ if !chan.context.is_usable() {
+ return Err(MsgHandleErrInternal::from_no_close(LightningError{err: "Got an announcement_signatures before we were ready for it".to_owned(), action: msgs::ErrorAction::IgnoreError}));
+ }
- peer_state.pending_msg_events.push(events::MessageSendEvent::BroadcastChannelAnnouncement {
- msg: try_chan_entry!(self, chan.get_mut().announcement_signatures(
- &self.node_signer, self.genesis_hash.clone(), self.best_block.read().unwrap().height(),
- msg, &self.default_configuration
- ), chan),
- // Note that announcement_signatures fails if the channel cannot be announced,
- // so get_channel_update_for_broadcast will never fail by the time we get here.
- update_msg: Some(self.get_channel_update_for_broadcast(chan.get()).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(),
+ msg, &self.default_configuration
+ ), chan_phase_entry),
+ // Note that announcement_signatures fails if the channel cannot be announced,
+ // so get_channel_update_for_broadcast will never fail by the time we get here.
+ update_msg: Some(self.get_channel_update_for_broadcast(chan).unwrap()),
+ });
+ } else {
+ return try_chan_phase_entry!(self, Err(ChannelError::Close(
+ "Got an announcement_signatures 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))
}
Ok(())
}
- /// Returns ShouldPersist if anything changed, otherwise either SkipPersist or an Err.
+ /// Returns DoPersist if anything changed, otherwise either SkipPersistNoEvents or an Err.
fn internal_channel_update(&self, counterparty_node_id: &PublicKey, msg: &msgs::ChannelUpdate) -> Result<NotifyOption, MsgHandleErrInternal> {
let (chan_counterparty_node_id, chan_id) = match self.short_to_chan_info.read().unwrap().get(&msg.contents.short_channel_id) {
Some((cp_id, chan_id)) => (cp_id.clone(), chan_id.clone()),
None => {
// It's not a local channel
- return Ok(NotifyOption::SkipPersist)
+ return Ok(NotifyOption::SkipPersistNoEvents)
}
};
let per_peer_state = self.per_peer_state.read().unwrap();
let peer_state_mutex_opt = per_peer_state.get(&chan_counterparty_node_id);
if peer_state_mutex_opt.is_none() {
- return Ok(NotifyOption::SkipPersist)
+ return Ok(NotifyOption::SkipPersistNoEvents)
}
let mut peer_state_lock = peer_state_mutex_opt.unwrap().lock().unwrap();
let peer_state = &mut *peer_state_lock;
match peer_state.channel_by_id.entry(chan_id) {
- hash_map::Entry::Occupied(mut chan) => {
- if chan.get().context.get_counterparty_node_id() != *counterparty_node_id {
- if chan.get().context.should_announce() {
- // If the announcement is about a channel of ours which is public, some
- // other peer may simply be forwarding all its gossip to us. Don't provide
- // a scary-looking error message and return Ok instead.
- return Ok(NotifyOption::SkipPersist);
+ hash_map::Entry::Occupied(mut chan_phase_entry) => {
+ if let ChannelPhase::Funded(chan) = chan_phase_entry.get_mut() {
+ if chan.context.get_counterparty_node_id() != *counterparty_node_id {
+ if chan.context.should_announce() {
+ // If the announcement is about a channel of ours which is public, some
+ // other peer may simply be forwarding all its gossip to us. Don't provide
+ // a scary-looking error message and return Ok instead.
+ return Ok(NotifyOption::SkipPersistNoEvents);
+ }
+ return Err(MsgHandleErrInternal::send_err_msg_no_close("Got a channel_update for a channel from the wrong node - it shouldn't know about our private channels!".to_owned(), chan_id));
+ }
+ let were_node_one = self.get_our_node_id().serialize()[..] < chan.context.get_counterparty_node_id().serialize()[..];
+ let msg_from_node_one = msg.contents.flags & 1 == 0;
+ if were_node_one == msg_from_node_one {
+ return Ok(NotifyOption::SkipPersistNoEvents);
+ } else {
+ log_debug!(self.logger, "Received channel_update for channel {}.", chan_id);
+ try_chan_phase_entry!(self, chan.channel_update(&msg), chan_phase_entry);
}
- return Err(MsgHandleErrInternal::send_err_msg_no_close("Got a channel_update for a channel from the wrong node - it shouldn't know about our private channels!".to_owned(), chan_id));
- }
- let were_node_one = self.get_our_node_id().serialize()[..] < chan.get().context.get_counterparty_node_id().serialize()[..];
- let msg_from_node_one = msg.contents.flags & 1 == 0;
- if were_node_one == msg_from_node_one {
- return Ok(NotifyOption::SkipPersist);
} else {
- log_debug!(self.logger, "Received channel_update for channel {}.", log_bytes!(chan_id));
- try_chan_entry!(self, chan.get_mut().channel_update(&msg), chan);
+ return try_chan_phase_entry!(self, Err(ChannelError::Close(
+ "Got a channel_update for an unfunded channel!".into())), chan_phase_entry);
}
},
- hash_map::Entry::Vacant(_) => return Ok(NotifyOption::SkipPersist)
+ hash_map::Entry::Vacant(_) => return Ok(NotifyOption::SkipPersistNoEvents)
}
Ok(NotifyOption::DoPersist)
}
- fn internal_channel_reestablish(&self, counterparty_node_id: &PublicKey, msg: &msgs::ChannelReestablish) -> Result<(), MsgHandleErrInternal> {
+ fn internal_channel_reestablish(&self, counterparty_node_id: &PublicKey, msg: &msgs::ChannelReestablish) -> Result<NotifyOption, MsgHandleErrInternal> {
let htlc_forwards;
let need_lnd_workaround = {
let per_peer_state = self.per_peer_state.read().unwrap();
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) {
- hash_map::Entry::Occupied(mut chan) => {
- // Currently, we expect all holding cell update_adds to be dropped on peer
- // disconnect, so Channel's reestablish will never hand us any holding cell
- // 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_entry!(self, chan.get_mut().channel_reestablish(
- msg, &self.logger, &self.node_signer, self.genesis_hash,
- &self.default_configuration, &*self.best_block.read().unwrap()), chan);
- let mut channel_update = None;
- if let Some(msg) = responses.shutdown_msg {
- peer_state.pending_msg_events.push(events::MessageSendEvent::SendShutdown {
- node_id: counterparty_node_id.clone(),
- msg,
- });
- } else if chan.get().context.is_usable() {
- // If the channel is in a usable state (ie the channel is not being shut
- // down), send a unicast channel_update to our counterparty to make sure
- // they have the latest channel parameters.
- if let Ok(msg) = self.get_channel_update_for_unicast(chan.get()) {
- channel_update = Some(events::MessageSendEvent::SendChannelUpdate {
- node_id: chan.get().context.get_counterparty_node_id(),
+ hash_map::Entry::Occupied(mut chan_phase_entry) => {
+ if let ChannelPhase::Funded(chan) = chan_phase_entry.get_mut() {
+ // Currently, we expect all holding cell update_adds to be dropped on peer
+ // disconnect, so Channel's reestablish will never hand us any holding cell
+ // 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,
+ &self.default_configuration, &*self.best_block.read().unwrap()), chan_phase_entry);
+ let mut channel_update = None;
+ if let Some(msg) = responses.shutdown_msg {
+ peer_state.pending_msg_events.push(events::MessageSendEvent::SendShutdown {
+ node_id: counterparty_node_id.clone(),
msg,
});
+ } else if chan.context.is_usable() {
+ // If the channel is in a usable state (ie the channel is not being shut
+ // down), send a unicast channel_update to our counterparty to make sure
+ // they have the latest channel parameters.
+ if let Ok(msg) = self.get_channel_update_for_unicast(chan) {
+ channel_update = Some(events::MessageSendEvent::SendChannelUpdate {
+ node_id: chan.context.get_counterparty_node_id(),
+ msg,
+ });
+ }
}
+ let need_lnd_workaround = chan.context.workaround_lnd_bug_4006.take();
+ htlc_forwards = self.handle_channel_resumption(
+ &mut peer_state.pending_msg_events, chan, responses.raa, responses.commitment_update, responses.order,
+ Vec::new(), None, responses.channel_ready, responses.announcement_sigs);
+ if let Some(upd) = channel_update {
+ peer_state.pending_msg_events.push(upd);
+ }
+ need_lnd_workaround
+ } else {
+ return try_chan_phase_entry!(self, Err(ChannelError::Close(
+ "Got a channel_reestablish message for an unfunded channel!".into())), chan_phase_entry);
}
- let need_lnd_workaround = chan.get_mut().context.workaround_lnd_bug_4006.take();
- htlc_forwards = self.handle_channel_resumption(
- &mut peer_state.pending_msg_events, chan.get_mut(), responses.raa, responses.commitment_update, responses.order,
- Vec::new(), None, responses.channel_ready, responses.announcement_sigs);
- if let Some(upd) = channel_update {
- peer_state.pending_msg_events.push(upd);
- }
- need_lnd_workaround
},
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))
}
};
+ let mut persist = NotifyOption::SkipPersistHandleEvents;
if let Some(forwards) = htlc_forwards {
self.forward_htlcs(&mut [forwards][..]);
+ persist = NotifyOption::DoPersist;
}
if let Some(channel_ready_msg) = need_lnd_workaround {
self.internal_channel_ready(counterparty_node_id, &channel_ready_msg)?;
}
- Ok(())
+ Ok(persist)
}
/// Process pending events from the [`chain::Watch`], returning whether any events were processed.
match monitor_event {
MonitorEvent::HTLCEvent(htlc_update) => {
if let Some(preimage) = htlc_update.payment_preimage {
- log_trace!(self.logger, "Claiming HTLC with preimage {} from our monitor", log_bytes!(preimage.0));
- self.claim_funds_internal(htlc_update.source, preimage, htlc_update.htlc_value_satoshis.map(|v| v * 1000), true, funding_outpoint.to_channel_id());
+ 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);
} else {
- log_trace!(self.logger, "Failing HTLC with hash {} from our monitor", log_bytes!(htlc_update.payment_hash.0));
+ 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() };
let reason = HTLCFailReason::from_failure_code(0x4000 | 8);
self.fail_htlc_backwards_internal(&htlc_update.source, &htlc_update.payment_hash, &reason, receiver);
}
},
- MonitorEvent::CommitmentTxConfirmed(funding_outpoint) |
- MonitorEvent::UpdateFailed(funding_outpoint) => {
+ MonitorEvent::HolderForceClosed(funding_outpoint) => {
let counterparty_node_id_opt = match counterparty_node_id {
Some(cp_id) => Some(cp_id),
None => {
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
let peer_state = &mut *peer_state_lock;
let pending_msg_events = &mut peer_state.pending_msg_events;
- if let hash_map::Entry::Occupied(chan_entry) = peer_state.channel_by_id.entry(funding_outpoint.to_channel_id()) {
- let mut chan = remove_channel!(self, chan_entry);
- failed_channels.push(chan.context.force_shutdown(false));
- if let Ok(update) = self.get_channel_update_for_broadcast(&chan) {
- pending_msg_events.push(events::MessageSendEvent::BroadcastChannelUpdate {
- msg: update
+ if let hash_map::Entry::Occupied(chan_phase_entry) = peer_state.channel_by_id.entry(funding_outpoint.to_channel_id()) {
+ if let ChannelPhase::Funded(mut chan) = remove_channel_phase!(self, chan_phase_entry) {
+ failed_channels.push(chan.context.force_shutdown(false));
+ if let Ok(update) = self.get_channel_update_for_broadcast(&chan) {
+ pending_msg_events.push(events::MessageSendEvent::BroadcastChannelUpdate {
+ msg: update
+ });
+ }
+ 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() }
+ },
});
}
- let reason = if let MonitorEvent::UpdateFailed(_) = monitor_event {
- ClosureReason::ProcessingError { err: "Failed to persist ChannelMonitor update during chain sync".to_string() }
- } else {
- ClosureReason::CommitmentTxConfirmed
- };
- self.issue_channel_close_events(&chan.context, reason);
- 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() }
- },
- });
}
}
}
}
for failure in failed_channels.drain(..) {
- self.finish_force_close_channel(failure);
+ self.finish_close_channel(failure);
}
has_pending_monitor_events
fn check_free_holding_cells(&self) -> bool {
let mut has_monitor_update = false;
let mut failed_htlcs = Vec::new();
- let mut handle_errors = Vec::new();
// Walk our list of channels and find any that need to update. Note that when we do find an
// update, if it includes actions that must be taken afterwards, we have to drop the
'chan_loop: loop {
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
let peer_state: &mut PeerState<_> = &mut *peer_state_lock;
- for (channel_id, chan) in peer_state.channel_by_id.iter_mut() {
+ for (channel_id, chan) in peer_state.channel_by_id.iter_mut().filter_map(
+ |(chan_id, phase)| if let ChannelPhase::Funded(chan) = phase { Some((chan_id, chan)) } else { None }
+ ) {
let counterparty_node_id = chan.context.get_counterparty_node_id();
let funding_txo = chan.context.get_funding_txo();
let (monitor_opt, holding_cell_failed_htlcs) =
- chan.maybe_free_holding_cell_htlcs(&self.logger);
+ chan.maybe_free_holding_cell_htlcs(&self.fee_estimator, &self.logger);
if !holding_cell_failed_htlcs.is_empty() {
failed_htlcs.push((holding_cell_failed_htlcs, *channel_id, counterparty_node_id));
}
if let Some(monitor_update) = monitor_opt {
has_monitor_update = true;
- let channel_id: [u8; 32] = *channel_id;
- let res = handle_new_monitor_update!(self, funding_txo.unwrap(), monitor_update,
- peer_state_lock, peer_state, per_peer_state, chan, MANUALLY_REMOVING,
- peer_state.channel_by_id.remove(&channel_id));
- if res.is_err() {
- handle_errors.push((counterparty_node_id, res));
- }
+ handle_new_monitor_update!(self, funding_txo.unwrap(), monitor_update,
+ peer_state_lock, peer_state, per_peer_state, chan);
continue 'peer_loop;
}
}
break 'peer_loop;
}
- let has_update = has_monitor_update || !failed_htlcs.is_empty() || !handle_errors.is_empty();
+ let has_update = has_monitor_update || !failed_htlcs.is_empty();
for (failures, channel_id, counterparty_node_id) in failed_htlcs.drain(..) {
self.fail_holding_cell_htlcs(failures, channel_id, &counterparty_node_id);
}
- for (counterparty_node_id, err) in handle_errors.drain(..) {
- let _ = handle_error!(self, err, counterparty_node_id);
- }
-
has_update
}
fn maybe_generate_initial_closing_signed(&self) -> bool {
let mut handle_errors: Vec<(PublicKey, Result<(), _>)> = Vec::new();
let mut has_update = false;
+ let mut shutdown_result = None;
+ let mut unbroadcasted_batch_funding_txid = None;
{
let per_peer_state = self.per_peer_state.read().unwrap();
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
let peer_state = &mut *peer_state_lock;
let pending_msg_events = &mut peer_state.pending_msg_events;
- peer_state.channel_by_id.retain(|channel_id, chan| {
- match chan.maybe_propose_closing_signed(&self.fee_estimator, &self.logger) {
- Ok((msg_opt, tx_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,
- });
- }
- 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.
- if let Ok(update) = self.get_channel_update_for_broadcast(&chan) {
- pending_msg_events.push(events::MessageSendEvent::BroadcastChannelUpdate {
- msg: update
- });
- }
+ peer_state.channel_by_id.retain(|channel_id, phase| {
+ match phase {
+ ChannelPhase::Funded(chan) => {
+ 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)) => {
+ 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,
+ });
+ }
+ 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.
+ if let Ok(update) = self.get_channel_update_for_broadcast(&chan) {
+ pending_msg_events.push(events::MessageSendEvent::BroadcastChannelUpdate {
+ msg: update
+ });
+ }
- self.issue_channel_close_events(&chan.context, ClosureReason::CooperativeClosure);
+ self.issue_channel_close_events(&chan.context, ClosureReason::CooperativeClosure);
- log_info!(self.logger, "Broadcasting {}", log_tx!(tx));
- self.tx_broadcaster.broadcast_transactions(&[&tx]);
- update_maps_on_chan_removal!(self, &chan.context);
- false
- } else { true }
+ log_info!(self.logger, "Broadcasting {}", log_tx!(tx));
+ self.tx_broadcaster.broadcast_transactions(&[&tx]);
+ update_maps_on_chan_removal!(self, &chan.context);
+ shutdown_result = Some((None, Vec::new(), unbroadcasted_batch_funding_txid));
+ false
+ } else { true }
+ },
+ Err(e) => {
+ has_update = true;
+ let (close_channel, res) = convert_chan_phase_err!(self, e, chan, channel_id, FUNDED_CHANNEL);
+ handle_errors.push((chan.context.get_counterparty_node_id(), Err(res)));
+ !close_channel
+ }
+ }
},
- Err(e) => {
- has_update = true;
- let (close_channel, res) = convert_chan_err!(self, e, chan, channel_id);
- handle_errors.push((chan.context.get_counterparty_node_id(), Err(res)));
- !close_channel
- }
+ _ => true, // Retain unfunded channels if present.
}
});
}
let _ = handle_error!(self, err, counterparty_node_id);
}
+ if let Some(shutdown_result) = shutdown_result {
+ self.finish_close_channel(shutdown_result);
+ }
+
has_update
}
counterparty_node_id, funding_txo, update
});
}
- self.finish_force_close_channel(failure);
+ self.finish_close_channel(failure);
}
}
for (_cp_id, peer_state_mutex) in per_peer_state.iter() {
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
let peer_state = &mut *peer_state_lock;
- for chan in peer_state.channel_by_id.values() {
+ for chan in peer_state.channel_by_id.values().filter_map(
+ |phase| if let ChannelPhase::Funded(chan) = phase { Some(chan) } else { None }
+ ) {
for (htlc_source, _) in chan.inflight_htlc_sources() {
if let HTLCSource::OutboundRoute { path, .. } = htlc_source {
inflight_htlcs.process_path(path, self.get_our_node_id());
/// When something which was blocking a channel from updating its [`ChannelMonitor`] (e.g. an
/// [`Event`] being handled) completes, this should be called to restore the channel to normal
/// operation. It will double-check that nothing *else* is also blocking the same channel from
- /// making progress and then any blocked [`ChannelMonitorUpdate`]s fly.
+ /// making progress and then let any blocked [`ChannelMonitorUpdate`]s fly.
fn handle_monitor_update_release(&self, counterparty_node_id: PublicKey, channel_funding_outpoint: OutPoint, mut completed_blocker: Option<RAAMonitorUpdateBlockingAction>) {
- let mut errors = Vec::new();
loop {
let per_peer_state = self.per_peer_state.read().unwrap();
if let Some(peer_state_mtx) = per_peer_state.get(&counterparty_node_id) {
// blocking monitor updates for this channel. If we do, release the monitor
// update(s) when those blockers complete.
log_trace!(self.logger, "Delaying monitor unlock for channel {} as another channel's mon update needs to complete first",
- log_bytes!(&channel_funding_outpoint.to_channel_id()[..]));
+ &channel_funding_outpoint.to_channel_id());
break;
}
- if let hash_map::Entry::Occupied(mut chan) = peer_state.channel_by_id.entry(channel_funding_outpoint.to_channel_id()) {
- debug_assert_eq!(chan.get().context.get_funding_txo().unwrap(), channel_funding_outpoint);
- if let Some((monitor_update, further_update_exists)) = chan.get_mut().unblock_next_blocked_monitor_update() {
- log_debug!(self.logger, "Unlocking monitor updating for channel {} and updating monitor",
- log_bytes!(&channel_funding_outpoint.to_channel_id()[..]));
- if let Err(e) = handle_new_monitor_update!(self, channel_funding_outpoint, monitor_update,
- peer_state_lck, peer_state, per_peer_state, chan)
- {
- errors.push((e, counterparty_node_id));
- }
- if further_update_exists {
- // If there are more `ChannelMonitorUpdate`s to process, restart at the
- // top of the loop.
- continue;
+ if let hash_map::Entry::Occupied(mut chan_phase_entry) = peer_state.channel_by_id.entry(channel_funding_outpoint.to_channel_id()) {
+ if let ChannelPhase::Funded(chan) = chan_phase_entry.get_mut() {
+ debug_assert_eq!(chan.context.get_funding_txo().unwrap(), channel_funding_outpoint);
+ if let Some((monitor_update, further_update_exists)) = chan.unblock_next_blocked_monitor_update() {
+ log_debug!(self.logger, "Unlocking monitor updating for channel {} and updating monitor",
+ channel_funding_outpoint.to_channel_id());
+ handle_new_monitor_update!(self, channel_funding_outpoint, monitor_update,
+ peer_state_lck, peer_state, per_peer_state, chan);
+ if further_update_exists {
+ // If there are more `ChannelMonitorUpdate`s to process, restart at the
+ // top of the loop.
+ continue;
+ }
+ } else {
+ log_trace!(self.logger, "Unlocked monitor updating for channel {} without monitors to update",
+ channel_funding_outpoint.to_channel_id());
}
- } else {
- log_trace!(self.logger, "Unlocked monitor updating for channel {} without monitors to update",
- log_bytes!(&channel_funding_outpoint.to_channel_id()[..]));
}
}
} else {
}
break;
}
- for (err, counterparty_node_id) in errors {
- let res = Err::<(), _>(err);
- let _ = handle_error!(self, res, counterparty_node_id);
- }
}
fn handle_post_event_actions(&self, actions: Vec<EventCompletionAction>) {
/// the `MessageSendEvent`s to the specific peer they were generated under.
fn get_and_clear_pending_msg_events(&self) -> Vec<MessageSendEvent> {
let events = RefCell::new(Vec::new());
- PersistenceNotifierGuard::optionally_notify(&self.total_consistency_lock, &self.persistence_notifier, || {
- let mut result = self.process_background_events();
+ PersistenceNotifierGuard::optionally_notify(self, || {
+ let mut result = NotifyOption::SkipPersistNoEvents;
// TODO: This behavior should be documented. It's unintuitive that we query
// ChannelMonitors when clearing other events.
}
fn block_disconnected(&self, header: &BlockHeader, height: u32) {
- let _persistence_guard = PersistenceNotifierGuard::optionally_notify(&self.total_consistency_lock,
- &self.persistence_notifier, || -> NotifyOption { NotifyOption::DoPersist });
+ let _persistence_guard =
+ PersistenceNotifierGuard::optionally_notify_skipping_background_events(
+ self, || -> NotifyOption { NotifyOption::DoPersist });
let new_height = height - 1;
{
let mut best_block = self.best_block.write().unwrap();
let block_hash = header.block_hash();
log_trace!(self.logger, "{} transactions included in block {} at height {} provided", txdata.len(), block_hash, height);
- let _persistence_guard = PersistenceNotifierGuard::optionally_notify(&self.total_consistency_lock,
- &self.persistence_notifier, || -> NotifyOption { NotifyOption::DoPersist });
+ 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)
.map(|(a, b)| (a, Vec::new(), b)));
let block_hash = header.block_hash();
log_trace!(self.logger, "New best block: {} at height {}", block_hash, height);
- let _persistence_guard = PersistenceNotifierGuard::optionally_notify(&self.total_consistency_lock,
- &self.persistence_notifier, || -> NotifyOption { NotifyOption::DoPersist });
+ let _persistence_guard =
+ PersistenceNotifierGuard::optionally_notify_skipping_background_events(
+ 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));
for (_cp_id, peer_state_mutex) in self.per_peer_state.read().unwrap().iter() {
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
let peer_state = &mut *peer_state_lock;
- for chan in peer_state.channel_by_id.values() {
+ for chan in peer_state.channel_by_id.values().filter_map(|phase| if let ChannelPhase::Funded(chan) = phase { Some(chan) } else { None }) {
if let (Some(funding_txo), Some(block_hash)) = (chan.context.get_funding_txo(), chan.context.get_funding_tx_confirmed_in()) {
res.push((funding_txo.txid, Some(block_hash)));
}
}
fn transaction_unconfirmed(&self, txid: &Txid) {
- let _persistence_guard = PersistenceNotifierGuard::optionally_notify(&self.total_consistency_lock,
- &self.persistence_notifier, || -> NotifyOption { NotifyOption::DoPersist });
+ let _persistence_guard =
+ PersistenceNotifierGuard::optionally_notify_skipping_background_events(
+ self, || -> NotifyOption { NotifyOption::DoPersist });
self.do_chain_event(None, |channel| {
if let Some(funding_txo) = channel.context.get_funding_txo() {
if funding_txo.txid == *txid {
/// Calls a function which handles an on-chain event (blocks dis/connected, transactions
/// un/confirmed, etc) on each channel, handling any resulting errors or messages generated by
/// the function.
- fn do_chain_event<FN: Fn(&mut Channel<<SP::Target as SignerProvider>::Signer>) -> Result<(Option<msgs::ChannelReady>, Vec<(HTLCSource, PaymentHash)>, Option<msgs::AnnouncementSignatures>), ClosureReason>>
+ fn do_chain_event<FN: Fn(&mut Channel<SP>) -> Result<(Option<msgs::ChannelReady>, Vec<(HTLCSource, PaymentHash)>, Option<msgs::AnnouncementSignatures>), ClosureReason>>
(&self, height_opt: Option<u32>, f: FN) {
// Note that we MUST NOT end up calling methods on self.chain_monitor here - we're called
// during initialization prior to the chain_monitor being fully configured in some cases.
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
let peer_state = &mut *peer_state_lock;
let pending_msg_events = &mut peer_state.pending_msg_events;
- peer_state.channel_by_id.retain(|_, channel| {
- let res = f(channel);
- if let Ok((channel_ready_opt, mut timed_out_pending_htlcs, announcement_sigs)) = res {
- for (source, payment_hash) in timed_out_pending_htlcs.drain(..) {
- let (failure_code, data) = self.get_htlc_inbound_temp_fail_err_and_data(0x1000|14 /* expiry_too_soon */, &channel);
- timed_out_htlcs.push((source, payment_hash, HTLCFailReason::reason(failure_code, data),
- HTLCDestination::NextHopChannel { node_id: Some(channel.context.get_counterparty_node_id()), channel_id: channel.context.channel_id() }));
- }
- if let Some(channel_ready) = channel_ready_opt {
- send_channel_ready!(self, pending_msg_events, channel, channel_ready);
- if channel.context.is_usable() {
- log_trace!(self.logger, "Sending channel_ready with private initial channel_update for our counterparty on channel {}", log_bytes!(channel.context.channel_id()));
- if let Ok(msg) = self.get_channel_update_for_unicast(channel) {
- pending_msg_events.push(events::MessageSendEvent::SendChannelUpdate {
- node_id: channel.context.get_counterparty_node_id(),
- msg,
- });
+ peer_state.channel_by_id.retain(|_, phase| {
+ match phase {
+ // Retain unfunded channels.
+ ChannelPhase::UnfundedOutboundV1(_) | ChannelPhase::UnfundedInboundV1(_) => true,
+ ChannelPhase::Funded(channel) => {
+ let res = f(channel);
+ if let Ok((channel_ready_opt, mut timed_out_pending_htlcs, announcement_sigs)) = res {
+ for (source, payment_hash) in timed_out_pending_htlcs.drain(..) {
+ let (failure_code, data) = self.get_htlc_inbound_temp_fail_err_and_data(0x1000|14 /* expiry_too_soon */, &channel);
+ timed_out_htlcs.push((source, payment_hash, HTLCFailReason::reason(failure_code, data),
+ HTLCDestination::NextHopChannel { node_id: Some(channel.context.get_counterparty_node_id()), channel_id: channel.context.channel_id() }));
+ }
+ if let Some(channel_ready) = channel_ready_opt {
+ send_channel_ready!(self, pending_msg_events, channel, channel_ready);
+ if channel.context.is_usable() {
+ log_trace!(self.logger, "Sending channel_ready with private initial channel_update for our counterparty on channel {}", channel.context.channel_id());
+ if let Ok(msg) = self.get_channel_update_for_unicast(channel) {
+ pending_msg_events.push(events::MessageSendEvent::SendChannelUpdate {
+ node_id: channel.context.get_counterparty_node_id(),
+ msg,
+ });
+ }
+ } else {
+ log_trace!(self.logger, "Sending channel_ready WITHOUT channel_update for {}", channel.context.channel_id());
+ }
}
- } else {
- log_trace!(self.logger, "Sending channel_ready WITHOUT channel_update for {}", log_bytes!(channel.context.channel_id()));
- }
- }
- {
- let mut pending_events = self.pending_events.lock().unwrap();
- emit_channel_ready_event!(pending_events, channel);
- }
+ {
+ let mut pending_events = self.pending_events.lock().unwrap();
+ emit_channel_ready_event!(pending_events, channel);
+ }
- if let Some(announcement_sigs) = announcement_sigs {
- log_trace!(self.logger, "Sending announcement_signatures for channel {}", log_bytes!(channel.context.channel_id()));
- pending_msg_events.push(events::MessageSendEvent::SendAnnouncementSignatures {
- node_id: channel.context.get_counterparty_node_id(),
- 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) {
- pending_msg_events.push(events::MessageSendEvent::BroadcastChannelAnnouncement {
- msg: announcement,
- // Note that announcement_signatures fails if the channel cannot be announced,
- // so get_channel_update_for_broadcast will never fail by the time we get here.
- update_msg: Some(self.get_channel_update_for_broadcast(channel).unwrap()),
+ if let Some(announcement_sigs) = announcement_sigs {
+ log_trace!(self.logger, "Sending announcement_signatures for channel {}", channel.context.channel_id());
+ pending_msg_events.push(events::MessageSendEvent::SendAnnouncementSignatures {
+ node_id: channel.context.get_counterparty_node_id(),
+ 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) {
+ pending_msg_events.push(events::MessageSendEvent::BroadcastChannelAnnouncement {
+ msg: announcement,
+ // Note that announcement_signatures fails if the channel cannot be announced,
+ // so get_channel_update_for_broadcast will never fail by the time we get here.
+ update_msg: Some(self.get_channel_update_for_broadcast(channel).unwrap()),
+ });
+ }
+ }
}
+ if channel.is_our_channel_ready() {
+ if let Some(real_scid) = channel.context.get_short_channel_id() {
+ // If we sent a 0conf channel_ready, and now have an SCID, we add it
+ // to the short_to_chan_info map here. Note that we check whether we
+ // can relay using the real SCID at relay-time (i.e.
+ // enforce option_scid_alias then), and if the funding tx is ever
+ // un-confirmed we force-close the channel, ensuring short_to_chan_info
+ // is always consistent.
+ let mut short_to_chan_info = self.short_to_chan_info.write().unwrap();
+ let scid_insert = short_to_chan_info.insert(real_scid, (channel.context.get_counterparty_node_id(), channel.context.channel_id()));
+ assert!(scid_insert.is_none() || scid_insert.unwrap() == (channel.context.get_counterparty_node_id(), channel.context.channel_id()),
+ "SCIDs should never collide - ensure you weren't behind by a full {} blocks when creating channels",
+ fake_scid::MAX_SCID_BLOCKS_FROM_NOW);
+ }
+ }
+ } else if let Err(reason) = res {
+ update_maps_on_chan_removal!(self, &channel.context);
+ // It looks like our counterparty went on-chain or funding transaction was
+ // reorged out of the main chain. Close the channel.
+ failed_channels.push(channel.context.force_shutdown(true));
+ if let Ok(update) = self.get_channel_update_for_broadcast(&channel) {
+ pending_msg_events.push(events::MessageSendEvent::BroadcastChannelUpdate {
+ msg: update
+ });
+ }
+ let reason_message = format!("{}", reason);
+ 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,
+ } },
+ });
+ return false;
}
+ true
}
- if channel.is_our_channel_ready() {
- if let Some(real_scid) = channel.context.get_short_channel_id() {
- // If we sent a 0conf channel_ready, and now have an SCID, we add it
- // to the short_to_chan_info map here. Note that we check whether we
- // can relay using the real SCID at relay-time (i.e.
- // enforce option_scid_alias then), and if the funding tx is ever
- // un-confirmed we force-close the channel, ensuring short_to_chan_info
- // is always consistent.
- let mut short_to_chan_info = self.short_to_chan_info.write().unwrap();
- let scid_insert = short_to_chan_info.insert(real_scid, (channel.context.get_counterparty_node_id(), channel.context.channel_id()));
- assert!(scid_insert.is_none() || scid_insert.unwrap() == (channel.context.get_counterparty_node_id(), channel.context.channel_id()),
- "SCIDs should never collide - ensure you weren't behind by a full {} blocks when creating channels",
- fake_scid::MAX_SCID_BLOCKS_FROM_NOW);
- }
- }
- } else if let Err(reason) = res {
- update_maps_on_chan_removal!(self, &channel.context);
- // It looks like our counterparty went on-chain or funding transaction was
- // reorged out of the main chain. Close the channel.
- failed_channels.push(channel.context.force_shutdown(true));
- if let Ok(update) = self.get_channel_update_for_broadcast(&channel) {
- pending_msg_events.push(events::MessageSendEvent::BroadcastChannelUpdate {
- msg: update
- });
- }
- let reason_message = format!("{}", reason);
- 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,
- } },
- });
- return false;
}
- true
});
}
}
if height >= htlc.forward_info.outgoing_cltv_value - HTLC_FAIL_BACK_BUFFER {
let prev_hop_data = HTLCSource::PreviousHopData(HTLCPreviousHopData {
short_channel_id: htlc.prev_short_channel_id,
+ user_channel_id: Some(htlc.prev_user_channel_id),
htlc_id: htlc.prev_htlc_id,
incoming_packet_shared_secret: htlc.forward_info.incoming_shared_secret,
phantom_shared_secret: None,
}
}
- /// Gets a [`Future`] that completes when this [`ChannelManager`] needs to be persisted.
+ /// Gets a [`Future`] that completes when this [`ChannelManager`] may need to be persisted or
+ /// may have events that need processing.
+ ///
+ /// In order to check if this [`ChannelManager`] needs persisting, call
+ /// [`Self::get_and_clear_needs_persistence`].
///
/// Note that callbacks registered on the [`Future`] MUST NOT call back into this
/// [`ChannelManager`] and should instead register actions to be taken later.
- ///
- pub fn get_persistable_update_future(&self) -> Future {
- self.persistence_notifier.get_future()
+ pub fn get_event_or_persistence_needed_future(&self) -> Future {
+ self.event_persist_notifier.get_future()
+ }
+
+ /// Returns true if this [`ChannelManager`] needs to be persisted.
+ pub fn get_and_clear_needs_persistence(&self) -> bool {
+ self.needs_persist_flag.swap(false, Ordering::AcqRel)
}
#[cfg(any(test, feature = "_test_utils"))]
- pub fn get_persistence_condvar_value(&self) -> bool {
- self.persistence_notifier.notify_pending()
+ pub fn get_event_or_persist_condvar_value(&self) -> bool {
+ self.event_persist_notifier.notify_pending()
}
/// Gets the latest best block which was connected either via the [`chain::Listen`] or
provided_node_features(&self.default_configuration)
}
- /// Fetches the set of [`InvoiceFeatures`] flags which are provided by or required by
+ /// Fetches the set of [`Bolt11InvoiceFeatures`] flags which 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) -> InvoiceFeatures {
+ pub fn invoice_features(&self) -> Bolt11InvoiceFeatures {
provided_invoice_features(&self.default_configuration)
}
L::Target: Logger,
{
fn handle_open_channel(&self, counterparty_node_id: &PublicKey, msg: &msgs::OpenChannel) {
- let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
- let _ = handle_error!(self, self.internal_open_channel(counterparty_node_id, msg), *counterparty_node_id);
+ // Note that we never need to persist the updated ChannelManager for an inbound
+ // open_channel message - pre-funded channels are never written so there should be no
+ // change to the contents.
+ let _persistence_guard = PersistenceNotifierGuard::optionally_notify(self, || {
+ let res = self.internal_open_channel(counterparty_node_id, msg);
+ let persist = match &res {
+ Err(e) if e.closes_channel() => {
+ debug_assert!(false, "We shouldn't close a new channel");
+ NotifyOption::DoPersist
+ },
+ _ => NotifyOption::SkipPersistHandleEvents,
+ };
+ let _ = handle_error!(self, res, *counterparty_node_id);
+ persist
+ });
}
fn handle_open_channel_v2(&self, counterparty_node_id: &PublicKey, msg: &msgs::OpenChannelV2) {
}
fn handle_accept_channel(&self, counterparty_node_id: &PublicKey, msg: &msgs::AcceptChannel) {
- let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
- let _ = handle_error!(self, self.internal_accept_channel(counterparty_node_id, msg), *counterparty_node_id);
+ // Note that we never need to persist the updated ChannelManager for an inbound
+ // accept_channel message - pre-funded channels are never written so there should be no
+ // change to the contents.
+ let _persistence_guard = PersistenceNotifierGuard::optionally_notify(self, || {
+ let _ = handle_error!(self, self.internal_accept_channel(counterparty_node_id, msg), *counterparty_node_id);
+ NotifyOption::SkipPersistHandleEvents
+ });
}
fn handle_accept_channel_v2(&self, counterparty_node_id: &PublicKey, msg: &msgs::AcceptChannelV2) {
}
fn handle_channel_ready(&self, counterparty_node_id: &PublicKey, msg: &msgs::ChannelReady) {
- let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
- let _ = handle_error!(self, self.internal_channel_ready(counterparty_node_id, msg), *counterparty_node_id);
+ // Note that we never need to persist the updated ChannelManager for an inbound
+ // channel_ready message - while the channel's state will change, any channel_ready message
+ // will ultimately be re-sent on startup and the `ChannelMonitor` won't be updated so we
+ // will not force-close the channel on startup.
+ let _persistence_guard = PersistenceNotifierGuard::optionally_notify(self, || {
+ let res = self.internal_channel_ready(counterparty_node_id, msg);
+ let persist = match &res {
+ Err(e) if e.closes_channel() => NotifyOption::DoPersist,
+ _ => NotifyOption::SkipPersistHandleEvents,
+ };
+ let _ = handle_error!(self, res, *counterparty_node_id);
+ persist
+ });
}
fn handle_shutdown(&self, counterparty_node_id: &PublicKey, msg: &msgs::Shutdown) {
}
fn handle_update_add_htlc(&self, counterparty_node_id: &PublicKey, msg: &msgs::UpdateAddHTLC) {
- let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
- let _ = handle_error!(self, self.internal_update_add_htlc(counterparty_node_id, msg), *counterparty_node_id);
+ // Note that we never need to persist the updated ChannelManager for an inbound
+ // update_add_htlc message - the message itself doesn't change our channel state only the
+ // `commitment_signed` message afterwards will.
+ let _persistence_guard = PersistenceNotifierGuard::optionally_notify(self, || {
+ let res = self.internal_update_add_htlc(counterparty_node_id, msg);
+ let persist = match &res {
+ Err(e) if e.closes_channel() => NotifyOption::DoPersist,
+ Err(_) => NotifyOption::SkipPersistHandleEvents,
+ Ok(()) => NotifyOption::SkipPersistNoEvents,
+ };
+ let _ = handle_error!(self, res, *counterparty_node_id);
+ persist
+ });
}
fn handle_update_fulfill_htlc(&self, counterparty_node_id: &PublicKey, msg: &msgs::UpdateFulfillHTLC) {
}
fn handle_update_fail_htlc(&self, counterparty_node_id: &PublicKey, msg: &msgs::UpdateFailHTLC) {
- let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
- let _ = handle_error!(self, self.internal_update_fail_htlc(counterparty_node_id, msg), *counterparty_node_id);
+ // Note that we never need to persist the updated ChannelManager for an inbound
+ // update_fail_htlc message - the message itself doesn't change our channel state only the
+ // `commitment_signed` message afterwards will.
+ let _persistence_guard = PersistenceNotifierGuard::optionally_notify(self, || {
+ let res = self.internal_update_fail_htlc(counterparty_node_id, msg);
+ let persist = match &res {
+ Err(e) if e.closes_channel() => NotifyOption::DoPersist,
+ Err(_) => NotifyOption::SkipPersistHandleEvents,
+ Ok(()) => NotifyOption::SkipPersistNoEvents,
+ };
+ let _ = handle_error!(self, res, *counterparty_node_id);
+ persist
+ });
}
fn handle_update_fail_malformed_htlc(&self, counterparty_node_id: &PublicKey, msg: &msgs::UpdateFailMalformedHTLC) {
- let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
- let _ = handle_error!(self, self.internal_update_fail_malformed_htlc(counterparty_node_id, msg), *counterparty_node_id);
+ // Note that we never need to persist the updated ChannelManager for an inbound
+ // update_fail_malformed_htlc message - the message itself doesn't change our channel state
+ // only the `commitment_signed` message afterwards will.
+ let _persistence_guard = PersistenceNotifierGuard::optionally_notify(self, || {
+ let res = self.internal_update_fail_malformed_htlc(counterparty_node_id, msg);
+ let persist = match &res {
+ Err(e) if e.closes_channel() => NotifyOption::DoPersist,
+ Err(_) => NotifyOption::SkipPersistHandleEvents,
+ Ok(()) => NotifyOption::SkipPersistNoEvents,
+ };
+ let _ = handle_error!(self, res, *counterparty_node_id);
+ persist
+ });
}
fn handle_commitment_signed(&self, counterparty_node_id: &PublicKey, msg: &msgs::CommitmentSigned) {
}
fn handle_update_fee(&self, counterparty_node_id: &PublicKey, msg: &msgs::UpdateFee) {
- let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
- let _ = handle_error!(self, self.internal_update_fee(counterparty_node_id, msg), *counterparty_node_id);
+ // Note that we never need to persist the updated ChannelManager for an inbound
+ // update_fee message - the message itself doesn't change our channel state only the
+ // `commitment_signed` message afterwards will.
+ let _persistence_guard = PersistenceNotifierGuard::optionally_notify(self, || {
+ let res = self.internal_update_fee(counterparty_node_id, msg);
+ let persist = match &res {
+ Err(e) if e.closes_channel() => NotifyOption::DoPersist,
+ Err(_) => NotifyOption::SkipPersistHandleEvents,
+ Ok(()) => NotifyOption::SkipPersistNoEvents,
+ };
+ let _ = handle_error!(self, res, *counterparty_node_id);
+ persist
+ });
}
fn handle_announcement_signatures(&self, counterparty_node_id: &PublicKey, msg: &msgs::AnnouncementSignatures) {
}
fn handle_channel_update(&self, counterparty_node_id: &PublicKey, msg: &msgs::ChannelUpdate) {
- PersistenceNotifierGuard::optionally_notify(&self.total_consistency_lock, &self.persistence_notifier, || {
- let force_persist = self.process_background_events();
+ PersistenceNotifierGuard::optionally_notify(self, || {
if let Ok(persist) = handle_error!(self, self.internal_channel_update(counterparty_node_id, msg), *counterparty_node_id) {
- if force_persist == NotifyOption::DoPersist { NotifyOption::DoPersist } else { persist }
+ persist
} else {
- NotifyOption::SkipPersist
+ NotifyOption::DoPersist
}
});
}
fn handle_channel_reestablish(&self, counterparty_node_id: &PublicKey, msg: &msgs::ChannelReestablish) {
- let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
- let _ = handle_error!(self, self.internal_channel_reestablish(counterparty_node_id, msg), *counterparty_node_id);
+ let _persistence_guard = PersistenceNotifierGuard::optionally_notify(self, || {
+ let res = self.internal_channel_reestablish(counterparty_node_id, msg);
+ let persist = match &res {
+ Err(e) if e.closes_channel() => NotifyOption::DoPersist,
+ Err(_) => NotifyOption::SkipPersistHandleEvents,
+ Ok(persist) => *persist,
+ };
+ let _ = handle_error!(self, res, *counterparty_node_id);
+ persist
+ });
}
fn peer_disconnected(&self, counterparty_node_id: &PublicKey) {
- let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
+ let _persistence_guard = PersistenceNotifierGuard::optionally_notify(
+ self, || NotifyOption::SkipPersistHandleEvents);
let mut failed_channels = Vec::new();
let mut per_peer_state = self.per_peer_state.write().unwrap();
let remove_peer = {
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
let peer_state = &mut *peer_state_lock;
let pending_msg_events = &mut peer_state.pending_msg_events;
- peer_state.channel_by_id.retain(|_, chan| {
- chan.remove_uncommitted_htlcs_and_mark_paused(&self.logger);
- if chan.is_shutdown() {
- update_maps_on_chan_removal!(self, &chan.context);
- self.issue_channel_close_events(&chan.context, ClosureReason::DisconnectedPeer);
- return false;
- }
- true
- });
- peer_state.inbound_v1_channel_by_id.retain(|_, chan| {
- update_maps_on_chan_removal!(self, &chan.context);
- self.issue_channel_close_events(&chan.context, ClosureReason::DisconnectedPeer);
- false
- });
- peer_state.outbound_v1_channel_by_id.retain(|_, chan| {
- update_maps_on_chan_removal!(self, &chan.context);
- self.issue_channel_close_events(&chan.context, ClosureReason::DisconnectedPeer);
+ peer_state.channel_by_id.retain(|_, phase| {
+ let context = match phase {
+ ChannelPhase::Funded(chan) => {
+ if chan.remove_uncommitted_htlcs_and_mark_paused(&self.logger).is_ok() {
+ // We only retain funded channels that are not shutdown.
+ return true;
+ }
+ &mut chan.context
+ },
+ // Unfunded channels will always be removed.
+ ChannelPhase::UnfundedOutboundV1(chan) => {
+ &mut chan.context
+ },
+ ChannelPhase::UnfundedInboundV1(chan) => {
+ &mut chan.context
+ },
+ };
+ // Clean up for removal.
+ update_maps_on_chan_removal!(self, &context);
+ self.issue_channel_close_events(&context, ClosureReason::DisconnectedPeer);
+ failed_channels.push(context.force_shutdown(false));
false
});
+ // Note that we don't bother generating any events for pre-accept channels -
+ // they're not considered "channels" yet from the PoV of our events interface.
+ peer_state.inbound_channel_request_by_id.clear();
pending_msg_events.retain(|msg| {
match msg {
// V1 Channel Establishment
mem::drop(per_peer_state);
for failure in failed_channels.drain(..) {
- self.finish_force_close_channel(failure);
+ self.finish_close_channel(failure);
}
}
return Err(());
}
- let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
+ let mut res = Ok(());
- // If we have too many peers connected which don't have funded channels, disconnect the
- // peer immediately (as long as it doesn't have funded channels). If we have a bunch of
- // unfunded channels taking up space in memory for disconnected peers, we still let new
- // peers connect, but we'll reject new channels from them.
- let connected_peers_without_funded_channels = self.peers_without_funded_channels(|node| node.is_connected);
- let inbound_peer_limited = inbound && connected_peers_without_funded_channels >= MAX_NO_CHANNEL_PEERS;
+ PersistenceNotifierGuard::optionally_notify(self, || {
+ // If we have too many peers connected which don't have funded channels, disconnect the
+ // peer immediately (as long as it doesn't have funded channels). If we have a bunch of
+ // unfunded channels taking up space in memory for disconnected peers, we still let new
+ // peers connect, but we'll reject new channels from them.
+ let connected_peers_without_funded_channels = self.peers_without_funded_channels(|node| node.is_connected);
+ let inbound_peer_limited = inbound && connected_peers_without_funded_channels >= MAX_NO_CHANNEL_PEERS;
- {
- let mut peer_state_lock = self.per_peer_state.write().unwrap();
- match peer_state_lock.entry(counterparty_node_id.clone()) {
- hash_map::Entry::Vacant(e) => {
- if inbound_peer_limited {
- return Err(());
- }
- e.insert(Mutex::new(PeerState {
- channel_by_id: HashMap::new(),
- outbound_v1_channel_by_id: HashMap::new(),
- inbound_v1_channel_by_id: HashMap::new(),
- latest_features: init_msg.features.clone(),
- pending_msg_events: Vec::new(),
- in_flight_monitor_updates: BTreeMap::new(),
- monitor_update_blocked_actions: BTreeMap::new(),
- actions_blocking_raa_monitor_updates: BTreeMap::new(),
- is_connected: true,
- }));
- },
- hash_map::Entry::Occupied(e) => {
- let mut peer_state = e.get().lock().unwrap();
- peer_state.latest_features = init_msg.features.clone();
-
- let best_block_height = self.best_block.read().unwrap().height();
- if inbound_peer_limited &&
- Self::unfunded_channel_count(&*peer_state, best_block_height) ==
- peer_state.channel_by_id.len()
- {
- return Err(());
- }
+ {
+ let mut peer_state_lock = self.per_peer_state.write().unwrap();
+ match peer_state_lock.entry(counterparty_node_id.clone()) {
+ hash_map::Entry::Vacant(e) => {
+ if inbound_peer_limited {
+ res = Err(());
+ return NotifyOption::SkipPersistNoEvents;
+ }
+ e.insert(Mutex::new(PeerState {
+ channel_by_id: HashMap::new(),
+ inbound_channel_request_by_id: HashMap::new(),
+ latest_features: init_msg.features.clone(),
+ pending_msg_events: Vec::new(),
+ in_flight_monitor_updates: BTreeMap::new(),
+ monitor_update_blocked_actions: BTreeMap::new(),
+ actions_blocking_raa_monitor_updates: BTreeMap::new(),
+ is_connected: true,
+ }));
+ },
+ hash_map::Entry::Occupied(e) => {
+ let mut peer_state = e.get().lock().unwrap();
+ peer_state.latest_features = init_msg.features.clone();
+
+ let best_block_height = self.best_block.read().unwrap().height();
+ if inbound_peer_limited &&
+ Self::unfunded_channel_count(&*peer_state, best_block_height) ==
+ peer_state.channel_by_id.len()
+ {
+ res = Err(());
+ return NotifyOption::SkipPersistNoEvents;
+ }
- debug_assert!(!peer_state.is_connected, "A peer shouldn't be connected twice");
- peer_state.is_connected = true;
- },
+ debug_assert!(!peer_state.is_connected, "A peer shouldn't be connected twice");
+ peer_state.is_connected = true;
+ },
+ }
}
- }
- log_debug!(self.logger, "Generating channel_reestablish events for {}", log_pubkey!(counterparty_node_id));
+ log_debug!(self.logger, "Generating channel_reestablish events for {}", log_pubkey!(counterparty_node_id));
- let per_peer_state = self.per_peer_state.read().unwrap();
- for (_cp_id, peer_state_mutex) in per_peer_state.iter() {
- let mut peer_state_lock = peer_state_mutex.lock().unwrap();
- let peer_state = &mut *peer_state_lock;
- let pending_msg_events = &mut peer_state.pending_msg_events;
- peer_state.channel_by_id.retain(|_, chan| {
- let retain = if chan.context.get_counterparty_node_id() == *counterparty_node_id {
- if !chan.context.have_received_message() {
- // If we created this (outbound) channel while we were disconnected from the
- // peer we probably failed to send the open_channel message, which is now
- // lost. We can't have had anything pending related to this channel, so we just
- // drop it.
- false
- } else {
- pending_msg_events.push(events::MessageSendEvent::SendChannelReestablish {
- node_id: chan.context.get_counterparty_node_id(),
- msg: chan.get_channel_reestablish(&self.logger),
- });
- true
+ let per_peer_state = self.per_peer_state.read().unwrap();
+ if let Some(peer_state_mutex) = per_peer_state.get(counterparty_node_id) {
+ let mut peer_state_lock = peer_state_mutex.lock().unwrap();
+ let peer_state = &mut *peer_state_lock;
+ let pending_msg_events = &mut peer_state.pending_msg_events;
+
+ peer_state.channel_by_id.iter_mut().filter_map(|(_, phase)|
+ if let ChannelPhase::Funded(chan) = phase { Some(chan) } else {
+ // Since unfunded channel maps are cleared upon disconnecting a peer, and they're not persisted
+ // (so won't be recovered after a crash), they shouldn't exist here and we would never need to
+ // worry about closing and removing them.
+ debug_assert!(false);
+ None
}
- } else { true };
- if retain && chan.context.get_counterparty_node_id() != *counterparty_node_id {
- if let Some(msg) = chan.get_signed_channel_announcement(&self.node_signer, self.genesis_hash.clone(), self.best_block.read().unwrap().height(), &self.default_configuration) {
- if let Ok(update_msg) = self.get_channel_update_for_broadcast(chan) {
- pending_msg_events.push(events::MessageSendEvent::SendChannelAnnouncement {
+ ).for_each(|chan| {
+ pending_msg_events.push(events::MessageSendEvent::SendChannelReestablish {
+ node_id: chan.context.get_counterparty_node_id(),
+ msg: chan.get_channel_reestablish(&self.logger),
+ });
+ });
+ }
+
+ return NotifyOption::SkipPersistHandleEvents;
+ //TODO: Also re-broadcast announcement_signatures
+ });
+ res
+ }
+
+ fn handle_error(&self, counterparty_node_id: &PublicKey, msg: &msgs::ErrorMessage) {
+ let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
+
+ match &msg.data as &str {
+ "cannot co-op close channel w/ active htlcs"|
+ "link failed to shutdown" =>
+ {
+ // LND hasn't properly handled shutdown messages ever, and force-closes any time we
+ // send one while HTLCs are still present. The issue is tracked at
+ // https://github.com/lightningnetwork/lnd/issues/6039 and has had multiple patches
+ // to fix it but none so far have managed to land upstream. The issue appears to be
+ // very low priority for the LND team despite being marked "P1".
+ // We're not going to bother handling this in a sensible way, instead simply
+ // repeating the Shutdown message on repeat until morale improves.
+ if !msg.channel_id.is_zero() {
+ let per_peer_state = self.per_peer_state.read().unwrap();
+ let peer_state_mutex_opt = per_peer_state.get(counterparty_node_id);
+ if peer_state_mutex_opt.is_none() { return; }
+ let mut peer_state = peer_state_mutex_opt.unwrap().lock().unwrap();
+ if let Some(ChannelPhase::Funded(chan)) = peer_state.channel_by_id.get(&msg.channel_id) {
+ if let Some(msg) = chan.get_outbound_shutdown() {
+ peer_state.pending_msg_events.push(events::MessageSendEvent::SendShutdown {
node_id: *counterparty_node_id,
- msg, update_msg,
+ msg,
});
}
+ peer_state.pending_msg_events.push(events::MessageSendEvent::HandleError {
+ node_id: *counterparty_node_id,
+ action: msgs::ErrorAction::SendWarningMessage {
+ msg: msgs::WarningMessage {
+ channel_id: msg.channel_id,
+ data: "You appear to be exhibiting LND bug 6039, we'll keep sending you shutdown messages until you handle them correctly".to_owned()
+ },
+ log_level: Level::Trace,
+ }
+ });
}
}
- retain
- });
+ return;
+ }
+ _ => {}
}
- //TODO: Also re-broadcast announcement_signatures
- Ok(())
- }
-
- fn handle_error(&self, counterparty_node_id: &PublicKey, msg: &msgs::ErrorMessage) {
- let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
- if msg.channel_id == [0; 32] {
- let channel_ids: Vec<[u8; 32]> = {
+ if msg.channel_id.is_zero() {
+ let channel_ids: Vec<ChannelId> = {
let per_peer_state = self.per_peer_state.read().unwrap();
let peer_state_mutex_opt = per_peer_state.get(counterparty_node_id);
if peer_state_mutex_opt.is_none() { return; }
let mut peer_state_lock = peer_state_mutex_opt.unwrap().lock().unwrap();
let peer_state = &mut *peer_state_lock;
- peer_state.channel_by_id.keys().cloned()
- .chain(peer_state.outbound_v1_channel_by_id.keys().cloned())
- .chain(peer_state.inbound_v1_channel_by_id.keys().cloned()).collect()
+ // Note that we don't bother generating any events for pre-accept channels -
+ // they're not considered "channels" yet from the PoV of our events interface.
+ peer_state.inbound_channel_request_by_id.clear();
+ peer_state.channel_by_id.keys().cloned().collect()
};
for channel_id in channel_ids {
// Untrusted messages from peer, we throw away the error if id points to a non-existent channel
if peer_state_mutex_opt.is_none() { return; }
let mut peer_state_lock = peer_state_mutex_opt.unwrap().lock().unwrap();
let peer_state = &mut *peer_state_lock;
- if let Some(chan) = peer_state.outbound_v1_channel_by_id.get_mut(&msg.channel_id) {
- if let Ok(msg) = chan.maybe_handle_error_without_close(self.genesis_hash) {
+ 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) {
peer_state.pending_msg_events.push(events::MessageSendEvent::SendOpenChannel {
node_id: *counterparty_node_id,
msg,
/// Fetches the set of [`NodeFeatures`] flags which are provided by or required by
/// [`ChannelManager`].
pub(crate) fn provided_node_features(config: &UserConfig) -> NodeFeatures {
- provided_init_features(config).to_context()
+ let mut node_features = provided_init_features(config).to_context();
+ node_features.set_keysend_optional();
+ node_features
}
-/// Fetches the set of [`InvoiceFeatures`] flags which are provided by or required by
+/// Fetches the set of [`Bolt11InvoiceFeatures`] flags which 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) -> InvoiceFeatures {
+pub(crate) fn provided_invoice_features(config: &UserConfig) -> Bolt11InvoiceFeatures {
provided_init_features(config).to_context()
}
(10, self.channel_value_satoshis, required),
(12, self.unspendable_punishment_reserve, option),
(14, user_channel_id_low, required),
- (16, self.balance_msat, required),
+ (16, self.next_outbound_htlc_limit_msat, required), // Forwards compatibility for removed balance_msat field.
(18, self.outbound_capacity_msat, required),
(19, self.next_outbound_htlc_limit_msat, required),
(20, self.inbound_capacity_msat, required),
impl Readable for ChannelDetails {
fn read<R: Read>(reader: &mut R) -> Result<Self, DecodeError> {
- _init_and_read_tlv_fields!(reader, {
+ _init_and_read_len_prefixed_tlv_fields!(reader, {
(1, inbound_scid_alias, option),
(2, channel_id, required),
(3, channel_type, option),
(10, channel_value_satoshis, required),
(12, unspendable_punishment_reserve, option),
(14, user_channel_id_low, required),
- (16, balance_msat, required),
+ (16, _balance_msat, option), // Backwards compatibility for removed balance_msat field.
(18, outbound_capacity_msat, required),
// Note that by the time we get past the required read above, outbound_capacity_msat will be
// filled in, so we can safely unwrap it here.
let user_channel_id = user_channel_id_low as u128 +
((user_channel_id_high_opt.unwrap_or(0 as u64) as u128) << 64);
+ let _balance_msat: Option<u64> = _balance_msat;
+
Ok(Self {
inbound_scid_alias,
channel_id: channel_id.0.unwrap(),
channel_value_satoshis: channel_value_satoshis.0.unwrap(),
unspendable_punishment_reserve,
user_channel_id,
- balance_msat: balance_msat.0.unwrap(),
outbound_capacity_msat: outbound_capacity_msat.0.unwrap(),
next_outbound_htlc_limit_msat: next_outbound_htlc_limit_msat.0.unwrap(),
next_outbound_htlc_minimum_msat: next_outbound_htlc_minimum_msat.0.unwrap(),
}
impl_writeable_tlv_based!(PhantomRouteHints, {
- (2, channels, vec_type),
+ (2, channels, required_vec),
(4, phantom_scid, required),
(6, real_node_pubkey, required),
});
(1, phantom_shared_secret, option),
(2, incoming_cltv_expiry, required),
(3, payment_metadata, option),
+ (5, custom_tlvs, optional_vec),
},
(2, ReceiveKeysend) => {
(0, payment_preimage, required),
(2, incoming_cltv_expiry, required),
(3, payment_metadata, option),
(4, payment_data, option), // Added in 0.0.116
+ (5, custom_tlvs, optional_vec),
},
;);
(1, phantom_shared_secret, option),
(2, outpoint, required),
(4, htlc_id, required),
- (6, incoming_packet_shared_secret, required)
+ (6, incoming_packet_shared_secret, required),
+ (7, user_channel_id, option),
});
impl Writeable for ClaimableHTLC {
impl Readable for ClaimableHTLC {
fn read<R: Read>(reader: &mut R) -> Result<Self, DecodeError> {
- _init_and_read_tlv_fields!(reader, {
+ _init_and_read_len_prefixed_tlv_fields!(reader, {
(0, prev_hop, required),
(1, total_msat, option),
(2, value_ser, required),
0 => {
let mut session_priv: crate::util::ser::RequiredWrapper<SecretKey> = crate::util::ser::RequiredWrapper(None);
let mut first_hop_htlc_msat: u64 = 0;
- let mut path_hops: Option<Vec<RouteHop>> = Some(Vec::new());
+ let mut path_hops = Vec::new();
let mut payment_id = None;
let mut payment_params: Option<PaymentParameters> = None;
let mut blinded_tail: Option<BlindedTail> = None;
(0, session_priv, required),
(1, payment_id, option),
(2, first_hop_htlc_msat, required),
- (4, path_hops, vec_type),
+ (4, path_hops, required_vec),
(5, payment_params, (option: ReadableArgs, 0)),
(6, blinded_tail, option),
});
// instead.
payment_id = Some(PaymentId(*session_priv.0.unwrap().as_ref()));
}
- let path = Path { hops: path_hops.ok_or(DecodeError::InvalidValue)?, blinded_tail };
+ let path = Path { hops: path_hops, blinded_tail };
if path.hops.len() == 0 {
return Err(DecodeError::InvalidValue);
}
(1, payment_id_opt, option),
(2, first_hop_htlc_msat, required),
// 3 was previously used to write a PaymentSecret for the payment.
- (4, path.hops, vec_type),
+ (4, path.hops, required_vec),
(5, None::<PaymentParameters>, option), // payment_params in LDK versions prior to 0.0.115
(6, path.blinded_tail, option),
});
let mut serializable_peer_count: u64 = 0;
{
let per_peer_state = self.per_peer_state.read().unwrap();
- let mut unfunded_channels = 0;
- let mut number_of_channels = 0;
+ let mut number_of_funded_channels = 0;
for (_, peer_state_mutex) in per_peer_state.iter() {
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
let peer_state = &mut *peer_state_lock;
if !peer_state.ok_to_remove(false) {
serializable_peer_count += 1;
}
- number_of_channels += peer_state.channel_by_id.len();
- for (_, channel) in peer_state.channel_by_id.iter() {
- if !channel.context.is_funding_initiated() {
- unfunded_channels += 1;
- }
- }
+
+ number_of_funded_channels += peer_state.channel_by_id.iter().filter(
+ |(_, phase)| if let ChannelPhase::Funded(chan) = phase { chan.context.is_funding_broadcast() } else { false }
+ ).count();
}
- ((number_of_channels - unfunded_channels) as u64).write(writer)?;
+ (number_of_funded_channels as u64).write(writer)?;
for (_, peer_state_mutex) in per_peer_state.iter() {
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
let peer_state = &mut *peer_state_lock;
- for (_, channel) in peer_state.channel_by_id.iter() {
- if channel.context.is_funding_initiated() {
- channel.write(writer)?;
- }
+ for channel in peer_state.channel_by_id.iter().filter_map(
+ |(_, phase)| if let ChannelPhase::Funded(channel) = phase {
+ if channel.context.is_funding_broadcast() { Some(channel) } else { None }
+ } else { None }
+ ) {
+ channel.write(writer)?;
}
}
}
session_priv.write(writer)?;
}
}
+ PendingOutboundPayment::AwaitingInvoice { .. } => {},
+ PendingOutboundPayment::InvoiceReceived { .. } => {},
PendingOutboundPayment::Fulfilled { .. } => {},
PendingOutboundPayment::Abandoned { .. } => {},
}
(6, monitor_update_blocked_actions_per_peer, option),
(7, self.fake_scid_rand_bytes, required),
(8, if events_not_backwards_compatible { Some(&*events) } else { None }, option),
- (9, htlc_purposes, vec_type),
+ (9, htlc_purposes, required_vec),
(10, in_flight_monitor_updates, option),
(11, self.probing_cookie_secret, required),
(13, htlc_onion_fields, optional_vec),
let channel_count: u64 = Readable::read(reader)?;
let mut funding_txo_set = HashSet::with_capacity(cmp::min(channel_count as usize, 128));
- let mut peer_channels: HashMap<PublicKey, HashMap<[u8; 32], Channel<<SP::Target as SignerProvider>::Signer>>> = HashMap::with_capacity(cmp::min(channel_count as usize, 128));
+ let mut funded_peer_channels: HashMap<PublicKey, HashMap<ChannelId, ChannelPhase<SP>>> = HashMap::with_capacity(cmp::min(channel_count as usize, 128));
let mut id_to_peer = HashMap::with_capacity(cmp::min(channel_count as usize, 128));
let mut short_to_chan_info = HashMap::with_capacity(cmp::min(channel_count as usize, 128));
let mut channel_closures = VecDeque::new();
let mut close_background_events = Vec::new();
for _ in 0..channel_count {
- let mut channel: Channel<<SP::Target as SignerProvider>::Signer> = Channel::read(reader, (
+ let mut channel: Channel<SP> = Channel::read(reader, (
&args.entropy_source, &args.signer_provider, best_block_height, &provided_channel_type_features(&args.default_config)
))?;
let funding_txo = channel.context.get_funding_txo().ok_or(DecodeError::InvalidValue)?;
// But if the channel is behind of the monitor, close the channel:
log_error!(args.logger, "A ChannelManager is stale compared to the current ChannelMonitor!");
log_error!(args.logger, " The channel will be force-closed and the latest commitment transaction from the ChannelMonitor broadcast.");
- log_error!(args.logger, " The ChannelMonitor for channel {} is at update_id {} but the ChannelManager is at update_id {}.",
- log_bytes!(channel.context.channel_id()), monitor.get_latest_update_id(), channel.context.get_latest_monitor_update_id());
- let (monitor_update, mut new_failed_htlcs) = channel.context.force_shutdown(true);
+ if channel.context.get_latest_monitor_update_id() < monitor.get_latest_update_id() {
+ log_error!(args.logger, " The ChannelMonitor for channel {} is at update_id {} but the ChannelManager is at update_id {}.",
+ &channel.context.channel_id(), monitor.get_latest_update_id(), channel.context.get_latest_monitor_update_id());
+ }
+ if channel.get_cur_holder_commitment_transaction_number() > monitor.get_cur_holder_commitment_number() {
+ log_error!(args.logger, " The ChannelMonitor for channel {} is at holder commitment number {} but the ChannelManager is at holder commitment number {}.",
+ &channel.context.channel_id(), monitor.get_cur_holder_commitment_number(), channel.get_cur_holder_commitment_transaction_number());
+ }
+ if channel.get_revoked_counterparty_commitment_transaction_number() > monitor.get_min_seen_secret() {
+ log_error!(args.logger, " The ChannelMonitor for channel {} is at revoked counterparty transaction number {} but the ChannelManager is at revoked counterparty transaction number {}.",
+ &channel.context.channel_id(), monitor.get_min_seen_secret(), channel.get_revoked_counterparty_commitment_transaction_number());
+ }
+ if channel.get_cur_counterparty_commitment_transaction_number() > monitor.get_cur_counterparty_commitment_number() {
+ 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() {
+ return Err(DecodeError::InvalidValue);
+ }
if let Some((counterparty_node_id, funding_txo, update)) = monitor_update {
close_background_events.push(BackgroundEvent::MonitorUpdateRegeneratedOnStartup {
counterparty_node_id, funding_txo, update
channel_closures.push_back((events::Event::ChannelClosed {
channel_id: channel.context.channel_id(),
user_channel_id: channel.context.get_user_id(),
- reason: ClosureReason::OutdatedChannelManager
+ reason: ClosureReason::OutdatedChannelManager,
+ counterparty_node_id: Some(channel.context.get_counterparty_node_id()),
+ channel_capacity_sats: Some(channel.context.get_value_satoshis()),
}, None));
for (channel_htlc_source, payment_hash) in channel.inflight_htlc_sources() {
let mut found_htlc = false;
// backwards leg of the HTLC will simply be rejected.
log_info!(args.logger,
"Failing HTLC with hash {} as it is missing in the ChannelMonitor for channel {} but was present in the (stale) ChannelManager",
- log_bytes!(channel.context.channel_id()), log_bytes!(payment_hash.0));
+ &channel.context.channel_id(), &payment_hash);
failed_htlcs.push((channel_htlc_source.clone(), *payment_hash, channel.context.get_counterparty_node_id(), channel.context.channel_id()));
}
}
} else {
log_info!(args.logger, "Successfully loaded channel {} at update_id {} against monitor at update id {}",
- log_bytes!(channel.context.channel_id()), channel.context.get_latest_monitor_update_id(),
+ &channel.context.channel_id(), channel.context.get_latest_monitor_update_id(),
monitor.get_latest_update_id());
if let Some(short_channel_id) = channel.context.get_short_channel_id() {
short_to_chan_info.insert(short_channel_id, (channel.context.get_counterparty_node_id(), channel.context.channel_id()));
}
- if channel.context.is_funding_initiated() {
+ if channel.context.is_funding_broadcast() {
id_to_peer.insert(channel.context.channel_id(), channel.context.get_counterparty_node_id());
}
- match peer_channels.entry(channel.context.get_counterparty_node_id()) {
+ match funded_peer_channels.entry(channel.context.get_counterparty_node_id()) {
hash_map::Entry::Occupied(mut entry) => {
let by_id_map = entry.get_mut();
- by_id_map.insert(channel.context.channel_id(), channel);
+ by_id_map.insert(channel.context.channel_id(), ChannelPhase::Funded(channel));
},
hash_map::Entry::Vacant(entry) => {
let mut by_id_map = HashMap::new();
- by_id_map.insert(channel.context.channel_id(), channel);
+ by_id_map.insert(channel.context.channel_id(), ChannelPhase::Funded(channel));
entry.insert(by_id_map);
}
}
channel_id: channel.context.channel_id(),
user_channel_id: channel.context.get_user_id(),
reason: ClosureReason::DisconnectedPeer,
+ counterparty_node_id: Some(channel.context.get_counterparty_node_id()),
+ channel_capacity_sats: Some(channel.context.get_value_satoshis()),
}, None));
} else {
- log_error!(args.logger, "Missing ChannelMonitor for channel {} needed by ChannelManager.", log_bytes!(channel.context.channel_id()));
+ log_error!(args.logger, "Missing ChannelMonitor for channel {} needed by ChannelManager.", &channel.context.channel_id());
log_error!(args.logger, " The chain::Watch API *requires* that monitors are persisted durably before returning,");
log_error!(args.logger, " client applications must ensure that ChannelMonitor data is always available and the latest to avoid funds loss!");
log_error!(args.logger, " Without the ChannelMonitor we cannot continue without risking funds.");
for (funding_txo, _) in args.channel_monitors.iter() {
if !funding_txo_set.contains(funding_txo) {
log_info!(args.logger, "Queueing monitor update to ensure missing channel {} is force closed",
- log_bytes!(funding_txo.to_channel_id()));
+ &funding_txo.to_channel_id());
let monitor_update = ChannelMonitorUpdate {
update_id: CLOSED_CHANNEL_UPDATE_ID,
updates: vec![ChannelMonitorUpdateStep::ChannelForceClosed { should_broadcast: true }],
};
- close_background_events.push(BackgroundEvent::ClosingMonitorUpdateRegeneratedOnStartup((*funding_txo, monitor_update)));
+ close_background_events.push(BackgroundEvent::ClosedMonitorUpdateRegeneratedOnStartup((*funding_txo, monitor_update)));
}
}
let peer_state_from_chans = |channel_by_id| {
PeerState {
channel_by_id,
- outbound_v1_channel_by_id: HashMap::new(),
- inbound_v1_channel_by_id: HashMap::new(),
+ inbound_channel_request_by_id: HashMap::new(),
latest_features: InitFeatures::empty(),
pending_msg_events: Vec::new(),
in_flight_monitor_updates: BTreeMap::new(),
};
let peer_count: u64 = Readable::read(reader)?;
- let mut per_peer_state = HashMap::with_capacity(cmp::min(peer_count as usize, MAX_ALLOC_SIZE/mem::size_of::<(PublicKey, Mutex<PeerState<<SP::Target as SignerProvider>::Signer>>)>()));
+ let mut per_peer_state = HashMap::with_capacity(cmp::min(peer_count as usize, MAX_ALLOC_SIZE/mem::size_of::<(PublicKey, Mutex<PeerState<SP>>)>()));
for _ in 0..peer_count {
let peer_pubkey = Readable::read(reader)?;
- let peer_chans = peer_channels.remove(&peer_pubkey).unwrap_or(HashMap::new());
+ let peer_chans = funded_peer_channels.remove(&peer_pubkey).unwrap_or(HashMap::new());
let mut peer_state = peer_state_from_chans(peer_chans);
peer_state.latest_features = Readable::read(reader)?;
per_peer_state.insert(peer_pubkey, Mutex::new(peer_state));
(6, monitor_update_blocked_actions_per_peer, option),
(7, fake_scid_rand_bytes, option),
(8, events_override, option),
- (9, claimable_htlc_purposes, vec_type),
+ (9, claimable_htlc_purposes, optional_vec),
(10, in_flight_monitor_updates, option),
(11, probing_cookie_secret, option),
(13, claimable_htlc_onion_fields, optional_vec),
$chan_in_flight_upds.retain(|upd| upd.update_id > $monitor.get_latest_update_id());
for update in $chan_in_flight_upds.iter() {
log_trace!(args.logger, "Replaying ChannelMonitorUpdate {} for {}channel {}",
- update.update_id, $channel_info_log, log_bytes!($funding_txo.to_channel_id()));
+ update.update_id, $channel_info_log, &$funding_txo.to_channel_id());
max_in_flight_update_id = cmp::max(max_in_flight_update_id, update.update_id);
pending_background_events.push(
BackgroundEvent::MonitorUpdateRegeneratedOnStartup {
update: update.clone(),
});
}
+ if $chan_in_flight_upds.is_empty() {
+ // We had some updates to apply, but it turns out they had completed before we
+ // were serialized, we just weren't notified of that. Thus, we may have to run
+ // the completion actions for any monitor updates, but otherwise are done.
+ pending_background_events.push(
+ BackgroundEvent::MonitorUpdatesComplete {
+ counterparty_node_id: $counterparty_node_id,
+ channel_id: $funding_txo.to_channel_id(),
+ });
+ }
if $peer_state.in_flight_monitor_updates.insert($funding_txo, $chan_in_flight_upds).is_some() {
log_error!(args.logger, "Duplicate in-flight monitor update set for the same channel!");
return Err(DecodeError::InvalidValue);
for (counterparty_id, peer_state_mtx) in per_peer_state.iter_mut() {
let mut peer_state_lock = peer_state_mtx.lock().unwrap();
let peer_state = &mut *peer_state_lock;
- for (_, chan) in peer_state.channel_by_id.iter() {
- // Channels that were persisted have to be funded, otherwise they should have been
- // discarded.
- let funding_txo = chan.context.get_funding_txo().ok_or(DecodeError::InvalidValue)?;
- let monitor = args.channel_monitors.get(&funding_txo)
- .expect("We already checked for monitor presence when loading channels");
- let mut max_in_flight_update_id = monitor.get_latest_update_id();
- if let Some(in_flight_upds) = &mut in_flight_monitor_updates {
- if let Some(mut chan_in_flight_upds) = in_flight_upds.remove(&(*counterparty_id, funding_txo)) {
- max_in_flight_update_id = cmp::max(max_in_flight_update_id,
- handle_in_flight_updates!(*counterparty_id, chan_in_flight_upds,
- funding_txo, monitor, peer_state, ""));
+ for phase in peer_state.channel_by_id.values() {
+ if let ChannelPhase::Funded(chan) = phase {
+ // Channels that were persisted have to be funded, otherwise they should have been
+ // discarded.
+ let funding_txo = chan.context.get_funding_txo().ok_or(DecodeError::InvalidValue)?;
+ let monitor = args.channel_monitors.get(&funding_txo)
+ .expect("We already checked for monitor presence when loading channels");
+ let mut max_in_flight_update_id = monitor.get_latest_update_id();
+ if let Some(in_flight_upds) = &mut in_flight_monitor_updates {
+ if let Some(mut chan_in_flight_upds) = in_flight_upds.remove(&(*counterparty_id, funding_txo)) {
+ max_in_flight_update_id = cmp::max(max_in_flight_update_id,
+ handle_in_flight_updates!(*counterparty_id, chan_in_flight_upds,
+ funding_txo, monitor, peer_state, ""));
+ }
}
- }
- if chan.get_latest_unblocked_monitor_update_id() > max_in_flight_update_id {
- // If the channel is ahead of the monitor, return InvalidValue:
- log_error!(args.logger, "A ChannelMonitor is stale compared to the current ChannelManager! This indicates a potentially-critical violation of the chain::Watch API!");
- log_error!(args.logger, " The ChannelMonitor for channel {} is at update_id {} with update_id through {} in-flight",
- log_bytes!(chan.context.channel_id()), monitor.get_latest_update_id(), max_in_flight_update_id);
- log_error!(args.logger, " but the ChannelManager is at update_id {}.", chan.get_latest_unblocked_monitor_update_id());
- log_error!(args.logger, " The chain::Watch API *requires* that monitors are persisted durably before returning,");
- log_error!(args.logger, " client applications must ensure that ChannelMonitor data is always available and the latest to avoid funds loss!");
- log_error!(args.logger, " Without the latest ChannelMonitor we cannot continue without risking funds.");
- log_error!(args.logger, " Please ensure the chain::Watch API requirements are met and file a bug report at https://github.com/lightningdevkit/rust-lightning");
+ if chan.get_latest_unblocked_monitor_update_id() > max_in_flight_update_id {
+ // If the channel is ahead of the monitor, return InvalidValue:
+ log_error!(args.logger, "A ChannelMonitor is stale compared to the current ChannelManager! This indicates a potentially-critical violation of the chain::Watch API!");
+ log_error!(args.logger, " The ChannelMonitor for channel {} is at update_id {} with update_id through {} in-flight",
+ chan.context.channel_id(), monitor.get_latest_update_id(), max_in_flight_update_id);
+ log_error!(args.logger, " but the ChannelManager is at update_id {}.", chan.get_latest_unblocked_monitor_update_id());
+ log_error!(args.logger, " The chain::Watch API *requires* that monitors are persisted durably before returning,");
+ log_error!(args.logger, " client applications must ensure that ChannelMonitor data is always available and the latest to avoid funds loss!");
+ log_error!(args.logger, " Without the latest ChannelMonitor we cannot continue without risking funds.");
+ log_error!(args.logger, " Please ensure the chain::Watch API requirements are met and file a bug report at https://github.com/lightningdevkit/rust-lightning");
+ return Err(DecodeError::InvalidValue);
+ }
+ } else {
+ // We shouldn't have persisted (or read) any unfunded channel types so none should have been
+ // created in this `channel_by_id` map.
+ debug_assert!(false);
return Err(DecodeError::InvalidValue);
}
}
} else {
log_error!(args.logger, "A ChannelMonitor is missing even though we have in-flight updates for it! This indicates a potentially-critical violation of the chain::Watch API!");
log_error!(args.logger, " The ChannelMonitor for channel {} is missing.",
- log_bytes!(funding_txo.to_channel_id()));
+ &funding_txo.to_channel_id());
log_error!(args.logger, " The chain::Watch API *requires* that monitors are persisted durably before returning,");
log_error!(args.logger, " client applications must ensure that ChannelMonitor data is always available and the latest to avoid funds loss!");
log_error!(args.logger, " Without the latest ChannelMonitor we cannot continue without risking funds.");
// Note that we have to do the above replays before we push new monitor updates.
pending_background_events.append(&mut close_background_events);
+ // If there's any preimages for forwarded HTLCs hanging around in ChannelMonitors we
+ // should ensure we try them again on the inbound edge. We put them here and do so after we
+ // have a fully-constructed `ChannelManager` at the end.
+ let mut pending_claims_to_replay = Vec::new();
+
{
// If we're tracking pending payments, ensure we haven't lost any by looking at the
// ChannelMonitor data for any channels for which we do not have authorative state
// We only rebuild the pending payments map if we were most recently serialized by
// 0.0.102+
for (_, monitor) in args.channel_monitors.iter() {
- if id_to_peer.get(&monitor.get_funding_txo().0.to_channel_id()).is_none() {
+ let counterparty_opt = id_to_peer.get(&monitor.get_funding_txo().0.to_channel_id());
+ if counterparty_opt.is_none() {
for (htlc_source, (htlc, _)) in monitor.get_pending_or_resolved_outbound_htlcs() {
if let HTLCSource::OutboundRoute { payment_id, session_priv, path, .. } = htlc_source {
if path.hops.is_empty() {
hash_map::Entry::Occupied(mut entry) => {
let newly_added = entry.get_mut().insert(session_priv_bytes, &path);
log_info!(args.logger, "{} a pending payment path for {} msat for session priv {} on an existing pending payment with payment hash {}",
- if newly_added { "Added" } else { "Had" }, path_amt, log_bytes!(session_priv_bytes), log_bytes!(htlc.payment_hash.0));
+ if newly_added { "Added" } else { "Had" }, path_amt, log_bytes!(session_priv_bytes), &htlc.payment_hash);
},
hash_map::Entry::Vacant(entry) => {
let path_fee = path.fee_msat();
payment_secret: None, // only used for retries, and we'll never retry on startup
payment_metadata: None, // only used for retries, and we'll never retry on startup
keysend_preimage: None, // only used for retries, and we'll never retry on startup
+ custom_tlvs: Vec::new(), // only used for retries, and we'll never retry on startup
pending_amt_msat: path_amt,
pending_fee_msat: Some(path_fee),
total_msat: path_amt,
starting_block_height: best_block_height,
+ remaining_max_total_routing_fee_msat: None, // only used for retries, and we'll never retry on startup
});
log_info!(args.logger, "Added a pending payment for {} msat with payment hash {} for path with session priv {}",
- path_amt, log_bytes!(htlc.payment_hash.0), log_bytes!(session_priv_bytes));
+ path_amt, &htlc.payment_hash, log_bytes!(session_priv_bytes));
}
}
}
if let HTLCForwardInfo::AddHTLC(htlc_info) = forward {
if pending_forward_matches_htlc(&htlc_info) {
log_info!(args.logger, "Removing pending to-forward HTLC with hash {} as it was forwarded to the closed channel {}",
- log_bytes!(htlc.payment_hash.0), log_bytes!(monitor.get_funding_txo().0.to_channel_id()));
+ &htlc.payment_hash, &monitor.get_funding_txo().0.to_channel_id());
false
} else { true }
} else { true }
pending_intercepted_htlcs.as_mut().unwrap().retain(|intercepted_id, htlc_info| {
if pending_forward_matches_htlc(&htlc_info) {
log_info!(args.logger, "Removing pending intercepted HTLC with hash {} as it was forwarded to the closed channel {}",
- log_bytes!(htlc.payment_hash.0), log_bytes!(monitor.get_funding_txo().0.to_channel_id()));
+ &htlc.payment_hash, &monitor.get_funding_txo().0.to_channel_id());
pending_events_read.retain(|(event, _)| {
if let Event::HTLCIntercepted { intercept_id: ev_id, .. } = event {
intercepted_id != ev_id
// generating a `PaymentPathSuccessful` event but regenerating
// it and the `PaymentSent` on every restart until the
// `ChannelMonitor` is removed.
- pending_outbounds.claim_htlc(payment_id, preimage, session_priv, path, false, &pending_events, &args.logger);
+ let compl_action =
+ EventCompletionAction::ReleaseRAAChannelMonitorUpdate {
+ channel_funding_outpoint: monitor.get_funding_txo().0,
+ counterparty_node_id: path.hops[0].pubkey,
+ };
+ pending_outbounds.claim_htlc(payment_id, preimage, session_priv,
+ path, false, compl_action, &pending_events, &args.logger);
pending_events_read = pending_events.into_inner().unwrap();
}
},
}
}
}
+
+ // Whether the downstream channel was closed or not, try to re-apply any payment
+ // preimages from it which may be needed in upstream channels for forwarded
+ // payments.
+ let outbound_claimed_htlcs_iter = monitor.get_all_current_outbound_htlcs()
+ .into_iter()
+ .filter_map(|(htlc_source, (htlc, preimage_opt))| {
+ if let HTLCSource::PreviousHopData(_) = htlc_source {
+ if let Some(payment_preimage) = preimage_opt {
+ Some((htlc_source, payment_preimage, htlc.amount_msat,
+ // Check if `counterparty_opt.is_none()` to see if the
+ // downstream chan is closed (because we don't have a
+ // channel_id -> peer map entry).
+ counterparty_opt.is_none(),
+ counterparty_opt.cloned().or(monitor.get_counterparty_node_id()),
+ monitor.get_funding_txo().0))
+ } else { None }
+ } else {
+ // If it was an outbound payment, we've handled it above - if a preimage
+ // came in and we persisted the `ChannelManager` we either handled it and
+ // are good to go or the channel force-closed - we don't have to handle the
+ // channel still live case here.
+ None
+ }
+ });
+ for tuple in outbound_claimed_htlcs_iter {
+ pending_claims_to_replay.push(tuple);
+ }
}
}
None => match inbound_payment::verify(payment_hash, &hop_data, 0, &expanded_inbound_key, &args.logger) {
Ok((payment_preimage, _)) => payment_preimage,
Err(()) => {
- log_error!(args.logger, "Failed to read claimable payment data for HTLC with payment hash {} - was not a pending inbound payment and didn't match our payment key", log_bytes!(payment_hash.0));
+ log_error!(args.logger, "Failed to read claimable payment data for HTLC with payment hash {} - was not a pending inbound payment and didn't match our payment key", &payment_hash);
return Err(DecodeError::InvalidValue);
}
}
for (_peer_node_id, peer_state_mutex) in per_peer_state.iter_mut() {
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
let peer_state = &mut *peer_state_lock;
- for (chan_id, chan) in peer_state.channel_by_id.iter_mut() {
- if chan.context.outbound_scid_alias() == 0 {
- 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);
- if outbound_scid_aliases.insert(outbound_scid_alias) { break; }
- }
- chan.context.set_outbound_scid_alias(outbound_scid_alias);
- } else if !outbound_scid_aliases.insert(chan.context.outbound_scid_alias()) {
- // Note that in rare cases its possible to hit this while reading an older
- // channel if we just happened to pick a colliding outbound alias above.
- log_error!(args.logger, "Got duplicate outbound SCID alias; {}", chan.context.outbound_scid_alias());
- return Err(DecodeError::InvalidValue);
- }
- if chan.context.is_usable() {
- if short_to_chan_info.insert(chan.context.outbound_scid_alias(), (chan.context.get_counterparty_node_id(), *chan_id)).is_some() {
+ for (chan_id, phase) in peer_state.channel_by_id.iter_mut() {
+ if let ChannelPhase::Funded(chan) = phase {
+ if chan.context.outbound_scid_alias() == 0 {
+ 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);
+ if outbound_scid_aliases.insert(outbound_scid_alias) { break; }
+ }
+ chan.context.set_outbound_scid_alias(outbound_scid_alias);
+ } else if !outbound_scid_aliases.insert(chan.context.outbound_scid_alias()) {
// Note that in rare cases its possible to hit this while reading an older
// channel if we just happened to pick a colliding outbound alias above.
log_error!(args.logger, "Got duplicate outbound SCID alias; {}", chan.context.outbound_scid_alias());
return Err(DecodeError::InvalidValue);
}
+ if chan.context.is_usable() {
+ if short_to_chan_info.insert(chan.context.outbound_scid_alias(), (chan.context.get_counterparty_node_id(), *chan_id)).is_some() {
+ // Note that in rare cases its possible to hit this while reading an older
+ // channel if we just happened to pick a colliding outbound alias above.
+ log_error!(args.logger, "Got duplicate outbound SCID alias; {}", chan.context.outbound_scid_alias());
+ return Err(DecodeError::InvalidValue);
+ }
+ }
+ } else {
+ // We shouldn't have persisted (or read) any unfunded channel types so none should have been
+ // created in this `channel_by_id` map.
+ debug_assert!(false);
+ return Err(DecodeError::InvalidValue);
}
}
}
for (_, monitor) in args.channel_monitors.iter() {
for (payment_hash, payment_preimage) in monitor.get_stored_preimages() {
if let Some(payment) = claimable_payments.remove(&payment_hash) {
- log_info!(args.logger, "Re-claiming HTLCs with payment hash {} as we've released the preimage to a ChannelMonitor!", log_bytes!(payment_hash.0));
+ log_info!(args.logger, "Re-claiming HTLCs with payment hash {} as we've released the preimage to a ChannelMonitor!", &payment_hash);
let mut claimable_amt_msat = 0;
let mut receiver_node_id = Some(our_network_pubkey);
let phantom_shared_secret = payment.htlcs[0].prev_hop.phantom_shared_secret;
.expect("Failed to get node_id for phantom node recipient");
receiver_node_id = Some(phantom_pubkey)
}
- for claimable_htlc in payment.htlcs {
+ for claimable_htlc in &payment.htlcs {
claimable_amt_msat += claimable_htlc.value;
// Add a holding-cell claim of the payment to the Channel, which should be
let peer_state_mutex = per_peer_state.get(peer_node_id).unwrap();
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
let peer_state = &mut *peer_state_lock;
- if let Some(channel) = peer_state.channel_by_id.get_mut(&previous_channel_id) {
+ if let Some(ChannelPhase::Funded(channel)) = peer_state.channel_by_id.get_mut(&previous_channel_id) {
channel.claim_htlc_while_disconnected_dropping_mon_update(claimable_htlc.prev_hop.htlc_id, payment_preimage, &args.logger);
}
}
payment_hash,
purpose: payment.purpose,
amount_msat: claimable_amt_msat,
+ htlcs: payment.htlcs.iter().map(events::ClaimedHTLC::from).collect(),
+ sender_intended_total_msat: payment.htlcs.first().map(|htlc| htlc.total_msat),
}, None));
}
}
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());
+ } else {
+ // If the channel we were blocking has closed, we don't need to
+ // worry about it - the blocked monitor update should never have
+ // been released from the `Channel` object so it can't have
+ // completed, and if the channel closed there's no reason to bother
+ // anymore.
}
}
}
pending_events_processor: AtomicBool::new(false),
pending_background_events: Mutex::new(pending_background_events),
total_consistency_lock: RwLock::new(()),
- #[cfg(debug_assertions)]
background_events_processed_since_startup: AtomicBool::new(false),
- persistence_notifier: Notifier::new(),
+
+ event_persist_notifier: Notifier::new(),
+ needs_persist_flag: AtomicBool::new(false),
+
+ funding_batch_states: Mutex::new(BTreeMap::new()),
entropy_source: args.entropy_source,
node_signer: args.node_signer,
channel_manager.fail_htlc_backwards_internal(&source, &payment_hash, &reason, receiver);
}
+ for (source, preimage, downstream_value, downstream_closed, downstream_node_id, downstream_funding) in pending_claims_to_replay {
+ // We use `downstream_closed` in place of `from_onchain` here just as a guess - we
+ // 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);
+ }
+
//TODO: Broadcast channel update for closed channels, but only after we've made a
//connection or two.
use core::sync::atomic::Ordering;
use crate::events::{Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider, ClosureReason};
use crate::ln::{PaymentPreimage, PaymentHash, PaymentSecret};
+ use crate::ln::ChannelId;
use crate::ln::channelmanager::{inbound_payment, PaymentId, PaymentSendFailure, RecipientOnionFields, InterceptId};
use crate::ln::functional_test_utils::*;
use crate::ln::msgs::{self, ErrorAction};
// All nodes start with a persistable update pending as `create_network` connects each node
// with all other nodes to make most tests simpler.
- assert!(nodes[0].node.get_persistable_update_future().poll_is_complete());
- assert!(nodes[1].node.get_persistable_update_future().poll_is_complete());
- assert!(nodes[2].node.get_persistable_update_future().poll_is_complete());
+ assert!(nodes[0].node.get_event_or_persistence_needed_future().poll_is_complete());
+ assert!(nodes[1].node.get_event_or_persistence_needed_future().poll_is_complete());
+ assert!(nodes[2].node.get_event_or_persistence_needed_future().poll_is_complete());
let mut chan = create_announced_chan_between_nodes(&nodes, 0, 1);
&nodes[0].node.get_our_node_id()).pop().unwrap();
// The first two nodes (which opened a channel) should now require fresh persistence
- assert!(nodes[0].node.get_persistable_update_future().poll_is_complete());
- assert!(nodes[1].node.get_persistable_update_future().poll_is_complete());
+ assert!(nodes[0].node.get_event_or_persistence_needed_future().poll_is_complete());
+ assert!(nodes[1].node.get_event_or_persistence_needed_future().poll_is_complete());
// ... but the last node should not.
- assert!(!nodes[2].node.get_persistable_update_future().poll_is_complete());
+ assert!(!nodes[2].node.get_event_or_persistence_needed_future().poll_is_complete());
// After persisting the first two nodes they should no longer need fresh persistence.
- assert!(!nodes[0].node.get_persistable_update_future().poll_is_complete());
- assert!(!nodes[1].node.get_persistable_update_future().poll_is_complete());
+ assert!(!nodes[0].node.get_event_or_persistence_needed_future().poll_is_complete());
+ assert!(!nodes[1].node.get_event_or_persistence_needed_future().poll_is_complete());
// Node 3, unrelated to the only channel, shouldn't care if it receives a channel_update
// about the channel.
nodes[2].node.handle_channel_update(&nodes[1].node.get_our_node_id(), &chan.0);
nodes[2].node.handle_channel_update(&nodes[1].node.get_our_node_id(), &chan.1);
- assert!(!nodes[2].node.get_persistable_update_future().poll_is_complete());
+ assert!(!nodes[2].node.get_event_or_persistence_needed_future().poll_is_complete());
// The nodes which are a party to the channel should also ignore messages from unrelated
// parties.
nodes[0].node.handle_channel_update(&nodes[2].node.get_our_node_id(), &chan.1);
nodes[1].node.handle_channel_update(&nodes[2].node.get_our_node_id(), &chan.0);
nodes[1].node.handle_channel_update(&nodes[2].node.get_our_node_id(), &chan.1);
- assert!(!nodes[0].node.get_persistable_update_future().poll_is_complete());
- assert!(!nodes[1].node.get_persistable_update_future().poll_is_complete());
+ assert!(!nodes[0].node.get_event_or_persistence_needed_future().poll_is_complete());
+ assert!(!nodes[1].node.get_event_or_persistence_needed_future().poll_is_complete());
// At this point the channel info given by peers should still be the same.
assert_eq!(nodes[0].node.list_channels()[0], node_a_chan_info);
// persisted and that its channel info remains the same.
nodes[0].node.handle_channel_update(&nodes[1].node.get_our_node_id(), &as_update);
nodes[1].node.handle_channel_update(&nodes[0].node.get_our_node_id(), &bs_update);
- assert!(!nodes[0].node.get_persistable_update_future().poll_is_complete());
- assert!(!nodes[1].node.get_persistable_update_future().poll_is_complete());
+ assert!(!nodes[0].node.get_event_or_persistence_needed_future().poll_is_complete());
+ assert!(!nodes[1].node.get_event_or_persistence_needed_future().poll_is_complete());
assert_eq!(nodes[0].node.list_channels()[0], node_a_chan_info);
assert_eq!(nodes[1].node.list_channels()[0], node_b_chan_info);
// the channel info has updated.
nodes[0].node.handle_channel_update(&nodes[1].node.get_our_node_id(), &bs_update);
nodes[1].node.handle_channel_update(&nodes[0].node.get_our_node_id(), &as_update);
- assert!(nodes[0].node.get_persistable_update_future().poll_is_complete());
- assert!(nodes[1].node.get_persistable_update_future().poll_is_complete());
+ assert!(nodes[0].node.get_event_or_persistence_needed_future().poll_is_complete());
+ assert!(nodes[1].node.get_event_or_persistence_needed_future().poll_is_complete());
assert_ne!(nodes[0].node.list_channels()[0], node_a_chan_info);
assert_ne!(nodes[1].node.list_channels()[0], node_b_chan_info);
}
let bs_first_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_first_updates.update_fulfill_htlcs[0]);
+ expect_payment_sent(&nodes[0], payment_preimage, None, false, false);
nodes[0].node.handle_commitment_signed(&nodes[1].node.get_our_node_id(), &bs_first_updates.commitment_signed);
check_added_monitors!(nodes[0], 1);
let (as_first_raa, as_first_cs) = get_revoke_commit_msgs!(nodes[0], nodes[1].node.get_our_node_id());
// Note that successful MPP payments will generate a single PaymentSent event upon the first
// path's success and a PaymentPathSuccessful event for each path's success.
let events = nodes[0].node.get_and_clear_pending_events();
- assert_eq!(events.len(), 3);
+ assert_eq!(events.len(), 2);
match events[0] {
- Event::PaymentSent { payment_id: ref id, payment_preimage: ref preimage, payment_hash: ref hash, .. } => {
- assert_eq!(Some(payment_id), *id);
- assert_eq!(payment_preimage, *preimage);
- assert_eq!(our_payment_hash, *hash);
- },
- _ => panic!("Unexpected event"),
- }
- match events[1] {
Event::PaymentPathSuccessful { payment_id: ref actual_payment_id, ref payment_hash, ref path } => {
assert_eq!(payment_id, *actual_payment_id);
assert_eq!(our_payment_hash, *payment_hash.as_ref().unwrap());
},
_ => panic!("Unexpected event"),
}
- match events[2] {
+ match events[1] {
Event::PaymentPathSuccessful { payment_id: ref actual_payment_id, ref payment_hash, ref path } => {
assert_eq!(payment_id, *actual_payment_id);
assert_eq!(our_payment_hash, *payment_hash.as_ref().unwrap());
// To start (1), send a regular payment but don't claim it.
let expected_route = [&nodes[1]];
- let (payment_preimage, payment_hash, _) = route_payment(&nodes[0], &expected_route, 100_000);
+ let (payment_preimage, payment_hash, ..) = route_payment(&nodes[0], &expected_route, 100_000);
// Next, attempt a keysend payment and make sure it fails.
- let route_params = RouteParameters {
- payment_params: PaymentParameters::for_keysend(expected_route.last().unwrap().node.get_our_node_id(), TEST_FINAL_CLTV, false),
- final_value_msat: 100_000,
- };
+ let route_params = RouteParameters::from_payment_params_and_value(
+ PaymentParameters::for_keysend(expected_route.last().unwrap().node.get_our_node_id(),
+ TEST_FINAL_CLTV, false), 100_000);
let route = find_route(
&nodes[0].node.get_our_node_id(), &route_params, &nodes[0].network_graph,
- None, nodes[0].logger, &scorer, &(), &random_seed_bytes
+ None, nodes[0].logger, &scorer, &Default::default(), &random_seed_bytes
).unwrap();
nodes[0].node.send_spontaneous_payment(&route, Some(payment_preimage),
RecipientOnionFields::spontaneous_empty(), PaymentId(payment_preimage.0)).unwrap();
let payment_preimage = PaymentPreimage([42; 32]);
let route = find_route(
&nodes[0].node.get_our_node_id(), &route_params, &nodes[0].network_graph,
- None, nodes[0].logger, &scorer, &(), &random_seed_bytes
+ None, nodes[0].logger, &scorer, &Default::default(), &random_seed_bytes
).unwrap();
let payment_hash = nodes[0].node.send_spontaneous_payment(&route, Some(payment_preimage),
RecipientOnionFields::spontaneous_empty(), PaymentId(payment_preimage.0)).unwrap();
pass_along_path(&nodes[0], &path, 100_000, payment_hash, None, event, true, Some(payment_preimage));
// Next, attempt a keysend payment and make sure it fails.
- let route_params = RouteParameters {
- payment_params: PaymentParameters::for_keysend(expected_route.last().unwrap().node.get_our_node_id(), TEST_FINAL_CLTV, false),
- final_value_msat: 100_000,
- };
+ let route_params = RouteParameters::from_payment_params_and_value(
+ PaymentParameters::for_keysend(expected_route.last().unwrap().node.get_our_node_id(), TEST_FINAL_CLTV, false),
+ 100_000
+ );
let route = find_route(
&nodes[0].node.get_our_node_id(), &route_params, &nodes[0].network_graph,
- None, nodes[0].logger, &scorer, &(), &random_seed_bytes
+ None, nodes[0].logger, &scorer, &Default::default(), &random_seed_bytes
).unwrap();
let payment_id_2 = PaymentId([45; 32]);
nodes[0].node.send_spontaneous_payment(&route, Some(payment_preimage),
let payee_pubkey = nodes[1].node.get_our_node_id();
let _chan = create_chan_between_nodes(&nodes[0], &nodes[1]);
- let route_params = RouteParameters {
- payment_params: PaymentParameters::for_keysend(payee_pubkey, 40, false),
- final_value_msat: 10_000,
- };
+ let route_params = RouteParameters::from_payment_params_and_value(
+ PaymentParameters::for_keysend(payee_pubkey, 40, false), 10_000);
let network_graph = nodes[0].network_graph.clone();
let first_hops = nodes[0].node.list_usable_channels();
let scorer = test_utils::TestScorer::new();
let random_seed_bytes = chanmon_cfgs[1].keys_manager.get_secure_random_bytes();
let route = find_route(
&payer_pubkey, &route_params, &network_graph, Some(&first_hops.iter().collect::<Vec<_>>()),
- nodes[0].logger, &scorer, &(), &random_seed_bytes
+ nodes[0].logger, &scorer, &Default::default(), &random_seed_bytes
).unwrap();
let test_preimage = PaymentPreimage([42; 32]);
let payee_pubkey = nodes[1].node.get_our_node_id();
let _chan = create_chan_between_nodes(&nodes[0], &nodes[1]);
- let route_params = RouteParameters {
- payment_params: PaymentParameters::for_keysend(payee_pubkey, 40, false),
- final_value_msat: 10_000,
- };
+ let route_params = RouteParameters::from_payment_params_and_value(
+ PaymentParameters::for_keysend(payee_pubkey, 40, false), 10_000);
let network_graph = nodes[0].network_graph.clone();
let first_hops = nodes[0].node.list_usable_channels();
let scorer = test_utils::TestScorer::new();
let random_seed_bytes = chanmon_cfgs[1].keys_manager.get_secure_random_bytes();
let route = find_route(
&payer_pubkey, &route_params, &network_graph, Some(&first_hops.iter().collect::<Vec<_>>()),
- nodes[0].logger, &scorer, &(), &random_seed_bytes
+ nodes[0].logger, &scorer, &Default::default(), &random_seed_bytes
).unwrap();
let test_preimage = PaymentPreimage([42; 32]);
nodes[0].node.force_close_broadcasting_latest_txn(&chan.2, &nodes[1].node.get_our_node_id()).unwrap();
check_closed_broadcast!(nodes[0], true);
check_added_monitors!(nodes[0], 1);
- check_closed_event!(nodes[0], 1, ClosureReason::HolderForceClosed);
+ check_closed_event!(nodes[0], 1, ClosureReason::HolderForceClosed, [nodes[1].node.get_our_node_id()], 100000);
{
// Assert that nodes[1] is awaiting removal for nodes[0] once nodes[1] has been
nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), &accept_channel);
let (temporary_channel_id, tx, _funding_output) = create_funding_transaction(&nodes[0], &nodes[1].node.get_our_node_id(), 1_000_000, 42);
- let channel_id = &tx.txid().into_inner();
+ let channel_id = ChannelId::from_bytes(tx.txid().into_inner());
{
// Ensure that the `id_to_peer` map is empty until either party has received the
// funding transaction, and have the real `channel_id`.
// as it has the funding transaction.
let nodes_0_lock = nodes[0].node.id_to_peer.lock().unwrap();
assert_eq!(nodes_0_lock.len(), 1);
- assert!(nodes_0_lock.contains_key(channel_id));
+ assert!(nodes_0_lock.contains_key(&channel_id));
}
assert_eq!(nodes[1].node.id_to_peer.lock().unwrap().len(), 0);
{
let nodes_0_lock = nodes[0].node.id_to_peer.lock().unwrap();
assert_eq!(nodes_0_lock.len(), 1);
- assert!(nodes_0_lock.contains_key(channel_id));
+ assert!(nodes_0_lock.contains_key(&channel_id));
}
expect_channel_pending_event(&nodes[1], &nodes[0].node.get_our_node_id());
// as it has the funding transaction.
let nodes_1_lock = nodes[1].node.id_to_peer.lock().unwrap();
assert_eq!(nodes_1_lock.len(), 1);
- assert!(nodes_1_lock.contains_key(channel_id));
+ assert!(nodes_1_lock.contains_key(&channel_id));
}
check_added_monitors!(nodes[1], 1);
let funding_signed = get_event_msg!(nodes[1], MessageSendEvent::SendFundingSigned, nodes[0].node.get_our_node_id());
let (announcement, nodes_0_update, nodes_1_update) = create_chan_between_nodes_with_value_b(&nodes[0], &nodes[1], &channel_ready);
update_nodes_with_chan_announce(&nodes, 0, 1, &announcement, &nodes_0_update, &nodes_1_update);
- nodes[0].node.close_channel(channel_id, &nodes[1].node.get_our_node_id()).unwrap();
+ nodes[0].node.close_channel(&channel_id, &nodes[1].node.get_our_node_id()).unwrap();
nodes[1].node.handle_shutdown(&nodes[0].node.get_our_node_id(), &get_event_msg!(nodes[0], MessageSendEvent::SendShutdown, nodes[1].node.get_our_node_id()));
let nodes_1_shutdown = get_event_msg!(nodes[1], MessageSendEvent::SendShutdown, nodes[0].node.get_our_node_id());
nodes[0].node.handle_shutdown(&nodes[1].node.get_our_node_id(), &nodes_1_shutdown);
// party's signature for the fee negotiated closing transaction.)
let nodes_0_lock = nodes[0].node.id_to_peer.lock().unwrap();
assert_eq!(nodes_0_lock.len(), 1);
- assert!(nodes_0_lock.contains_key(channel_id));
+ assert!(nodes_0_lock.contains_key(&channel_id));
}
{
// kept in the `nodes[1]`'s `id_to_peer` map.
let nodes_1_lock = nodes[1].node.id_to_peer.lock().unwrap();
assert_eq!(nodes_1_lock.len(), 1);
- assert!(nodes_1_lock.contains_key(channel_id));
+ assert!(nodes_1_lock.contains_key(&channel_id));
}
nodes[0].node.handle_closing_signed(&nodes[1].node.get_our_node_id(), &get_event_msg!(nodes[1], MessageSendEvent::SendClosingSigned, nodes[0].node.get_our_node_id()));
// doesn't have `nodes[0]`'s signature for the closing transaction yet.
let nodes_1_lock = nodes[1].node.id_to_peer.lock().unwrap();
assert_eq!(nodes_1_lock.len(), 1);
- assert!(nodes_1_lock.contains_key(channel_id));
+ assert!(nodes_1_lock.contains_key(&channel_id));
}
let (_nodes_0_update, closing_signed_node_0) = get_closing_signed_broadcast!(nodes[0].node, nodes[1].node.get_our_node_id());
}
let (_nodes_1_update, _none) = get_closing_signed_broadcast!(nodes[1].node, nodes[0].node.get_our_node_id());
- check_closed_event!(nodes[0], 1, ClosureReason::CooperativeClosure);
- check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure);
+ check_closed_event!(nodes[0], 1, ClosureReason::CooperativeClosure, [nodes[1].node.get_our_node_id()], 1000000);
+ check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure, [nodes[0].node.get_our_node_id()], 1000000);
}
fn check_not_connected_to_peer_error<T>(res_err: Result<T, APIError>, expected_public_key: PublicKey) {
let nodes = create_network(2, &node_cfg, &node_chanmgr);
// Dummy values
- let channel_id = [4; 32];
+ let channel_id = ChannelId::from_bytes([4; 32]);
let unkown_public_key = PublicKey::from_secret_key(&Secp256k1::signing_only(), &SecretKey::from_slice(&[42; 32]).unwrap());
let intercept_id = InterceptId([0; 32]);
check_added_monitors!(nodes[0], 1);
expect_channel_pending_event(&nodes[0], &nodes[1].node.get_our_node_id());
}
- open_channel_msg.temporary_channel_id = nodes[0].keys_manager.get_secure_random_bytes();
+ open_channel_msg.temporary_channel_id = ChannelId::temporary_from_entropy_source(&nodes[0].keys_manager);
}
// A MAX_UNFUNDED_CHANS_PER_PEER + 1 channel will be summarily rejected
- open_channel_msg.temporary_channel_id = nodes[0].keys_manager.get_secure_random_bytes();
+ open_channel_msg.temporary_channel_id = ChannelId::temporary_from_entropy_source(&nodes[0].keys_manager);
nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &open_channel_msg);
assert_eq!(get_err_msg(&nodes[1], &nodes[0].node.get_our_node_id()).channel_id,
open_channel_msg.temporary_channel_id);
for i in 0..super::MAX_UNFUNDED_CHANNEL_PEERS - 1 {
nodes[1].node.handle_open_channel(&peer_pks[i], &open_channel_msg);
get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, peer_pks[i]);
- open_channel_msg.temporary_channel_id = nodes[0].keys_manager.get_secure_random_bytes();
+ open_channel_msg.temporary_channel_id = ChannelId::temporary_from_entropy_source(&nodes[0].keys_manager);
}
nodes[1].node.handle_open_channel(&last_random_pk, &open_channel_msg);
assert_eq!(get_err_msg(&nodes[1], &last_random_pk).channel_id,
for _ in 0..super::MAX_UNFUNDED_CHANS_PER_PEER {
nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &open_channel_msg);
get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, nodes[0].node.get_our_node_id());
- open_channel_msg.temporary_channel_id = nodes[0].keys_manager.get_secure_random_bytes();
+ open_channel_msg.temporary_channel_id = ChannelId::temporary_from_entropy_source(&nodes[0].keys_manager);
}
// Once we have MAX_UNFUNDED_CHANS_PER_PEER unfunded channels, new inbound channels will be
_ => panic!("Unexpected event"),
}
get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, random_pk);
- open_channel_msg.temporary_channel_id = nodes[0].keys_manager.get_secure_random_bytes();
+ open_channel_msg.temporary_channel_id = ChannelId::temporary_from_entropy_source(&nodes[0].keys_manager);
}
// If we try to accept a channel from another peer non-0conf it will fail.
let node = create_network(1, &node_cfg, &node_chanmgr);
let sender_intended_amt_msat = 100;
let extra_fee_msat = 10;
- let hop_data = msgs::OnionHopData {
- amt_to_forward: 100,
+ let hop_data = msgs::InboundOnionPayload::Receive {
+ amt_msat: 100,
outgoing_cltv_value: 42,
- format: msgs::OnionHopDataFormat::FinalNode {
- keysend_preimage: None,
- payment_metadata: None,
- payment_data: Some(msgs::FinalOnionHopData {
- payment_secret: PaymentSecret([0; 32]), total_msat: sender_intended_amt_msat,
- }),
- }
+ payment_metadata: None,
+ keysend_preimage: None,
+ payment_data: Some(msgs::FinalOnionHopData {
+ payment_secret: PaymentSecret([0; 32]), total_msat: sender_intended_amt_msat,
+ }),
+ custom_tlvs: Vec::new(),
};
// Check that if the amount we received + the penultimate hop extra fee is less than the sender
// intended amount, we fail the payment.
- if let Err(crate::ln::channelmanager::ReceiveError { err_code, .. }) =
+ if let Err(crate::ln::channelmanager::InboundOnionErr { err_code, .. }) =
node[0].node.construct_recv_pending_htlc_info(hop_data, [0; 32], PaymentHash([0; 32]),
sender_intended_amt_msat - extra_fee_msat - 1, 42, None, true, Some(extra_fee_msat))
{
} else { panic!(); }
// If amt_received + extra_fee is equal to the sender intended amount, we're fine.
- let hop_data = msgs::OnionHopData { // This is the same hop_data as above, OnionHopData doesn't implement Clone
- amt_to_forward: 100,
+ let hop_data = msgs::InboundOnionPayload::Receive { // This is the same payload as above, InboundOnionPayload doesn't implement Clone
+ amt_msat: 100,
outgoing_cltv_value: 42,
- format: msgs::OnionHopDataFormat::FinalNode {
- keysend_preimage: None,
- payment_metadata: None,
- payment_data: Some(msgs::FinalOnionHopData {
- payment_secret: PaymentSecret([0; 32]), total_msat: sender_intended_amt_msat,
- }),
- }
+ payment_metadata: None,
+ keysend_preimage: None,
+ payment_data: Some(msgs::FinalOnionHopData {
+ payment_secret: PaymentSecret([0; 32]), total_msat: sender_intended_amt_msat,
+ }),
+ custom_tlvs: Vec::new(),
};
assert!(node[0].node.construct_recv_pending_htlc_info(hop_data, [0; 32], PaymentHash([0; 32]),
sender_intended_amt_msat - extra_fee_msat, 42, None, true, Some(extra_fee_msat)).is_ok());
let open_channel_msg = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, nodes[1].node.get_our_node_id());
assert!(!open_channel_msg.channel_type.unwrap().supports_anchors_zero_fee_htlc_tx());
- check_closed_event!(nodes[1], 1, ClosureReason::HolderForceClosed);
+ // Since nodes[1] should not have accepted the channel, it should
+ // not have generated any events.
+ assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
}
#[test]
MessageSendEvent::BroadcastChannelUpdate { .. } => {},
_ => panic!("expected BroadcastChannelUpdate event"),
}
+
+ // If we provide a channel_id not associated with the peer, we should get an error and no updates
+ // should be applied to ensure update atomicity as specified in the API docs.
+ let bad_channel_id = ChannelId::v1_from_funding_txid(&[10; 32], 10);
+ let current_fee = nodes[0].node.list_channels()[0].config.unwrap().forwarding_fee_proportional_millionths;
+ let new_fee = current_fee + 100;
+ assert!(
+ matches!(
+ nodes[0].node.update_partial_channel_config(&channel.counterparty.node_id, &[channel.channel_id, bad_channel_id], &ChannelConfigUpdate {
+ forwarding_fee_proportional_millionths: Some(new_fee),
+ ..Default::default()
+ }),
+ Err(APIError::ChannelUnavailable { err: _ }),
+ )
+ );
+ // Check that the fee hasn't changed for the channel that exists.
+ assert_eq!(nodes[0].node.list_channels()[0].config.unwrap().forwarding_fee_proportional_millionths, current_fee);
+ let events = nodes[0].node.get_and_clear_pending_msg_events();
+ assert_eq!(events.len(), 0);
+ }
+
+ #[test]
+ fn test_payment_display() {
+ let payment_id = PaymentId([42; 32]);
+ assert_eq!(format!("{}", &payment_id), "2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a");
+ let payment_hash = PaymentHash([42; 32]);
+ assert_eq!(format!("{}", &payment_hash), "2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a");
+ let payment_preimage = PaymentPreimage([42; 32]);
+ assert_eq!(format!("{}", &payment_preimage), "2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a");
}
}
use crate::routing::gossip::NetworkGraph;
use crate::routing::router::{PaymentParameters, RouteParameters};
use crate::util::test_utils;
- use crate::util::config::UserConfig;
+ use crate::util::config::{UserConfig, MaxDustHTLCExposure};
use bitcoin::hashes::Hash;
use bitcoin::hashes::sha256::Hash as Sha256;
use bitcoin::{Block, BlockHeader, PackedLockTime, Transaction, TxMerkleNode, TxOut};
- use crate::sync::{Arc, Mutex};
+ use crate::sync::{Arc, Mutex, RwLock};
use criterion::Criterion;
&'a test_utils::TestFeeEstimator, &'a test_utils::TestRouter<'a>,
&'a test_utils::TestLogger>;
- struct ANodeHolder<'a, P: Persist<InMemorySigner>> {
- node: &'a Manager<'a, P>,
+ struct ANodeHolder<'node_cfg, 'chan_mon_cfg: 'node_cfg, P: Persist<InMemorySigner>> {
+ node: &'node_cfg Manager<'chan_mon_cfg, P>,
}
- impl<'a, P: Persist<InMemorySigner>> NodeHolder for ANodeHolder<'a, P> {
- type CM = Manager<'a, P>;
+ impl<'node_cfg, 'chan_mon_cfg: 'node_cfg, P: Persist<InMemorySigner>> NodeHolder for ANodeHolder<'node_cfg, 'chan_mon_cfg, P> {
+ type CM = Manager<'chan_mon_cfg, P>;
#[inline]
- fn node(&self) -> &Manager<'a, P> { self.node }
+ fn node(&self) -> &Manager<'chan_mon_cfg, P> { self.node }
#[inline]
fn chain_monitor(&self) -> Option<&test_utils::TestChainMonitor> { None }
}
let tx_broadcaster = test_utils::TestBroadcaster::new(network);
let fee_estimator = test_utils::TestFeeEstimator { sat_per_kw: Mutex::new(253) };
let logger_a = test_utils::TestLogger::with_id("node a".to_owned());
- let scorer = Mutex::new(test_utils::TestScorer::new());
+ let scorer = RwLock::new(test_utils::TestScorer::new());
let router = test_utils::TestRouter::new(Arc::new(NetworkGraph::new(network, &logger_a)), &scorer);
let mut config: UserConfig = Default::default();
+ config.channel_config.max_dust_htlc_exposure = MaxDustHTLCExposure::FeeRateMultiplier(5_000_000 / 253);
config.channel_handshake_config.minimum_depth = 1;
let chain_monitor_a = ChainMonitor::new(None, &tx_broadcaster, &logger_a, &fee_estimator, &persister_a);
let payment_secret = $node_b.create_inbound_payment_for_hash(payment_hash, None, 7200, None).unwrap();
$node_a.send_payment(payment_hash, RecipientOnionFields::secret_only(payment_secret),
- PaymentId(payment_hash.0), RouteParameters {
- payment_params, final_value_msat: 10_000,
- }, Retry::Attempts(0)).unwrap();
+ PaymentId(payment_hash.0),
+ RouteParameters::from_payment_params_and_value(payment_params, 10_000),
+ Retry::Attempts(0)).unwrap();
let payment_event = SendEvent::from_event($node_a.get_and_clear_pending_msg_events().pop().unwrap());
$node_b.handle_update_add_htlc(&$node_a.get_our_node_id(), &payment_event.msgs[0]);
$node_b.handle_commitment_signed(&$node_a.get_our_node_id(), &payment_event.commitment_msg);
// Byte 2
BasicMPP | Wumbo | AnchorsNonzeroFeeHtlcTx | AnchorsZeroFeeHtlcTx,
// Byte 3
- ShutdownAnySegwit,
+ ShutdownAnySegwit | Taproot,
// Byte 4
OnionMessages,
// Byte 5
// Byte 2
BasicMPP | Wumbo | AnchorsNonzeroFeeHtlcTx | AnchorsZeroFeeHtlcTx,
// Byte 3
- ShutdownAnySegwit,
+ ShutdownAnySegwit | Taproot,
// Byte 4
OnionMessages,
// Byte 5
ZeroConf | Keysend,
]);
define_context!(ChannelContext, []);
- define_context!(InvoiceContext, [
+ define_context!(Bolt11InvoiceContext, [
// Byte 0
,
// Byte 1
// Byte 2
AnchorsNonzeroFeeHtlcTx | AnchorsZeroFeeHtlcTx,
// Byte 3
- ,
+ Taproot,
// Byte 4
,
// Byte 5
define_feature!(7, GossipQueries, [InitContext, NodeContext],
"Feature flags for `gossip_queries`.", set_gossip_queries_optional, set_gossip_queries_required,
supports_gossip_queries, requires_gossip_queries);
- define_feature!(9, VariableLengthOnion, [InitContext, NodeContext, InvoiceContext],
+ define_feature!(9, VariableLengthOnion, [InitContext, NodeContext, Bolt11InvoiceContext],
"Feature flags for `var_onion_optin`.", set_variable_length_onion_optional,
set_variable_length_onion_required, supports_variable_length_onion,
requires_variable_length_onion);
define_feature!(13, StaticRemoteKey, [InitContext, NodeContext, ChannelTypeContext],
"Feature flags for `option_static_remotekey`.", set_static_remote_key_optional,
set_static_remote_key_required, supports_static_remote_key, requires_static_remote_key);
- define_feature!(15, PaymentSecret, [InitContext, NodeContext, InvoiceContext],
+ define_feature!(15, PaymentSecret, [InitContext, NodeContext, Bolt11InvoiceContext],
"Feature flags for `payment_secret`.", set_payment_secret_optional, set_payment_secret_required,
supports_payment_secret, requires_payment_secret);
- define_feature!(17, BasicMPP, [InitContext, NodeContext, InvoiceContext, Bolt12InvoiceContext],
+ define_feature!(17, BasicMPP, [InitContext, NodeContext, Bolt11InvoiceContext, Bolt12InvoiceContext],
"Feature flags for `basic_mpp`.", set_basic_mpp_optional, set_basic_mpp_required,
supports_basic_mpp, requires_basic_mpp);
define_feature!(19, Wumbo, [InitContext, NodeContext],
define_feature!(27, ShutdownAnySegwit, [InitContext, NodeContext],
"Feature flags for `opt_shutdown_anysegwit`.", set_shutdown_any_segwit_optional,
set_shutdown_any_segwit_required, supports_shutdown_anysegwit, requires_shutdown_anysegwit);
+ define_feature!(31, Taproot, [InitContext, NodeContext, ChannelTypeContext],
+ "Feature flags for `option_taproot`.", set_taproot_optional,
+ set_taproot_required, supports_taproot, requires_taproot);
define_feature!(39, OnionMessages, [InitContext, NodeContext],
"Feature flags for `option_onion_messages`.", set_onion_messages_optional,
set_onion_messages_required, supports_onion_messages, requires_onion_messages);
define_feature!(47, SCIDPrivacy, [InitContext, NodeContext, ChannelTypeContext],
"Feature flags for only forwarding with SCID aliasing. Called `option_scid_alias` in the BOLTs",
set_scid_privacy_optional, set_scid_privacy_required, supports_scid_privacy, requires_scid_privacy);
- define_feature!(49, PaymentMetadata, [InvoiceContext],
+ define_feature!(49, PaymentMetadata, [Bolt11InvoiceContext],
"Feature flags for payment metadata in invoices.", set_payment_metadata_optional,
set_payment_metadata_required, supports_payment_metadata, requires_payment_metadata);
define_feature!(51, ZeroConf, [InitContext, NodeContext, ChannelTypeContext],
#[cfg(test)]
define_feature!(123456789, UnknownFeature,
- [NodeContext, ChannelContext, InvoiceContext, OfferContext, InvoiceRequestContext, Bolt12InvoiceContext, BlindedHopContext],
+ [NodeContext, ChannelContext, Bolt11InvoiceContext, OfferContext, InvoiceRequestContext, Bolt12InvoiceContext, BlindedHopContext],
"Feature flags for an unknown feature used in testing.", set_unknown_feature_optional,
set_unknown_feature_required, supports_unknown_test_feature, requires_unknown_test_feature);
}
/// Features used within a `channel_announcement` message.
pub type ChannelFeatures = Features<sealed::ChannelContext>;
/// Features used within an invoice.
-pub type InvoiceFeatures = Features<sealed::InvoiceContext>;
+pub type Bolt11InvoiceFeatures = Features<sealed::Bolt11InvoiceContext>;
/// Features used within an `offer`.
pub type OfferFeatures = Features<sealed::OfferContext>;
/// Features used within an `invoice_request`.
}
}
-impl InvoiceFeatures {
- /// Converts `InvoiceFeatures` to `Features<C>`. Only known `InvoiceFeatures` relevant to
+impl Bolt11InvoiceFeatures {
+ /// Converts `Bolt11InvoiceFeatures` to `Features<C>`. Only known `Bolt11InvoiceFeatures` relevant to
/// context `C` are included in the result.
pub(crate) fn to_context<C: sealed::Context>(&self) -> Features<C> {
self.to_context_internal()
/// features (since they were not announced in a node announcement). However, keysend payments
/// don't have an invoice to pull the payee's features from, so this method is provided for use in
/// [`PaymentParameters::for_keysend`], thus omitting the need for payers to manually construct an
- /// `InvoiceFeatures` for [`find_route`].
+ /// `Bolt11InvoiceFeatures` for [`find_route`].
///
/// MPP keysend is not widely supported yet, so we parameterize support to allow the user to
/// choose whether their router should find multi-part routes.
///
/// [`PaymentParameters::for_keysend`]: crate::routing::router::PaymentParameters::for_keysend
/// [`find_route`]: crate::routing::router::find_route
- pub(crate) fn for_keysend(allow_mpp: bool) -> InvoiceFeatures {
- let mut res = InvoiceFeatures::empty();
+ pub(crate) fn for_keysend(allow_mpp: bool) -> Bolt11InvoiceFeatures {
+ let mut res = Bolt11InvoiceFeatures::empty();
res.set_variable_length_onion_optional();
if allow_mpp {
res.set_basic_mpp_optional();
}
impl Bolt12InvoiceFeatures {
- /// Converts `Bolt12InvoiceFeatures` to `Features<C>`. Only known `Bolt12InvoiceFeatures` relevant
- /// to context `C` are included in the result.
+ /// Converts [`Bolt12InvoiceFeatures`] to [`Features<C>`]. Only known [`Bolt12InvoiceFeatures`]
+ /// relevant to context `C` are included in the result.
pub(crate) fn to_context<C: sealed::Context>(&self) -> Features<C> {
self.to_context_internal()
}
}
}
-impl ToBase32 for InvoiceFeatures {
+impl ToBase32 for Bolt11InvoiceFeatures {
fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
// Explanation for the "4": the normal way to round up when dividing is to add the divisor
// minus one before dividing
}
}
-impl Base32Len for InvoiceFeatures {
+impl Base32Len for Bolt11InvoiceFeatures {
fn base32_len(&self) -> usize {
self.to_base32().len()
}
}
-impl FromBase32 for InvoiceFeatures {
+impl FromBase32 for Bolt11InvoiceFeatures {
type Err = bech32::Error;
- fn from_base32(field_data: &[u5]) -> Result<InvoiceFeatures, bech32::Error> {
+ fn from_base32(field_data: &[u5]) -> Result<Bolt11InvoiceFeatures, bech32::Error> {
// Explanation for the "7": the normal way to round up when dividing is to add the divisor
// minus one before dividing
let length_bytes = (field_data.len() * 5 + 7) / 8 as usize;
while !res_bytes.is_empty() && res_bytes[res_bytes.len() - 1] == 0 {
res_bytes.pop();
}
- Ok(InvoiceFeatures::from_le_bytes(res_bytes))
+ Ok(Bolt11InvoiceFeatures::from_le_bytes(res_bytes))
}
}
}
/// Returns true if this `Features` object contains required features unknown by `other`.
- pub fn requires_unknown_bits_from(&self, other: &Features<T>) -> bool {
+ pub fn requires_unknown_bits_from(&self, other: &Self) -> bool {
// Bitwise AND-ing with all even bits set except for known features will select required
// unknown features.
self.flags.iter().enumerate().any(|(i, &byte)| {
true
}
+ /// Sets a required feature bit. Errors if `bit` is outside the feature range as defined
+ /// by [BOLT 9].
+ ///
+ /// Note: Required bits are even. If an odd bit is given, then the corresponding even bit will
+ /// be set instead (i.e., `bit - 1`).
+ ///
+ /// [BOLT 9]: https://github.com/lightning/bolts/blob/master/09-features.md
+ pub fn set_required_feature_bit(&mut self, bit: usize) -> Result<(), ()> {
+ self.set_feature_bit(bit - (bit % 2))
+ }
+
+ /// Sets an optional feature bit. Errors if `bit` is outside the feature range as defined
+ /// by [BOLT 9].
+ ///
+ /// Note: Optional bits are odd. If an even bit is given, then the corresponding odd bit will be
+ /// set instead (i.e., `bit + 1`).
+ ///
+ /// [BOLT 9]: https://github.com/lightning/bolts/blob/master/09-features.md
+ pub fn set_optional_feature_bit(&mut self, bit: usize) -> Result<(), ()> {
+ self.set_feature_bit(bit + (1 - (bit % 2)))
+ }
+
+ fn set_feature_bit(&mut self, bit: usize) -> Result<(), ()> {
+ if bit > 255 {
+ return Err(());
+ }
+ self.set_bit(bit, false)
+ }
+
/// Sets a required custom feature bit. Errors if `bit` is outside the custom range as defined
/// by [bLIP 2] or if it is a known `T` feature.
///
if bit < 256 {
return Err(());
}
+ self.set_bit(bit, true)
+ }
+ fn set_bit(&mut self, bit: usize, custom: bool) -> Result<(), ()> {
let byte_offset = bit / 8;
let mask = 1 << (bit - 8 * byte_offset);
- if byte_offset < T::KNOWN_FEATURE_MASK.len() {
+ if byte_offset < T::KNOWN_FEATURE_MASK.len() && custom {
if (T::KNOWN_FEATURE_MASK[byte_offset] & mask) != 0 {
return Err(());
}
impl_feature_len_prefixed_write!(InitFeatures);
impl_feature_len_prefixed_write!(ChannelFeatures);
impl_feature_len_prefixed_write!(NodeFeatures);
-impl_feature_len_prefixed_write!(InvoiceFeatures);
+impl_feature_len_prefixed_write!(Bolt11InvoiceFeatures);
impl_feature_len_prefixed_write!(Bolt12InvoiceFeatures);
impl_feature_len_prefixed_write!(BlindedHopFeatures);
#[cfg(test)]
mod tests {
- use super::{ChannelFeatures, ChannelTypeFeatures, InitFeatures, InvoiceFeatures, NodeFeatures, OfferFeatures, sealed};
+ use super::{ChannelFeatures, ChannelTypeFeatures, InitFeatures, Bolt11InvoiceFeatures, NodeFeatures, OfferFeatures, sealed};
use bitcoin::bech32::{Base32Len, FromBase32, ToBase32, u5};
use crate::util::ser::{Readable, WithoutLength, Writeable};
fn convert_to_context_with_unknown_flags() {
// Ensure the `from` context has fewer known feature bytes than the `to` context.
assert!(<sealed::ChannelContext as sealed::Context>::KNOWN_FEATURE_MASK.len() <
- <sealed::InvoiceContext as sealed::Context>::KNOWN_FEATURE_MASK.len());
+ <sealed::Bolt11InvoiceContext as sealed::Context>::KNOWN_FEATURE_MASK.len());
let mut channel_features = ChannelFeatures::empty();
channel_features.set_unknown_feature_optional();
assert!(channel_features.supports_unknown_bits());
- let invoice_features: InvoiceFeatures = channel_features.to_context_internal();
+ let invoice_features: Bolt11InvoiceFeatures = channel_features.to_context_internal();
assert!(!invoice_features.supports_unknown_bits());
}
#[test]
fn set_feature_bits() {
- let mut features = InvoiceFeatures::empty();
+ let mut features = Bolt11InvoiceFeatures::empty();
features.set_basic_mpp_optional();
features.set_payment_secret_required();
assert!(features.supports_basic_mpp());
assert!(!features.requires_basic_mpp());
assert!(features.requires_payment_secret());
assert!(features.supports_payment_secret());
+
+ // Set flags manually
+ let mut features = NodeFeatures::empty();
+ assert!(features.set_optional_feature_bit(55).is_ok());
+ assert!(features.supports_keysend());
+ assert!(features.set_optional_feature_bit(255).is_ok());
+ assert!(features.set_required_feature_bit(256).is_err());
}
#[test]
fn set_custom_bits() {
- let mut features = InvoiceFeatures::empty();
+ let mut features = Bolt11InvoiceFeatures::empty();
features.set_variable_length_onion_optional();
assert_eq!(features.flags[1], 0b00000010);
assert_eq!(features.flags[31], 0b00000000);
assert_eq!(features.flags[32], 0b00000101);
- let known_bit = <sealed::InvoiceContext as sealed::PaymentSecret>::EVEN_BIT;
- let byte_offset = <sealed::InvoiceContext as sealed::PaymentSecret>::BYTE_OFFSET;
+ let known_bit = <sealed::Bolt11InvoiceContext as sealed::PaymentSecret>::EVEN_BIT;
+ let byte_offset = <sealed::Bolt11InvoiceContext as sealed::PaymentSecret>::BYTE_OFFSET;
assert_eq!(byte_offset, 1);
assert_eq!(features.flags[byte_offset], 0b00000010);
assert!(features.set_required_custom_bit(known_bit).is_err());
assert_eq!(features.flags[byte_offset], 0b00000010);
- let mut features = InvoiceFeatures::empty();
+ let mut features = Bolt11InvoiceFeatures::empty();
assert!(features.set_optional_custom_bit(256).is_ok());
assert!(features.set_optional_custom_bit(259).is_ok());
assert_eq!(features.flags[32], 0b00001010);
- let mut features = InvoiceFeatures::empty();
+ let mut features = Bolt11InvoiceFeatures::empty();
assert!(features.set_required_custom_bit(257).is_ok());
assert!(features.set_required_custom_bit(258).is_ok());
assert_eq!(features.flags[32], 0b00000101);
u5::try_from_u8(16).unwrap(),
u5::try_from_u8(1).unwrap(),
];
- let features = InvoiceFeatures::from_le_bytes(vec![1, 2, 3, 4, 5, 42, 100, 101]);
+ let features = Bolt11InvoiceFeatures::from_le_bytes(vec![1, 2, 3, 4, 5, 42, 100, 101]);
// Test length calculation.
assert_eq!(features.base32_len(), 13);
assert_eq!(features_as_u5s, features_serialized);
// Test deserialization.
- let features_deserialized = InvoiceFeatures::from_base32(&features_as_u5s).unwrap();
+ let features_deserialized = Bolt11InvoiceFeatures::from_base32(&features_as_u5s).unwrap();
assert_eq!(features, features_deserialized);
}
#[test]
fn test_channel_type_mapping() {
- // If we map an InvoiceFeatures with StaticRemoteKey optional, it should map into a
+ // If we map an Bolt11InvoiceFeatures with StaticRemoteKey optional, it should map into a
// required-StaticRemoteKey ChannelTypeFeatures.
let mut init_features = InitFeatures::empty();
init_features.set_static_remote_key_optional();
//! A bunch of useful utilities for building networks of nodes and exchanging messages between
//! nodes for functional tests.
-use crate::chain::{BestBlock, ChannelMonitorUpdateStatus, Confirm, Listen, Watch};
+use crate::chain::{BestBlock, ChannelMonitorUpdateStatus, Confirm, Listen, Watch, chainmonitor::Persist};
use crate::sign::EntropySource;
use crate::chain::channelmonitor::ChannelMonitor;
use crate::chain::transaction::OutPoint;
-use crate::events::{ClosureReason, Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider, PathFailure, PaymentPurpose, PaymentFailureReason};
-use crate::ln::{PaymentPreimage, PaymentHash, PaymentSecret};
+use crate::events::{ClaimedHTLC, ClosureReason, Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider, PathFailure, PaymentPurpose, PaymentFailureReason};
+use crate::events::bump_transaction::{BumpTransactionEventHandler, Wallet, WalletSource};
+use crate::ln::{ChannelId, PaymentPreimage, PaymentHash, PaymentSecret};
use crate::ln::channelmanager::{AChannelManager, ChainParameters, ChannelManager, ChannelManagerReadArgs, RAACommitmentOrder, PaymentSendFailure, RecipientOnionFields, PaymentId, MIN_CLTV_EXPIRY_DELTA};
use crate::routing::gossip::{P2PGossipSync, NetworkGraph, NetworkUpdate};
-use crate::routing::router::{self, PaymentParameters, Route};
+use crate::routing::router::{self, PaymentParameters, Route, RouteParameters};
use crate::ln::features::InitFeatures;
use crate::ln::msgs;
use crate::ln::msgs::{ChannelMessageHandler,RoutingMessageHandler};
-use crate::util::enforcing_trait_impls::EnforcingSigner;
+use crate::util::test_channel_signer::TestChannelSigner;
use crate::util::scid_utils;
use crate::util::test_utils;
use crate::util::test_utils::{panicking, TestChainMonitor, TestScorer, TestKeysInterface};
use crate::util::errors::APIError;
-use crate::util::config::UserConfig;
+use crate::util::config::{UserConfig, MaxDustHTLCExposure};
use crate::util::ser::{ReadableArgs, Writeable};
use bitcoin::blockdata::block::{Block, BlockHeader};
use bitcoin::blockdata::transaction::{Transaction, TxOut};
-use bitcoin::network::constants::Network;
-
use bitcoin::hash_types::BlockHash;
use bitcoin::hashes::sha256::Hash as Sha256;
use bitcoin::hashes::Hash as _;
-
-use bitcoin::secp256k1::PublicKey;
+use bitcoin::network::constants::Network;
+use bitcoin::secp256k1::{PublicKey, SecretKey};
use crate::io;
use crate::prelude::*;
use core::cell::RefCell;
use alloc::rc::Rc;
-use crate::sync::{Arc, Mutex, LockTestExt};
+use crate::sync::{Arc, Mutex, LockTestExt, RwLock};
use core::mem;
use core::iter::repeat;
-use bitcoin::{PackedLockTime, TxMerkleNode};
+use bitcoin::{PackedLockTime, TxIn, TxMerkleNode};
pub const CHAN_CONFIRM_DEPTH: u32 = 10;
let height = node.best_block_info().1 + 1;
confirm_transactions_at(node, txn, height);
}
+/// Mine a single block containing the given transaction without extra consistency checks which may
+/// impact ChannelManager state.
+pub fn mine_transaction_without_consistency_checks<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, tx: &Transaction) {
+ let height = node.best_block_info().1 + 1;
+ let mut block = Block {
+ header: BlockHeader { version: 0x20000000, prev_blockhash: node.best_block_hash(), merkle_root: TxMerkleNode::all_zeros(), time: height, bits: 42, nonce: 42 },
+ txdata: Vec::new(),
+ };
+ for _ in 0..*node.network_chan_count.borrow() { // Make sure we don't end up with channels at the same short id by offsetting by chan_count
+ block.txdata.push(Transaction { version: 0, lock_time: PackedLockTime::ZERO, input: Vec::new(), output: Vec::new() });
+ }
+ block.txdata.push((*tx).clone());
+ do_connect_block_without_consistency_checks(node, block, false);
+}
/// Mine the given transaction at the given height, mining blocks as required to build to that
/// height
///
assert!(depth >= 1);
for i in 1..depth {
let prev_blockhash = block.header.block_hash();
- do_connect_block(node, block, skip_intermediaries);
+ do_connect_block_with_consistency_checks(node, block, skip_intermediaries);
block = create_dummy_block(prev_blockhash, height + i, Vec::new());
}
let hash = block.header.block_hash();
- do_connect_block(node, block, false);
+ do_connect_block_with_consistency_checks(node, block, false);
hash
}
pub fn connect_block<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, block: &Block) {
- do_connect_block(node, block.clone(), false);
+ do_connect_block_with_consistency_checks(node, block.clone(), false);
}
fn call_claimable_balances<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>) {
}
}
-fn do_connect_block<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, block: Block, skip_intermediaries: bool) {
+fn do_connect_block_with_consistency_checks<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, block: Block, skip_intermediaries: bool) {
call_claimable_balances(node);
+ do_connect_block_without_consistency_checks(node, block, skip_intermediaries);
+ call_claimable_balances(node);
+ node.node.test_process_background_events();
+}
+
+fn do_connect_block_without_consistency_checks<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, block: Block, skip_intermediaries: bool) {
let height = node.best_block_info().1 + 1;
#[cfg(feature = "std")] {
eprintln!("Connecting block using Block Connection Style: {:?}", *node.connect_style.borrow());
}
}
}
- call_claimable_balances(node);
- node.node.test_process_background_events();
+
+ for tx in &block.txdata {
+ for input in &tx.input {
+ node.wallet_source.remove_utxo(input.previous_output);
+ }
+ let wallet_script = node.wallet_source.get_change_script().unwrap();
+ for (idx, output) in tx.output.iter().enumerate() {
+ if output.script_pubkey == wallet_script {
+ let outpoint = bitcoin::OutPoint { txid: tx.txid(), vout: idx as u32 };
+ node.wallet_source.add_utxo(outpoint, output.value);
+ }
+ }
+ }
}
pub fn disconnect_blocks<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, count: u32) {
pub persister: test_utils::TestPersister,
pub logger: test_utils::TestLogger,
pub keys_manager: test_utils::TestKeysInterface,
- pub scorer: Mutex<test_utils::TestScorer>,
+ pub scorer: RwLock<test_utils::TestScorer>,
}
pub struct NodeCfg<'a> {
pub override_init_features: Rc<RefCell<Option<InitFeatures>>>,
}
-type TestChannelManager<'a, 'b, 'c> = ChannelManager<&'b TestChainMonitor<'c>, &'c test_utils::TestBroadcaster, &'b test_utils::TestKeysInterface, &'b test_utils::TestKeysInterface, &'b test_utils::TestKeysInterface, &'c test_utils::TestFeeEstimator, &'b test_utils::TestRouter<'c>, &'c test_utils::TestLogger>;
-
-pub struct Node<'a, 'b: 'a, 'c: 'b> {
- pub chain_source: &'c test_utils::TestChainSource,
- pub tx_broadcaster: &'c test_utils::TestBroadcaster,
- pub fee_estimator: &'c test_utils::TestFeeEstimator,
- pub router: &'b test_utils::TestRouter<'c>,
- pub chain_monitor: &'b test_utils::TestChainMonitor<'c>,
- pub keys_manager: &'b test_utils::TestKeysInterface,
- pub node: &'a TestChannelManager<'a, 'b, 'c>,
- pub network_graph: &'a NetworkGraph<&'c test_utils::TestLogger>,
- pub gossip_sync: P2PGossipSync<&'b NetworkGraph<&'c test_utils::TestLogger>, &'c test_utils::TestChainSource, &'c test_utils::TestLogger>,
+type TestChannelManager<'node_cfg, 'chan_mon_cfg> = ChannelManager<
+ &'node_cfg TestChainMonitor<'chan_mon_cfg>,
+ &'chan_mon_cfg test_utils::TestBroadcaster,
+ &'node_cfg test_utils::TestKeysInterface,
+ &'node_cfg test_utils::TestKeysInterface,
+ &'node_cfg test_utils::TestKeysInterface,
+ &'chan_mon_cfg test_utils::TestFeeEstimator,
+ &'node_cfg test_utils::TestRouter<'chan_mon_cfg>,
+ &'chan_mon_cfg test_utils::TestLogger,
+>;
+
+pub struct Node<'chan_man, 'node_cfg: 'chan_man, 'chan_mon_cfg: 'node_cfg> {
+ pub chain_source: &'chan_mon_cfg test_utils::TestChainSource,
+ pub tx_broadcaster: &'chan_mon_cfg test_utils::TestBroadcaster,
+ pub fee_estimator: &'chan_mon_cfg test_utils::TestFeeEstimator,
+ pub router: &'node_cfg test_utils::TestRouter<'chan_mon_cfg>,
+ pub chain_monitor: &'node_cfg test_utils::TestChainMonitor<'chan_mon_cfg>,
+ pub keys_manager: &'chan_mon_cfg test_utils::TestKeysInterface,
+ pub node: &'chan_man TestChannelManager<'node_cfg, 'chan_mon_cfg>,
+ pub network_graph: &'node_cfg NetworkGraph<&'chan_mon_cfg test_utils::TestLogger>,
+ pub gossip_sync: P2PGossipSync<&'node_cfg NetworkGraph<&'chan_mon_cfg test_utils::TestLogger>, &'chan_mon_cfg test_utils::TestChainSource, &'chan_mon_cfg test_utils::TestLogger>,
pub node_seed: [u8; 32],
pub network_payment_count: Rc<RefCell<u8>>,
pub network_chan_count: Rc<RefCell<u32>>,
- pub logger: &'c test_utils::TestLogger,
+ pub logger: &'chan_mon_cfg test_utils::TestLogger,
pub blocks: Arc<Mutex<Vec<(Block, u32)>>>,
pub connect_style: Rc<RefCell<ConnectStyle>>,
pub override_init_features: Rc<RefCell<Option<InitFeatures>>>,
-}
+ pub wallet_source: Arc<test_utils::TestWalletSource>,
+ pub bump_tx_handler: BumpTransactionEventHandler<
+ &'chan_mon_cfg test_utils::TestBroadcaster,
+ Arc<Wallet<Arc<test_utils::TestWalletSource>, &'chan_mon_cfg test_utils::TestLogger>>,
+ &'chan_mon_cfg test_utils::TestKeysInterface,
+ &'chan_mon_cfg test_utils::TestLogger,
+ >,
+}
+#[cfg(feature = "std")]
+impl<'a, 'b, 'c> std::panic::UnwindSafe for Node<'a, 'b, 'c> {}
+#[cfg(feature = "std")]
+impl<'a, 'b, 'c> std::panic::RefUnwindSafe for Node<'a, 'b, 'c> {}
impl<'a, 'b, 'c> Node<'a, 'b, 'c> {
pub fn best_block_hash(&self) -> BlockHash {
self.blocks.lock().unwrap().last().unwrap().0.block_hash()
fn chain_monitor(&self) -> Option<&test_utils::TestChainMonitor> { (*self).chain_monitor() }
}
impl<'a, 'b: 'a, 'c: 'b> NodeHolder for Node<'a, 'b, 'c> {
- type CM = TestChannelManager<'a, 'b, 'c>;
- fn node(&self) -> &TestChannelManager<'a, 'b, 'c> { &self.node }
+ type CM = TestChannelManager<'b, 'c>;
+ fn node(&self) -> &TestChannelManager<'b, 'c> { &self.node }
fn chain_monitor(&self) -> Option<&test_utils::TestChainMonitor> { Some(self.chain_monitor) }
}
for outpoint in self.chain_monitor.chain_monitor.list_monitors() {
let mut w = test_utils::TestVecWriter(Vec::new());
self.chain_monitor.chain_monitor.get_monitor(outpoint).unwrap().write(&mut w).unwrap();
- let (_, deserialized_monitor) = <(BlockHash, ChannelMonitor<EnforcingSigner>)>::read(
+ let (_, deserialized_monitor) = <(BlockHash, ChannelMonitor<TestChannelSigner>)>::read(
&mut io::Cursor::new(&w.0), (self.keys_manager, self.keys_manager)).unwrap();
deserialized_monitors.push(deserialized_monitor);
}
channel_monitors.insert(monitor.get_funding_txo().0, monitor);
}
- let scorer = Mutex::new(test_utils::TestScorer::new());
+ let scorer = RwLock::new(test_utils::TestScorer::new());
let mut w = test_utils::TestVecWriter(Vec::new());
self.node.write(&mut w).unwrap();
<(BlockHash, ChannelManager<&test_utils::TestChainMonitor, &test_utils::TestBroadcaster, &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestFeeEstimator, &test_utils::TestRouter, &test_utils::TestLogger>)>::read(&mut io::Cursor::new(w.0), ChannelManagerReadArgs {
let chain_source = test_utils::TestChainSource::new(Network::Testnet);
let chain_monitor = test_utils::TestChainMonitor::new(Some(&chain_source), &broadcaster, &self.logger, &feeest, &persister, &self.keys_manager);
for deserialized_monitor in deserialized_monitors.drain(..) {
- if chain_monitor.watch_channel(deserialized_monitor.get_funding_txo().0, deserialized_monitor) != ChannelMonitorUpdateStatus::Completed {
+ if chain_monitor.watch_channel(deserialized_monitor.get_funding_txo().0, deserialized_monitor) != Ok(ChannelMonitorUpdateStatus::Completed) {
panic!();
}
}
}
}
-pub fn create_chan_between_nodes<'a, 'b, 'c, 'd>(node_a: &'a Node<'b, 'c, 'd>, node_b: &'a Node<'b, 'c, 'd>) -> (msgs::ChannelAnnouncement, msgs::ChannelUpdate, msgs::ChannelUpdate, [u8; 32], Transaction) {
+pub fn create_chan_between_nodes<'a, 'b, 'c: 'd, 'd>(node_a: &'a Node<'b, 'c, 'd>, node_b: &'a Node<'b, 'c, 'd>) -> (msgs::ChannelAnnouncement, msgs::ChannelUpdate, msgs::ChannelUpdate, ChannelId, Transaction) {
create_chan_between_nodes_with_value(node_a, node_b, 100000, 10001)
}
-pub fn create_chan_between_nodes_with_value<'a, 'b, 'c, 'd>(node_a: &'a Node<'b, 'c, 'd>, node_b: &'a Node<'b, 'c, 'd>, channel_value: u64, push_msat: u64) -> (msgs::ChannelAnnouncement, msgs::ChannelUpdate, msgs::ChannelUpdate, [u8; 32], Transaction) {
+pub fn create_chan_between_nodes_with_value<'a, 'b, 'c: 'd, 'd>(node_a: &'a Node<'b, 'c, 'd>, node_b: &'a Node<'b, 'c, 'd>, channel_value: u64, push_msat: u64) -> (msgs::ChannelAnnouncement, msgs::ChannelUpdate, msgs::ChannelUpdate, ChannelId, Transaction) {
let (channel_ready, channel_id, tx) = create_chan_between_nodes_with_value_a(node_a, node_b, channel_value, push_msat);
let (announcement, as_update, bs_update) = create_chan_between_nodes_with_value_b(node_a, node_b, &channel_ready);
(announcement, as_update, bs_update, channel_id, tx)
}
}
-#[cfg(test)]
-macro_rules! get_outbound_v1_channel_ref {
- ($node: expr, $counterparty_node: expr, $per_peer_state_lock: ident, $peer_state_lock: ident, $channel_id: expr) => {
- {
- $per_peer_state_lock = $node.node.per_peer_state.read().unwrap();
- $peer_state_lock = $per_peer_state_lock.get(&$counterparty_node.node.get_our_node_id()).unwrap().lock().unwrap();
- $peer_state_lock.outbound_v1_channel_by_id.get_mut(&$channel_id).unwrap()
- }
- }
-}
-
-#[cfg(test)]
-macro_rules! get_inbound_v1_channel_ref {
- ($node: expr, $counterparty_node: expr, $per_peer_state_lock: ident, $peer_state_lock: ident, $channel_id: expr) => {
- {
- $per_peer_state_lock = $node.node.per_peer_state.read().unwrap();
- $peer_state_lock = $per_peer_state_lock.get(&$counterparty_node.node.get_our_node_id()).unwrap().lock().unwrap();
- $peer_state_lock.inbound_v1_channel_by_id.get_mut(&$channel_id).unwrap()
- }
- }
-}
-
#[cfg(test)]
macro_rules! get_feerate {
($node: expr, $counterparty_node: expr, $channel_id: expr) => {
{
let mut per_peer_state_lock;
let mut peer_state_lock;
- let chan = get_channel_ref!($node, $counterparty_node, per_peer_state_lock, peer_state_lock, $channel_id);
- chan.context.get_feerate_sat_per_1000_weight()
+ let phase = get_channel_ref!($node, $counterparty_node, per_peer_state_lock, peer_state_lock, $channel_id);
+ phase.context().get_feerate_sat_per_1000_weight()
}
}
}
let mut per_peer_state_lock;
let mut peer_state_lock;
let chan = get_channel_ref!($node, $counterparty_node, per_peer_state_lock, peer_state_lock, $channel_id);
- chan.context.get_channel_type().clone()
+ chan.context().get_channel_type().clone()
}
}
}
for index in 0..2 {
if let Ok(mon) = $node.chain_monitor.chain_monitor.get_monitor(
$crate::chain::transaction::OutPoint {
- txid: bitcoin::Txid::from_slice(&$channel_id[..]).unwrap(), index
+ txid: bitcoin::Txid::from_slice(&$channel_id.0[..]).unwrap(), index
})
{
monitor = Some(mon);
}
}
-pub fn _reload_node<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, default_config: UserConfig, chanman_encoded: &[u8], monitors_encoded: &[&[u8]]) -> ChannelManager<&'b TestChainMonitor<'c>, &'c test_utils::TestBroadcaster, &'b test_utils::TestKeysInterface, &'b test_utils::TestKeysInterface, &'b test_utils::TestKeysInterface, &'c test_utils::TestFeeEstimator, &'b test_utils::TestRouter<'c>, &'c test_utils::TestLogger> {
+/// Checks whether the claimed HTLC for the specified path has the correct channel information.
+///
+/// This will panic if the path is empty, if the HTLC's channel ID is not actually a channel that
+/// connects the final two nodes in the path, or if the `user_channel_id` is incorrect.
+pub fn check_claimed_htlc_channel<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, path: &[&Node<'a, 'b, 'c>], htlc: &ClaimedHTLC) {
+ let mut nodes = path.iter().rev();
+ let dest = nodes.next().expect("path should have a destination").node;
+ let prev = nodes.next().unwrap_or(&origin_node).node;
+ let dest_channels = dest.list_channels();
+ let ch = dest_channels.iter().find(|ch| ch.channel_id == htlc.channel_id)
+ .expect("HTLC's channel should be one of destination node's channels");
+ assert_eq!(htlc.user_channel_id, ch.user_channel_id);
+ assert_eq!(ch.counterparty.node_id, prev.get_our_node_id());
+}
+
+pub fn _reload_node<'a, 'b, 'c>(node: &'a Node<'a, 'b, 'c>, default_config: UserConfig, chanman_encoded: &[u8], monitors_encoded: &[&[u8]]) -> TestChannelManager<'b, 'c> {
let mut monitors_read = Vec::with_capacity(monitors_encoded.len());
for encoded in monitors_encoded {
let mut monitor_read = &encoded[..];
- let (_, monitor) = <(BlockHash, ChannelMonitor<EnforcingSigner>)>
+ let (_, monitor) = <(BlockHash, ChannelMonitor<TestChannelSigner>)>
::read(&mut monitor_read, (node.keys_manager, node.keys_manager)).unwrap();
assert!(monitor_read.is_empty());
monitors_read.push(monitor);
for monitor in monitors_read.iter_mut() {
assert!(channel_monitors.insert(monitor.get_funding_txo().0, monitor).is_none());
}
- <(BlockHash, ChannelManager<&test_utils::TestChainMonitor, &test_utils::TestBroadcaster, &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestFeeEstimator, &test_utils::TestRouter, &test_utils::TestLogger>)>::read(&mut node_read, ChannelManagerReadArgs {
+ <(BlockHash, TestChannelManager<'b, 'c>)>::read(&mut node_read, ChannelManagerReadArgs {
default_config,
entropy_source: node.keys_manager,
node_signer: node.keys_manager,
for monitor in monitors_read.drain(..) {
assert_eq!(node.chain_monitor.watch_channel(monitor.get_funding_txo().0, monitor),
- ChannelMonitorUpdateStatus::Completed);
+ Ok(ChannelMonitorUpdateStatus::Completed));
check_added_monitors!(node, 1);
}
};
}
-pub fn create_funding_transaction<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, expected_counterparty_node_id: &PublicKey, expected_chan_value: u64, expected_user_chan_id: u128) -> ([u8; 32], Transaction, OutPoint) {
+pub fn create_funding_transaction<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>,
+ expected_counterparty_node_id: &PublicKey, expected_chan_value: u64, expected_user_chan_id: u128)
+ -> (ChannelId, Transaction, OutPoint)
+{
+ internal_create_funding_transaction(node, expected_counterparty_node_id, expected_chan_value, expected_user_chan_id, false)
+}
+
+pub fn create_coinbase_funding_transaction<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>,
+ expected_counterparty_node_id: &PublicKey, expected_chan_value: u64, expected_user_chan_id: u128)
+ -> (ChannelId, Transaction, OutPoint)
+{
+ internal_create_funding_transaction(node, expected_counterparty_node_id, expected_chan_value, expected_user_chan_id, true)
+}
+
+fn internal_create_funding_transaction<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>,
+ expected_counterparty_node_id: &PublicKey, expected_chan_value: u64, expected_user_chan_id: u128,
+ coinbase: bool) -> (ChannelId, Transaction, OutPoint) {
let chan_id = *node.network_chan_count.borrow();
let events = node.node.get_and_clear_pending_events();
assert_eq!(*channel_value_satoshis, expected_chan_value);
assert_eq!(user_channel_id, expected_user_chan_id);
- let tx = Transaction { version: chan_id as i32, lock_time: PackedLockTime::ZERO, input: Vec::new(), output: vec![TxOut {
+ let input = if coinbase {
+ vec![TxIn {
+ previous_output: bitcoin::OutPoint::null(),
+ ..Default::default()
+ }]
+ } else {
+ Vec::new()
+ };
+
+ let tx = Transaction { version: chan_id as i32, lock_time: PackedLockTime::ZERO, input, output: vec![TxOut {
value: *channel_value_satoshis, script_pubkey: output_script.clone(),
}]};
let funding_outpoint = OutPoint { txid: tx.txid(), index: 0 };
_ => panic!("Unexpected event"),
}
}
-pub fn sign_funding_transaction<'a, 'b, 'c>(node_a: &Node<'a, 'b, 'c>, node_b: &Node<'a, 'b, 'c>, channel_value: u64, expected_temporary_channel_id: [u8; 32]) -> Transaction {
+
+pub fn sign_funding_transaction<'a, 'b, 'c>(node_a: &Node<'a, 'b, 'c>, node_b: &Node<'a, 'b, 'c>, channel_value: u64, expected_temporary_channel_id: ChannelId) -> Transaction {
let (temporary_channel_id, tx, funding_output) = create_funding_transaction(node_a, &node_b.node.get_our_node_id(), channel_value, 42);
assert_eq!(temporary_channel_id, expected_temporary_channel_id);
}
// Receiver must have been initialized with manually_accept_inbound_channels set to true.
-pub fn open_zero_conf_channel<'a, 'b, 'c, 'd>(initiator: &'a Node<'b, 'c, 'd>, receiver: &'a Node<'b, 'c, 'd>, initiator_config: Option<UserConfig>) -> (bitcoin::Transaction, [u8; 32]) {
+pub fn open_zero_conf_channel<'a, 'b, 'c, 'd>(initiator: &'a Node<'b, 'c, 'd>, receiver: &'a Node<'b, 'c, 'd>, initiator_config: Option<UserConfig>) -> (bitcoin::Transaction, ChannelId) {
let initiator_channels = initiator.node.list_usable_channels().len();
let receiver_channels = receiver.node.list_usable_channels().len();
node_recv.node.handle_channel_ready(&node_conf.node.get_our_node_id(), &get_event_msg!(node_conf, MessageSendEvent::SendChannelReady, node_recv.node.get_our_node_id()));
}
-pub fn create_chan_between_nodes_with_value_confirm_second<'a, 'b, 'c>(node_recv: &Node<'a, 'b, 'c>, node_conf: &Node<'a, 'b, 'c>) -> ((msgs::ChannelReady, msgs::AnnouncementSignatures), [u8; 32]) {
+pub fn create_chan_between_nodes_with_value_confirm_second<'a, 'b, 'c>(node_recv: &Node<'a, 'b, 'c>, node_conf: &Node<'a, 'b, 'c>) -> ((msgs::ChannelReady, msgs::AnnouncementSignatures), ChannelId) {
let channel_id;
let events_6 = node_conf.node.get_and_clear_pending_msg_events();
assert_eq!(events_6.len(), 3);
}), channel_id)
}
-pub fn create_chan_between_nodes_with_value_confirm<'a, 'b, 'c, 'd>(node_a: &'a Node<'b, 'c, 'd>, node_b: &'a Node<'b, 'c, 'd>, tx: &Transaction) -> ((msgs::ChannelReady, msgs::AnnouncementSignatures), [u8; 32]) {
+pub fn create_chan_between_nodes_with_value_confirm<'a, 'b, 'c: 'd, 'd>(node_a: &'a Node<'b, 'c, 'd>, node_b: &'a Node<'b, 'c, 'd>, tx: &Transaction) -> ((msgs::ChannelReady, msgs::AnnouncementSignatures), ChannelId) {
let conf_height = core::cmp::max(node_a.best_block_info().1 + 1, node_b.best_block_info().1 + 1);
create_chan_between_nodes_with_value_confirm_first(node_a, node_b, tx, conf_height);
confirm_transaction_at(node_a, tx, conf_height);
create_chan_between_nodes_with_value_confirm_second(node_b, node_a)
}
-pub fn create_chan_between_nodes_with_value_a<'a, 'b, 'c, 'd>(node_a: &'a Node<'b, 'c, 'd>, node_b: &'a Node<'b, 'c, 'd>, channel_value: u64, push_msat: u64) -> ((msgs::ChannelReady, msgs::AnnouncementSignatures), [u8; 32], Transaction) {
+pub fn create_chan_between_nodes_with_value_a<'a, 'b, 'c: 'd, 'd>(node_a: &'a Node<'b, 'c, 'd>, node_b: &'a Node<'b, 'c, 'd>, channel_value: u64, push_msat: u64) -> ((msgs::ChannelReady, msgs::AnnouncementSignatures), ChannelId, Transaction) {
let tx = create_chan_between_nodes_with_value_init(node_a, node_b, channel_value, push_msat);
let (msgs, chan_id) = create_chan_between_nodes_with_value_confirm(node_a, node_b, &tx);
(msgs, chan_id, tx)
((*announcement).clone(), as_update, bs_update)
}
-pub fn create_announced_chan_between_nodes<'a, 'b, 'c, 'd>(nodes: &'a Vec<Node<'b, 'c, 'd>>, a: usize, b: usize) -> (msgs::ChannelUpdate, msgs::ChannelUpdate, [u8; 32], Transaction) {
+pub fn create_announced_chan_between_nodes<'a, 'b, 'c: 'd, 'd>(nodes: &'a Vec<Node<'b, 'c, 'd>>, a: usize, b: usize) -> (msgs::ChannelUpdate, msgs::ChannelUpdate, ChannelId, Transaction) {
create_announced_chan_between_nodes_with_value(nodes, a, b, 100000, 10001)
}
-pub fn create_announced_chan_between_nodes_with_value<'a, 'b, 'c, 'd>(nodes: &'a Vec<Node<'b, 'c, 'd>>, a: usize, b: usize, channel_value: u64, push_msat: u64) -> (msgs::ChannelUpdate, msgs::ChannelUpdate, [u8; 32], Transaction) {
+pub fn create_announced_chan_between_nodes_with_value<'a, 'b, 'c: 'd, 'd>(nodes: &'a Vec<Node<'b, 'c, 'd>>, a: usize, b: usize, channel_value: u64, push_msat: u64) -> (msgs::ChannelUpdate, msgs::ChannelUpdate, ChannelId, Transaction) {
let chan_announcement = create_chan_between_nodes_with_value(&nodes[a], &nodes[b], channel_value, push_msat);
update_nodes_with_chan_announce(nodes, a, b, &chan_announcement.0, &chan_announcement.1, &chan_announcement.2);
(chan_announcement.1, chan_announcement.2, chan_announcement.3, chan_announcement.4)
}
/// 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) {
+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, .. } => {
+ 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;
/// Don't use this, use the identically-named function instead.
#[macro_export]
macro_rules! check_closed_event {
- ($node: expr, $events: expr, $reason: expr) => {
- check_closed_event!($node, $events, $reason, false);
+ ($node: expr, $events: expr, $reason: expr, $counterparty_node_ids: expr, $channel_capacity: expr) => {
+ check_closed_event!($node, $events, $reason, false, $counterparty_node_ids, $channel_capacity);
};
- ($node: expr, $events: expr, $reason: expr, $is_check_discard_funding: expr) => {
- $crate::ln::functional_test_utils::check_closed_event(&$node, $events, $reason, $is_check_discard_funding);
+ ($node: expr, $events: expr, $reason: expr, $is_check_discard_funding: expr, $counterparty_node_ids: expr, $channel_capacity: expr) => {
+ $crate::ln::functional_test_utils::check_closed_event(&$node, $events, $reason,
+ $is_check_discard_funding, &$counterparty_node_ids, $channel_capacity);
}
}
-pub fn close_channel<'a, 'b, 'c>(outbound_node: &Node<'a, 'b, 'c>, inbound_node: &Node<'a, 'b, 'c>, channel_id: &[u8; 32], funding_tx: Transaction, close_inbound_first: bool) -> (msgs::ChannelUpdate, msgs::ChannelUpdate, Transaction) {
+pub fn close_channel<'a, 'b, 'c>(outbound_node: &Node<'a, 'b, 'c>, inbound_node: &Node<'a, 'b, 'c>, channel_id: &ChannelId, funding_tx: Transaction, close_inbound_first: bool) -> (msgs::ChannelUpdate, msgs::ChannelUpdate, Transaction) {
let (node_a, broadcaster_a, struct_a) = if close_inbound_first { (&inbound_node.node, &inbound_node.tx_broadcaster, inbound_node) } else { (&outbound_node.node, &outbound_node.tx_broadcaster, outbound_node) };
let (node_b, broadcaster_b, struct_b) = if close_inbound_first { (&outbound_node.node, &outbound_node.tx_broadcaster, outbound_node) } else { (&inbound_node.node, &inbound_node.tx_broadcaster, inbound_node) };
let (tx_a, tx_b);
bs_revoke_and_ack
}
};
- ($node_a: expr, $node_b: expr, (), $fail_backwards: expr, true /* skip last step */, false /* no extra message */) => {
- assert!($crate::ln::functional_test_utils::commitment_signed_dance_through_cp_raa(&$node_a, &$node_b, $fail_backwards).is_none());
+ ($node_a: expr, $node_b: expr, (), $fail_backwards: expr, true /* skip last step */, false /* no extra message */, $incl_claim: expr) => {
+ assert!($crate::ln::functional_test_utils::commitment_signed_dance_through_cp_raa(&$node_a, &$node_b, $fail_backwards, $incl_claim).is_none());
};
($node_a: expr, $node_b: expr, $commitment_signed: expr, $fail_backwards: expr) => {
$crate::ln::functional_test_utils::do_commitment_signed_dance(&$node_a, &$node_b, &$commitment_signed, $fail_backwards, false);
/// the initiator's `revoke_and_ack` response. i.e. [`do_main_commitment_signed_dance`] plus the
/// `revoke_and_ack` response to it.
///
+/// An HTLC claim on one channel blocks the RAA channel monitor update for the outbound edge
+/// channel until the inbound edge channel preimage monitor update completes. Thus, when checking
+/// for channel monitor updates, we need to know if an `update_fulfill_htlc` was included in the
+/// the commitment we're exchanging. `includes_claim` provides that information.
+///
/// Returns any additional message `node_b` generated in addition to the `revoke_and_ack` response.
-pub fn commitment_signed_dance_through_cp_raa(node_a: &Node<'_, '_, '_>, node_b: &Node<'_, '_, '_>, fail_backwards: bool) -> Option<MessageSendEvent> {
+pub fn commitment_signed_dance_through_cp_raa(node_a: &Node<'_, '_, '_>, node_b: &Node<'_, '_, '_>, fail_backwards: bool, includes_claim: bool) -> Option<MessageSendEvent> {
let (extra_msg_option, bs_revoke_and_ack) = do_main_commitment_signed_dance(node_a, node_b, fail_backwards);
node_a.node.handle_revoke_and_ack(&node_b.node.get_our_node_id(), &bs_revoke_and_ack);
- check_added_monitors(node_a, 1);
+ check_added_monitors(node_a, if includes_claim { 0 } else { 1 });
extra_msg_option
}
node_a.node.handle_commitment_signed(&node_b.node.get_our_node_id(), commitment_signed);
check_added_monitors!(node_a, 1);
- commitment_signed_dance!(node_a, node_b, (), fail_backwards, true, false);
+ // If this commitment signed dance was due to a claim, don't check for an RAA monitor update.
+ let got_claim = node_a.node.test_raa_monitor_updates_held(node_b.node.get_our_node_id(), commitment_signed.channel_id);
+ if fail_backwards { assert!(!got_claim); }
+ commitment_signed_dance!(node_a, node_b, (), fail_backwards, true, false, got_claim);
if skip_last_step { return; }
}
/// Gets a route from the given sender to the node described in `payment_params`.
-pub fn get_route(send_node: &Node, payment_params: &PaymentParameters, recv_value: u64) -> Result<Route, msgs::LightningError> {
+pub fn get_route(send_node: &Node, route_params: &RouteParameters) -> Result<Route, msgs::LightningError> {
let scorer = TestScorer::new();
let keys_manager = TestKeysInterface::new(&[0u8; 32], bitcoin::network::constants::Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
router::get_route(
- &send_node.node.get_our_node_id(), payment_params, &send_node.network_graph.read_only(),
+ &send_node.node.get_our_node_id(), route_params, &send_node.network_graph.read_only(),
Some(&send_node.node.list_usable_channels().iter().collect::<Vec<_>>()),
- recv_value, send_node.logger, &scorer, &(), &random_seed_bytes
+ send_node.logger, &scorer, &Default::default(), &random_seed_bytes
)
}
/// Don't use this, use the identically-named function instead.
#[macro_export]
macro_rules! get_route {
- ($send_node: expr, $payment_params: expr, $recv_value: expr) => {
- $crate::ln::functional_test_utils::get_route(&$send_node, &$payment_params, $recv_value)
- }
+ ($send_node: expr, $payment_params: expr, $recv_value: expr) => {{
+ let route_params = $crate::routing::router::RouteParameters::from_payment_params_and_value($payment_params, $recv_value);
+ $crate::ln::functional_test_utils::get_route(&$send_node, &route_params)
+ }}
}
#[cfg(test)]
$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) => {{
+ $crate::get_route_and_payment_hash!($send_node, $recv_node, $payment_params, $recv_value, None)
+ }};
+ ($send_node: expr, $recv_node: expr, $payment_params: expr, $recv_value: expr, $max_total_routing_fee_msat: expr) => {{
+ let mut route_params = $crate::routing::router::RouteParameters::from_payment_params_and_value($payment_params, $recv_value);
+ route_params.max_total_routing_fee_msat = $max_total_routing_fee_msat;
let (payment_preimage, payment_hash, payment_secret) =
$crate::ln::functional_test_utils::get_payment_preimage_hash(&$recv_node, Some($recv_value), None);
- let route = $crate::ln::functional_test_utils::get_route(&$send_node, &$payment_params, $recv_value);
+ let route = $crate::ln::functional_test_utils::get_route(&$send_node, &route_params);
(route.unwrap(), payment_hash, payment_preimage, payment_secret)
}}
}
+pub fn check_payment_claimable(
+ event: &Event, expected_payment_hash: PaymentHash, expected_payment_secret: PaymentSecret,
+ expected_recv_value: u64, expected_payment_preimage: Option<PaymentPreimage>,
+ expected_receiver_node_id: PublicKey,
+) {
+ match event {
+ Event::PaymentClaimable { ref payment_hash, ref purpose, amount_msat, receiver_node_id, .. } => {
+ assert_eq!(expected_payment_hash, *payment_hash);
+ assert_eq!(expected_recv_value, *amount_msat);
+ assert_eq!(expected_receiver_node_id, receiver_node_id.unwrap());
+ match purpose {
+ PaymentPurpose::InvoicePayment { payment_preimage, payment_secret, .. } => {
+ assert_eq!(&expected_payment_preimage, payment_preimage);
+ assert_eq!(expected_payment_secret, *payment_secret);
+ },
+ _ => {},
+ }
+ },
+ _ => panic!("Unexpected event"),
+ }
+}
+
#[macro_export]
#[cfg(any(test, ldk_bench, feature = "_test_utils"))]
macro_rules! expect_payment_claimable {
($node: expr, $expected_payment_hash: expr, $expected_payment_secret: expr, $expected_recv_value: expr, $expected_payment_preimage: expr, $expected_receiver_node_id: expr) => {
let events = $node.node.get_and_clear_pending_events();
assert_eq!(events.len(), 1);
- match events[0] {
- $crate::events::Event::PaymentClaimable { ref payment_hash, ref purpose, amount_msat, receiver_node_id, .. } => {
- assert_eq!($expected_payment_hash, *payment_hash);
- assert_eq!($expected_recv_value, amount_msat);
- assert_eq!($expected_receiver_node_id, receiver_node_id.unwrap());
- match purpose {
- $crate::events::PaymentPurpose::InvoicePayment { payment_preimage, payment_secret, .. } => {
- assert_eq!(&$expected_payment_preimage, payment_preimage);
- assert_eq!($expected_payment_secret, *payment_secret);
- },
- _ => {},
- }
- },
- _ => panic!("Unexpected event"),
- }
- }
+ $crate::ln::functional_test_utils::check_payment_claimable(&events[0], $expected_payment_hash, $expected_payment_secret, $expected_recv_value, $expected_payment_preimage, $expected_receiver_node_id)
+ };
}
#[macro_export]
pub fn expect_payment_sent<CM: AChannelManager, H: NodeHolder<CM=CM>>(node: &H,
expected_payment_preimage: PaymentPreimage, expected_fee_msat_opt: Option<Option<u64>>,
- expect_per_path_claims: bool,
+ expect_per_path_claims: bool, expect_post_ev_mon_update: bool,
) {
let events = node.node().get_and_clear_pending_events();
let expected_payment_hash = PaymentHash(
} else {
assert_eq!(events.len(), 1);
}
+ if expect_post_ev_mon_update {
+ check_added_monitors(node, 1);
+ }
let expected_payment_id = match events[0] {
Event::PaymentSent { ref payment_id, ref payment_preimage, ref payment_hash, ref fee_paid_msat } => {
assert_eq!(expected_payment_preimage, *payment_preimage);
}
}
-#[cfg(test)]
-#[macro_export]
-macro_rules! expect_payment_sent_without_paths {
- ($node: expr, $expected_payment_preimage: expr) => {
- expect_payment_sent!($node, $expected_payment_preimage, None::<u64>, false);
- };
- ($node: expr, $expected_payment_preimage: expr, $expected_fee_msat_opt: expr) => {
- expect_payment_sent!($node, $expected_payment_preimage, $expected_fee_msat_opt, false);
- }
-}
-
#[macro_export]
macro_rules! expect_payment_sent {
($node: expr, $expected_payment_preimage: expr) => {
};
($node: expr, $expected_payment_preimage: expr, $expected_fee_msat_opt: expr, $expect_paths: expr) => {
$crate::ln::functional_test_utils::expect_payment_sent(&$node, $expected_payment_preimage,
- $expected_fee_msat_opt.map(|o| Some(o)), $expect_paths);
+ $expected_fee_msat_opt.map(|o| Some(o)), $expect_paths, true);
}
}
}
}
+pub fn expect_payment_forwarded<CM: AChannelManager, H: NodeHolder<CM=CM>>(
+ event: Event, node: &H, prev_node: &H, next_node: &H, expected_fee: Option<u64>,
+ upstream_force_closed: bool, downstream_force_closed: bool
+) {
+ match event {
+ Event::PaymentForwarded {
+ fee_earned_msat, prev_channel_id, claim_from_onchain_tx, next_channel_id,
+ outbound_amount_forwarded_msat: _
+ } => {
+ assert_eq!(fee_earned_msat, expected_fee);
+ if !upstream_force_closed {
+ // Is the event prev_channel_id in one of the channels between the two nodes?
+ assert!(node.node().list_channels().iter().any(|x| x.counterparty.node_id == prev_node.node().get_our_node_id() && x.channel_id == prev_channel_id.unwrap()));
+ }
+ // We check for force closures since a force closed channel is removed from the
+ // node's channel list
+ if !downstream_force_closed {
+ assert!(node.node().list_channels().iter().any(|x| x.counterparty.node_id == next_node.node().get_our_node_id() && x.channel_id == next_channel_id.unwrap()));
+ }
+ assert_eq!(claim_from_onchain_tx, downstream_force_closed);
+ },
+ _ => panic!("Unexpected event"),
+ }
+}
+
macro_rules! expect_payment_forwarded {
($node: expr, $prev_node: expr, $next_node: expr, $expected_fee: expr, $upstream_force_closed: expr, $downstream_force_closed: expr) => {
- let events = $node.node.get_and_clear_pending_events();
+ let mut events = $node.node.get_and_clear_pending_events();
assert_eq!(events.len(), 1);
- match events[0] {
- Event::PaymentForwarded {
- fee_earned_msat, prev_channel_id, claim_from_onchain_tx, next_channel_id,
- outbound_amount_forwarded_msat: _
- } => {
- assert_eq!(fee_earned_msat, $expected_fee);
- if fee_earned_msat.is_some() {
- // Is the event prev_channel_id in one of the channels between the two nodes?
- assert!($node.node.list_channels().iter().any(|x| x.counterparty.node_id == $prev_node.node.get_our_node_id() && x.channel_id == prev_channel_id.unwrap()));
- }
- // We check for force closures since a force closed channel is removed from the
- // node's channel list
- if !$downstream_force_closed {
- assert!($node.node.list_channels().iter().any(|x| x.counterparty.node_id == $next_node.node.get_our_node_id() && x.channel_id == next_channel_id.unwrap()));
- }
- assert_eq!(claim_from_onchain_tx, $downstream_force_closed);
- },
- _ => panic!("Unexpected event"),
- }
+ $crate::ln::functional_test_utils::expect_payment_forwarded(
+ events.pop().unwrap(), &$node, &$prev_node, &$next_node, $expected_fee,
+ $upstream_force_closed, $downstream_force_closed);
}
}
assert_eq!(path.last().unwrap().node.get_our_node_id(), expected_paths[0].last().unwrap().node.get_our_node_id());
}
expected_paths[0].last().unwrap().node.claim_funds(our_payment_preimage);
+ pass_claimed_payment_along_route(origin_node, expected_paths, expected_extra_fees, skip_last, our_payment_preimage)
+}
+pub fn pass_claimed_payment_along_route<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_paths: &[&[&Node<'a, 'b, 'c>]], expected_extra_fees: &[u32], skip_last: bool, our_payment_preimage: PaymentPreimage) -> u64 {
let claim_event = expected_paths[0].last().unwrap().node.get_and_clear_pending_events();
assert_eq!(claim_event.len(), 1);
match claim_event[0] {
- Event::PaymentClaimed { purpose: PaymentPurpose::SpontaneousPayment(preimage), .. }|
- Event::PaymentClaimed { purpose: PaymentPurpose::InvoicePayment { payment_preimage: Some(preimage), ..}, .. } =>
- assert_eq!(preimage, our_payment_preimage),
- Event::PaymentClaimed { purpose: PaymentPurpose::InvoicePayment { .. }, payment_hash, .. } =>
- assert_eq!(&payment_hash.0, &Sha256::hash(&our_payment_preimage.0)[..]),
+ Event::PaymentClaimed {
+ purpose: PaymentPurpose::SpontaneousPayment(preimage),
+ amount_msat,
+ ref htlcs,
+ .. }
+ | Event::PaymentClaimed {
+ purpose: PaymentPurpose::InvoicePayment { payment_preimage: Some(preimage), ..},
+ ref htlcs,
+ amount_msat,
+ ..
+ } => {
+ assert_eq!(preimage, our_payment_preimage);
+ assert_eq!(htlcs.len(), expected_paths.len()); // One per path.
+ assert_eq!(htlcs.iter().map(|h| h.value_msat).sum::<u64>(), amount_msat);
+ expected_paths.iter().zip(htlcs).for_each(|(path, htlc)| check_claimed_htlc_channel(origin_node, path, htlc));
+ },
+ Event::PaymentClaimed {
+ purpose: PaymentPurpose::InvoicePayment { .. },
+ payment_hash,
+ amount_msat,
+ ref htlcs,
+ ..
+ } => {
+ assert_eq!(&payment_hash.0, &Sha256::hash(&our_payment_preimage.0)[..]);
+ assert_eq!(htlcs.len(), expected_paths.len()); // One per path.
+ assert_eq!(htlcs.iter().map(|h| h.value_msat).sum::<u64>(), amount_msat);
+ expected_paths.iter().zip(htlcs).for_each(|(path, htlc)| check_claimed_htlc_channel(origin_node, path, htlc));
+ }
_ => panic!(),
}
let peer_state = per_peer_state.get(&$prev_node.node.get_our_node_id())
.unwrap().lock().unwrap();
let channel = peer_state.channel_by_id.get(&next_msgs.as_ref().unwrap().0.channel_id).unwrap();
- if let Some(prev_config) = channel.context.prev_config() {
+ if let Some(prev_config) = channel.context().prev_config() {
prev_config.forwarding_fee_base_msat
} else {
- channel.context.config().forwarding_fee_base_msat
+ channel.context().config().forwarding_fee_base_msat
}
};
if $idx == 1 { fee += expected_extra_fees[i]; }
- expect_payment_forwarded!($node, $next_node, $prev_node, Some(fee as u64), false, false);
+ expect_payment_forwarded!(*$node, $next_node, $prev_node, Some(fee as u64), false, false);
expected_total_fee_msat += fee as u64;
check_added_monitors!($node, 1);
let new_next_msgs = if $new_msgs {
pub const TEST_FINAL_CLTV: u32 = 70;
-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) {
+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();
- let route = get_route(origin_node, &payment_params, recv_value).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);
assert_eq!(route.paths[0].hops.len(), expected_route.len());
for (node, hop) in expected_route.iter().zip(route.paths[0].hops.iter()) {
}
let res = send_along_route(origin_node, route, expected_route, recv_value);
- (res.0, res.1, res.2)
+ (res.0, res.1, res.2, res.3)
}
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();
+ 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();
let seed = [0u8; 32];
let keys_manager = test_utils::TestKeysInterface::new(&seed, Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
- let route = router::get_route(
- &origin_node.node.get_our_node_id(), &payment_params, &network_graph,
- None, recv_value, origin_node.logger, &scorer, &(), &random_seed_bytes).unwrap();
+ let route = router::get_route(&origin_node.node.get_our_node_id(), &route_params, &network_graph,
+ None, origin_node.logger, &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
assert_eq!(route.paths[0].hops.len(), expected_route.len());
for (node, hop) in expected_route.iter().zip(route.paths[0].hops.iter()) {
assert!(err.contains("Cannot send value that would put us over the max HTLC value in flight our peer will accept")));
}
-pub fn send_payment<'a, 'b, 'c>(origin: &Node<'a, 'b, 'c>, expected_route: &[&Node<'a, 'b, 'c>], recv_value: u64) -> (PaymentPreimage, PaymentHash, PaymentSecret) {
+pub fn send_payment<'a, 'b, 'c>(origin: &Node<'a, 'b, 'c>, expected_route: &[&Node<'a, 'b, 'c>], recv_value: u64) -> (PaymentPreimage, PaymentHash, PaymentSecret, PaymentId) {
let res = route_payment(&origin, expected_route, recv_value);
claim_payment(&origin, expected_route, res.0);
res
let persister = test_utils::TestPersister::new();
let seed = [i as u8; 32];
let keys_manager = test_utils::TestKeysInterface::new(&seed, Network::Testnet);
- let scorer = Mutex::new(test_utils::TestScorer::new());
+ let scorer = RwLock::new(test_utils::TestScorer::new());
chan_mon_cfgs.push(TestChanMonCfg { tx_broadcaster, fee_estimator, chain_source, logger, persister, keys_manager, scorer });
}
}
pub fn create_node_cfgs<'a>(node_count: usize, chanmon_cfgs: &'a Vec<TestChanMonCfg>) -> Vec<NodeCfg<'a>> {
+ create_node_cfgs_with_persisters(node_count, chanmon_cfgs, chanmon_cfgs.iter().map(|c| &c.persister).collect())
+}
+
+pub fn create_node_cfgs_with_persisters<'a>(node_count: usize, chanmon_cfgs: &'a Vec<TestChanMonCfg>, persisters: Vec<&'a impl Persist<TestChannelSigner>>) -> Vec<NodeCfg<'a>> {
let mut nodes = Vec::new();
for i in 0..node_count {
- let chain_monitor = test_utils::TestChainMonitor::new(Some(&chanmon_cfgs[i].chain_source), &chanmon_cfgs[i].tx_broadcaster, &chanmon_cfgs[i].logger, &chanmon_cfgs[i].fee_estimator, &chanmon_cfgs[i].persister, &chanmon_cfgs[i].keys_manager);
+ let chain_monitor = test_utils::TestChainMonitor::new(Some(&chanmon_cfgs[i].chain_source), &chanmon_cfgs[i].tx_broadcaster, &chanmon_cfgs[i].logger, &chanmon_cfgs[i].fee_estimator, persisters[i], &chanmon_cfgs[i].keys_manager);
let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &chanmon_cfgs[i].logger));
let seed = [i as u8; 32];
nodes.push(NodeCfg {
// It now defaults to 1, so we simply set it to the expected value here.
default_config.channel_handshake_config.our_htlc_minimum_msat = 1000;
// When most of our tests were written, we didn't have the notion of a `max_dust_htlc_exposure_msat`,
- // It now defaults to 5_000_000 msat; to avoid interfering with tests we bump it to 50_000_000 msat.
- default_config.channel_config.max_dust_htlc_exposure_msat = 50_000_000;
+ // to avoid interfering with tests we bump it to 50_000_000 msat (assuming the default test
+ // feerate of 253).
+ default_config.channel_config.max_dust_htlc_exposure =
+ MaxDustHTLCExposure::FeeRateMultiplier(50_000_000 / 253);
default_config
}
for i in 0..node_count {
let gossip_sync = P2PGossipSync::new(cfgs[i].network_graph.as_ref(), None, cfgs[i].logger);
+ let wallet_source = Arc::new(test_utils::TestWalletSource::new(SecretKey::from_slice(&[i as u8 + 1; 32]).unwrap()));
nodes.push(Node{
chain_source: cfgs[i].chain_source, tx_broadcaster: cfgs[i].tx_broadcaster,
fee_estimator: cfgs[i].fee_estimator, router: &cfgs[i].router,
blocks: Arc::clone(&cfgs[i].tx_broadcaster.blocks),
connect_style: Rc::clone(&connect_style),
override_init_features: Rc::clone(&cfgs[i].override_init_features),
+ wallet_source: Arc::clone(&wallet_source),
+ bump_tx_handler: BumpTransactionEventHandler::new(
+ cfgs[i].tx_broadcaster, Arc::new(Wallet::new(Arc::clone(&wallet_source), cfgs[i].logger)),
+ &cfgs[i].keys_manager, cfgs[i].logger,
+ ),
})
}
///
/// All broadcast transactions must be accounted for in one of the above three types of we'll
/// also fail.
-pub fn test_txn_broadcast<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, chan: &(msgs::ChannelUpdate, msgs::ChannelUpdate, [u8; 32], Transaction), commitment_tx: Option<Transaction>, has_htlc_tx: HTLCType) -> Vec<Transaction> {
+pub fn test_txn_broadcast<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, chan: &(msgs::ChannelUpdate, msgs::ChannelUpdate, ChannelId, Transaction), commitment_tx: Option<Transaction>, has_htlc_tx: HTLCType) -> Vec<Transaction> {
let mut node_txn = node.tx_broadcaster.txn_broadcasted.lock().unwrap();
let mut txn_seen = HashSet::new();
node_txn.retain(|tx| txn_seen.insert(tx.txid()));
($node: expr, $counterparty_node: expr, $channel_id: expr) => {{
let peer_state_lock = $node.node.per_peer_state.read().unwrap();
let chan_lock = peer_state_lock.get(&$counterparty_node.node.get_our_node_id()).unwrap().lock().unwrap();
- let chan = chan_lock.channel_by_id.get(&$channel_id).unwrap();
+ let chan = chan_lock.channel_by_id.get(&$channel_id).map(
+ |phase| if let ChannelPhase::Funded(chan) = phase { Some(chan) } else { None }
+ ).flatten().unwrap();
chan.get_value_stat()
}}
}
panic!("Unexpected event")
}
}
- for chan in $src_node.node.list_channels() {
- if chan.is_public && chan.counterparty.node_id != $dst_node.node.get_our_node_id() {
- if let Some(scid) = chan.short_channel_id {
- assert!(announcements.remove(&scid));
- }
- }
- }
assert!(announcements.is_empty());
res
}
}
}
+pub struct ReconnectArgs<'a, 'b, 'c, 'd> {
+ pub node_a: &'a Node<'b, 'c, 'd>,
+ pub node_b: &'a Node<'b, 'c, 'd>,
+ pub send_channel_ready: (bool, bool),
+ pub pending_responding_commitment_signed: (bool, bool),
+ /// Indicates that the pending responding commitment signed will be a dup for the recipient,
+ /// and no monitor update is expected
+ pub pending_responding_commitment_signed_dup_monitor: (bool, bool),
+ pub pending_htlc_adds: (usize, usize),
+ pub pending_htlc_claims: (usize, usize),
+ pub pending_htlc_fails: (usize, usize),
+ pub pending_cell_htlc_claims: (usize, usize),
+ pub pending_cell_htlc_fails: (usize, usize),
+ pub pending_raa: (bool, bool),
+}
+
+impl<'a, 'b, 'c, 'd> ReconnectArgs<'a, 'b, 'c, 'd> {
+ pub fn new(node_a: &'a Node<'b, 'c, 'd>, node_b: &'a Node<'b, 'c, 'd>) -> Self {
+ Self {
+ node_a,
+ node_b,
+ send_channel_ready: (false, false),
+ pending_responding_commitment_signed: (false, false),
+ pending_responding_commitment_signed_dup_monitor: (false, false),
+ pending_htlc_adds: (0, 0),
+ pending_htlc_claims: (0, 0),
+ pending_htlc_fails: (0, 0),
+ pending_cell_htlc_claims: (0, 0),
+ pending_cell_htlc_fails: (0, 0),
+ pending_raa: (false, false),
+ }
+ }
+}
+
/// pending_htlc_adds includes both the holding cell and in-flight update_add_htlcs, whereas
/// for claims/fails they are separated out.
-pub fn reconnect_nodes<'a, 'b, 'c>(node_a: &Node<'a, 'b, 'c>, node_b: &Node<'a, 'b, 'c>, send_channel_ready: (bool, bool), pending_htlc_adds: (i64, i64), pending_htlc_claims: (usize, usize), pending_htlc_fails: (usize, usize), pending_cell_htlc_claims: (usize, usize), pending_cell_htlc_fails: (usize, usize), pending_raa: (bool, bool)) {
+pub fn reconnect_nodes<'a, 'b, 'c, 'd>(args: ReconnectArgs<'a, 'b, 'c, 'd>) {
+ let ReconnectArgs {
+ node_a, node_b, send_channel_ready, pending_htlc_adds, pending_htlc_claims, pending_htlc_fails,
+ pending_cell_htlc_claims, pending_cell_htlc_fails, pending_raa,
+ pending_responding_commitment_signed, pending_responding_commitment_signed_dup_monitor,
+ } = args;
node_a.node.peer_connected(&node_b.node.get_our_node_id(), &msgs::Init {
features: node_b.node.init_features(), networks: None, remote_network_address: None
}, true).unwrap();
} else {
assert!(chan_msgs.1.is_none());
}
- if pending_htlc_adds.0 != 0 || pending_htlc_claims.0 != 0 || pending_htlc_fails.0 != 0 || pending_cell_htlc_claims.0 != 0 || pending_cell_htlc_fails.0 != 0 {
+ if pending_htlc_adds.0 != 0 || pending_htlc_claims.0 != 0 || pending_htlc_fails.0 != 0 ||
+ pending_cell_htlc_claims.0 != 0 || pending_cell_htlc_fails.0 != 0 ||
+ pending_responding_commitment_signed.0
+ {
let commitment_update = chan_msgs.2.unwrap();
- if pending_htlc_adds.0 != -1 { // We use -1 to denote a response commitment_signed
- assert_eq!(commitment_update.update_add_htlcs.len(), pending_htlc_adds.0 as usize);
- } else {
- assert!(commitment_update.update_add_htlcs.is_empty());
- }
+ assert_eq!(commitment_update.update_add_htlcs.len(), pending_htlc_adds.0);
assert_eq!(commitment_update.update_fulfill_htlcs.len(), pending_htlc_claims.0 + pending_cell_htlc_claims.0);
assert_eq!(commitment_update.update_fail_htlcs.len(), pending_htlc_fails.0 + pending_cell_htlc_fails.0);
assert!(commitment_update.update_fail_malformed_htlcs.is_empty());
node_a.node.handle_update_fail_htlc(&node_b.node.get_our_node_id(), &update_fail);
}
- if pending_htlc_adds.0 != -1 { // We use -1 to denote a response commitment_signed
+ if !pending_responding_commitment_signed.0 {
commitment_signed_dance!(node_a, node_b, commitment_update.commitment_signed, false);
} else {
node_a.node.handle_commitment_signed(&node_b.node.get_our_node_id(), &commitment_update.commitment_signed);
// No commitment_signed so get_event_msg's assert(len == 1) passes
node_b.node.handle_revoke_and_ack(&node_a.node.get_our_node_id(), &as_revoke_and_ack);
assert!(node_b.node.get_and_clear_pending_msg_events().is_empty());
- check_added_monitors!(node_b, 1);
+ check_added_monitors!(node_b, if pending_responding_commitment_signed_dup_monitor.0 { 0 } else { 1 });
}
} else {
assert!(chan_msgs.2.is_none());
} else {
assert!(chan_msgs.1.is_none());
}
- if pending_htlc_adds.1 != 0 || pending_htlc_claims.1 != 0 || pending_htlc_fails.1 != 0 || pending_cell_htlc_claims.1 != 0 || pending_cell_htlc_fails.1 != 0 {
+ if pending_htlc_adds.1 != 0 || pending_htlc_claims.1 != 0 || pending_htlc_fails.1 != 0 ||
+ pending_cell_htlc_claims.1 != 0 || pending_cell_htlc_fails.1 != 0 ||
+ pending_responding_commitment_signed.1
+ {
let commitment_update = chan_msgs.2.unwrap();
- if pending_htlc_adds.1 != -1 { // We use -1 to denote a response commitment_signed
- assert_eq!(commitment_update.update_add_htlcs.len(), pending_htlc_adds.1 as usize);
- }
+ assert_eq!(commitment_update.update_add_htlcs.len(), pending_htlc_adds.1);
assert_eq!(commitment_update.update_fulfill_htlcs.len(), pending_htlc_claims.1 + pending_cell_htlc_claims.1);
assert_eq!(commitment_update.update_fail_htlcs.len(), pending_htlc_fails.1 + pending_cell_htlc_fails.1);
assert!(commitment_update.update_fail_malformed_htlcs.is_empty());
node_b.node.handle_update_fail_htlc(&node_a.node.get_our_node_id(), &update_fail);
}
- if pending_htlc_adds.1 != -1 { // We use -1 to denote a response commitment_signed
+ if !pending_responding_commitment_signed.1 {
commitment_signed_dance!(node_b, node_a, commitment_update.commitment_signed, false);
} else {
node_b.node.handle_commitment_signed(&node_a.node.get_our_node_id(), &commitment_update.commitment_signed);
// No commitment_signed so get_event_msg's assert(len == 1) passes
node_a.node.handle_revoke_and_ack(&node_b.node.get_our_node_id(), &bs_revoke_and_ack);
assert!(node_a.node.get_and_clear_pending_msg_events().is_empty());
- check_added_monitors!(node_a, 1);
+ check_added_monitors!(node_a, if pending_responding_commitment_signed_dup_monitor.1 { 0 } else { 1 });
}
} else {
assert!(chan_msgs.2.is_none());
}
}
}
+
+/// Initiates channel opening and creates a single batch funding transaction.
+/// This will go through the open_channel / accept_channel flow, and return the batch funding
+/// transaction with corresponding funding_created messages.
+pub fn create_batch_channel_funding<'a, 'b, 'c>(
+ funding_node: &Node<'a, 'b, 'c>,
+ params: &[(&Node<'a, 'b, 'c>, u64, u64, u128, Option<UserConfig>)],
+) -> (Transaction, Vec<msgs::FundingCreated>) {
+ let mut tx_outs = Vec::new();
+ let mut temp_chan_ids = Vec::new();
+ let mut funding_created_msgs = Vec::new();
+
+ for (other_node, channel_value_satoshis, push_msat, user_channel_id, override_config) in params {
+ // Initialize channel opening.
+ let temp_chan_id = funding_node.node.create_channel(
+ other_node.node.get_our_node_id(), *channel_value_satoshis, *push_msat, *user_channel_id,
+ *override_config,
+ ).unwrap();
+ let open_channel_msg = get_event_msg!(funding_node, MessageSendEvent::SendOpenChannel, other_node.node.get_our_node_id());
+ other_node.node.handle_open_channel(&funding_node.node.get_our_node_id(), &open_channel_msg);
+ let accept_channel_msg = get_event_msg!(other_node, MessageSendEvent::SendAcceptChannel, funding_node.node.get_our_node_id());
+ funding_node.node.handle_accept_channel(&other_node.node.get_our_node_id(), &accept_channel_msg);
+
+ // Create the corresponding funding output.
+ let events = funding_node.node.get_and_clear_pending_events();
+ assert_eq!(events.len(), 1);
+ match events[0] {
+ Event::FundingGenerationReady {
+ ref temporary_channel_id,
+ ref counterparty_node_id,
+ channel_value_satoshis: ref event_channel_value_satoshis,
+ ref output_script,
+ user_channel_id: ref event_user_channel_id
+ } => {
+ assert_eq!(temporary_channel_id, &temp_chan_id);
+ assert_eq!(counterparty_node_id, &other_node.node.get_our_node_id());
+ assert_eq!(channel_value_satoshis, event_channel_value_satoshis);
+ assert_eq!(user_channel_id, event_user_channel_id);
+ tx_outs.push(TxOut {
+ value: *channel_value_satoshis, script_pubkey: output_script.clone(),
+ });
+ },
+ _ => panic!("Unexpected event"),
+ };
+ temp_chan_ids.push((temp_chan_id, other_node.node.get_our_node_id()));
+ }
+
+ // Compose the batch funding transaction and give it to the ChannelManager.
+ let tx = Transaction {
+ version: 2,
+ lock_time: PackedLockTime::ZERO,
+ input: Vec::new(),
+ output: tx_outs,
+ };
+ assert!(funding_node.node.batch_funding_transaction_generated(
+ temp_chan_ids.iter().map(|(a, b)| (a, b)).collect::<Vec<_>>().as_slice(),
+ tx.clone(),
+ ).is_ok());
+ check_added_monitors!(funding_node, 0);
+ let events = funding_node.node.get_and_clear_pending_msg_events();
+ assert_eq!(events.len(), params.len());
+ for (other_node, ..) in params {
+ let funding_created = events
+ .iter()
+ .find_map(|event| match event {
+ MessageSendEvent::SendFundingCreated { node_id, msg } if node_id == &other_node.node.get_our_node_id() => Some(msg.clone()),
+ _ => None,
+ })
+ .unwrap();
+ funding_created_msgs.push(funding_created);
+ }
+ return (tx, funding_created_msgs);
+}
use crate::chain::{ChannelMonitorUpdateStatus, Confirm, Listen, Watch};
use crate::chain::chaininterface::LowerBoundedFeeEstimator;
use crate::chain::channelmonitor;
-use crate::chain::channelmonitor::{CLTV_CLAIM_BUFFER, LATENCY_GRACE_PERIOD_BLOCKS, ANTI_REORG_DELAY};
+use crate::chain::channelmonitor::{CLOSED_CHANNEL_UPDATE_ID, CLTV_CLAIM_BUFFER, LATENCY_GRACE_PERIOD_BLOCKS, ANTI_REORG_DELAY};
use crate::chain::transaction::OutPoint;
-use crate::sign::{ChannelSigner, EcdsaChannelSigner, EntropySource};
+use crate::sign::{EcdsaChannelSigner, EntropySource, SignerProvider};
use crate::events::{Event, MessageSendEvent, MessageSendEventsProvider, PathFailure, PaymentPurpose, ClosureReason, HTLCDestination, PaymentFailureReason};
-use crate::ln::{PaymentPreimage, PaymentSecret, PaymentHash};
-use crate::ln::channel::{commitment_tx_base_weight, COMMITMENT_TX_WEIGHT_PER_HTLC, CONCURRENT_INBOUND_HTLC_FEE_BUFFER, FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE, MIN_AFFORDABLE_HTLC_COUNT, get_holder_selected_channel_reserve_satoshis, OutboundV1Channel, InboundV1Channel};
+use crate::ln::{ChannelId, PaymentPreimage, PaymentSecret, PaymentHash};
+use crate::ln::channel::{commitment_tx_base_weight, COMMITMENT_TX_WEIGHT_PER_HTLC, CONCURRENT_INBOUND_HTLC_FEE_BUFFER, FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE, MIN_AFFORDABLE_HTLC_COUNT, get_holder_selected_channel_reserve_satoshis, OutboundV1Channel, InboundV1Channel, COINBASE_MATURITY, ChannelPhase};
use crate::ln::channelmanager::{self, PaymentId, RAACommitmentOrder, PaymentSendFailure, RecipientOnionFields, BREAKDOWN_TIMEOUT, ENABLE_GOSSIP_TICKS, DISABLE_GOSSIP_TICKS, MIN_CLTV_EXPIRY_DELTA};
use crate::ln::channel::{DISCONNECT_PEER_AWAITING_RESPONSE_TICKS, ChannelError};
use crate::ln::{chan_utils, onion_utils};
use crate::ln::chan_utils::{OFFERED_HTLC_SCRIPT_WEIGHT, htlc_success_tx_weight, htlc_timeout_tx_weight, HTLCOutputInCommitment};
use crate::routing::gossip::{NetworkGraph, NetworkUpdate};
-use crate::routing::router::{Path, PaymentParameters, Route, RouteHop, RouteParameters, find_route, get_route};
+use crate::routing::router::{Path, PaymentParameters, Route, RouteHop, get_route, RouteParameters};
use crate::ln::features::{ChannelFeatures, ChannelTypeFeatures, NodeFeatures};
use crate::ln::msgs;
use crate::ln::msgs::{ChannelMessageHandler, RoutingMessageHandler, ErrorAction};
-use crate::util::enforcing_trait_impls::EnforcingSigner;
-use crate::util::test_utils;
+use crate::util::test_channel_signer::TestChannelSigner;
+use crate::util::test_utils::{self, WatchtowerPersister};
use crate::util::errors::APIError;
use crate::util::ser::{Writeable, ReadableArgs};
use crate::util::string::UntrustedString;
-use crate::util::config::UserConfig;
+use crate::util::config::{UserConfig, MaxDustHTLCExposure};
use bitcoin::hash_types::BlockHash;
use bitcoin::blockdata::script::{Builder, Script};
use core::default::Default;
use core::iter::repeat;
use bitcoin::hashes::Hash;
-use crate::sync::{Arc, Mutex};
+use crate::sync::{Arc, Mutex, RwLock};
use crate::ln::functional_test_utils::*;
use crate::ln::chan_utils::CommitmentTransaction;
+use super::channel::UNFUNDED_CHANNEL_AGE_LIMIT_TICKS;
+
#[test]
fn test_insane_channel_opens() {
// Stand up a network of 2 nodes
let counterparty_node = if send_from_initiator { &nodes[0] } else { &nodes[1] };
let mut sender_node_per_peer_lock;
let mut sender_node_peer_state_lock;
- if send_from_initiator {
- let chan = get_inbound_v1_channel_ref!(sender_node, counterparty_node, sender_node_per_peer_lock, sender_node_peer_state_lock, temp_channel_id);
- chan.context.holder_selected_channel_reserve_satoshis = 0;
- chan.context.holder_max_htlc_value_in_flight_msat = 100_000_000;
- } else {
- let chan = get_outbound_v1_channel_ref!(sender_node, counterparty_node, sender_node_per_peer_lock, sender_node_peer_state_lock, temp_channel_id);
- chan.context.holder_selected_channel_reserve_satoshis = 0;
- chan.context.holder_max_htlc_value_in_flight_msat = 100_000_000;
+
+ let channel_phase = get_channel_ref!(sender_node, counterparty_node, sender_node_per_peer_lock, sender_node_peer_state_lock, temp_channel_id);
+ match channel_phase {
+ ChannelPhase::UnfundedInboundV1(_) | ChannelPhase::UnfundedOutboundV1(_) => {
+ let chan_context = channel_phase.context_mut();
+ chan_context.holder_selected_channel_reserve_satoshis = 0;
+ chan_context.holder_max_htlc_value_in_flight_msat = 100_000_000;
+ },
+ ChannelPhase::Funded(_) => assert!(false),
}
}
const INITIAL_COMMITMENT_NUMBER: u64 = 281474976710654;
- // Get the EnforcingSigner for each channel, which will be used to (1) get the keys
+ // Get the TestChannelSigner for each channel, which will be used to (1) get the keys
// needed to sign the new commitment tx and (2) sign the new commitment tx.
let (local_revocation_basepoint, local_htlc_basepoint, local_funding) = {
let per_peer_state = nodes[0].node.per_peer_state.read().unwrap();
let chan_lock = per_peer_state.get(&nodes[1].node.get_our_node_id()).unwrap().lock().unwrap();
- let local_chan = chan_lock.channel_by_id.get(&chan.2).unwrap();
+ let local_chan = chan_lock.channel_by_id.get(&chan.2).map(
+ |phase| if let ChannelPhase::Funded(chan) = phase { Some(chan) } else { None }
+ ).flatten().unwrap();
let chan_signer = local_chan.get_signer();
- let pubkeys = chan_signer.pubkeys();
+ let pubkeys = chan_signer.as_ref().pubkeys();
(pubkeys.revocation_basepoint, pubkeys.htlc_basepoint,
pubkeys.funding_pubkey)
};
let (remote_delayed_payment_basepoint, remote_htlc_basepoint,remote_point, remote_funding) = {
let per_peer_state = nodes[1].node.per_peer_state.read().unwrap();
let chan_lock = per_peer_state.get(&nodes[0].node.get_our_node_id()).unwrap().lock().unwrap();
- let remote_chan = chan_lock.channel_by_id.get(&chan.2).unwrap();
+ let remote_chan = chan_lock.channel_by_id.get(&chan.2).map(
+ |phase| if let ChannelPhase::Funded(chan) = phase { Some(chan) } else { None }
+ ).flatten().unwrap();
let chan_signer = remote_chan.get_signer();
- let pubkeys = chan_signer.pubkeys();
+ let pubkeys = chan_signer.as_ref().pubkeys();
(pubkeys.delayed_payment_basepoint, pubkeys.htlc_basepoint,
- chan_signer.get_per_commitment_point(INITIAL_COMMITMENT_NUMBER - 1, &secp_ctx),
+ chan_signer.as_ref().get_per_commitment_point(INITIAL_COMMITMENT_NUMBER - 1, &secp_ctx),
pubkeys.funding_pubkey)
};
let res = {
let per_peer_state = nodes[0].node.per_peer_state.read().unwrap();
let local_chan_lock = per_peer_state.get(&nodes[1].node.get_our_node_id()).unwrap().lock().unwrap();
- let local_chan = local_chan_lock.channel_by_id.get(&chan.2).unwrap();
+ let local_chan = local_chan_lock.channel_by_id.get(&chan.2).map(
+ |phase| if let ChannelPhase::Funded(chan) = phase { Some(chan) } else { None }
+ ).flatten().unwrap();
let local_chan_signer = local_chan.get_signer();
let mut htlcs: Vec<(HTLCOutputInCommitment, ())> = vec![];
let commitment_tx = CommitmentTransaction::new_with_auxiliary_htlc_data(
&mut htlcs,
&local_chan.context.channel_transaction_parameters.as_counterparty_broadcastable()
);
- local_chan_signer.sign_counterparty_commitment(&commitment_tx, Vec::new(), &secp_ctx).unwrap()
+ local_chan_signer.as_ecdsa().unwrap().sign_counterparty_commitment(&commitment_tx, Vec::new(), &secp_ctx).unwrap()
};
let commit_signed_msg = msgs::CommitmentSigned {
nodes[1].logger.assert_log("lightning::ln::channelmanager".to_string(), "Funding remote cannot afford proposed new fee".to_string(), 1);
check_added_monitors!(nodes[1], 1);
check_closed_broadcast!(nodes[1], true);
- check_closed_event!(nodes[1], 1, ClosureReason::ProcessingError { err: String::from("Funding remote cannot afford proposed new fee") });
+ check_closed_event!(nodes[1], 1, ClosureReason::ProcessingError { err: String::from("Funding remote cannot afford proposed new fee") },
+ [nodes[0].node.get_our_node_id()], channel_value);
}
#[test]
send_payment(&nodes[1], &vec!(&nodes[0])[..], 800000);
send_payment(&nodes[0], &vec!(&nodes[1])[..], 800000);
close_channel(&nodes[0], &nodes[1], &chan.2, chan.3, true);
- check_closed_event!(nodes[0], 1, ClosureReason::CooperativeClosure);
- check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure);
+ check_closed_event!(nodes[0], 1, ClosureReason::CooperativeClosure, [nodes[1].node.get_our_node_id()], 100000);
+ check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure, [nodes[0].node.get_our_node_id()], 100000);
}
#[test]
assert_eq!(get_feerate!(nodes[0], nodes[1], channel_id), feerate + 30);
assert_eq!(get_feerate!(nodes[1], nodes[0], channel_id), feerate + 30);
close_channel(&nodes[0], &nodes[1], &chan.2, chan.3, true);
- check_closed_event!(nodes[0], 1, ClosureReason::CooperativeClosure);
- check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure);
+ check_closed_event!(nodes[0], 1, ClosureReason::CooperativeClosure, [nodes[1].node.get_our_node_id()], 100000);
+ check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure, [nodes[0].node.get_our_node_id()], 100000);
}
#[test]
short_channel_id: chan_2.0.contents.short_channel_id,
channel_features: ChannelFeatures::empty(),
fee_msat: 0,
- cltv_expiry_delta: chan_3.0.contents.cltv_expiry_delta as u32
+ cltv_expiry_delta: chan_3.0.contents.cltv_expiry_delta as u32,
+ maybe_announced_channel: true,
});
hops.push(RouteHop {
pubkey: nodes[3].node.get_our_node_id(),
short_channel_id: chan_3.0.contents.short_channel_id,
channel_features: ChannelFeatures::empty(),
fee_msat: 0,
- cltv_expiry_delta: chan_4.1.contents.cltv_expiry_delta as u32
+ cltv_expiry_delta: chan_4.1.contents.cltv_expiry_delta as u32,
+ maybe_announced_channel: true,
});
hops.push(RouteHop {
pubkey: nodes[1].node.get_our_node_id(),
channel_features: nodes[1].node.channel_features(),
fee_msat: 1000000,
cltv_expiry_delta: TEST_FINAL_CLTV,
+ maybe_announced_channel: true,
});
hops[1].fee_msat = chan_4.1.contents.fee_base_msat as u64 + chan_4.1.contents.fee_proportional_millionths as u64 * hops[2].fee_msat as u64 / 1000000;
hops[0].fee_msat = chan_3.0.contents.fee_base_msat as u64 + chan_3.0.contents.fee_proportional_millionths as u64 * hops[1].fee_msat as u64 / 1000000;
- let payment_preimage_1 = send_along_route(&nodes[1], Route { paths: vec![Path { hops, blinded_tail: None }], payment_params: None }, &vec!(&nodes[2], &nodes[3], &nodes[1])[..], 1000000).0;
+ let payment_preimage_1 = send_along_route(&nodes[1],
+ Route { paths: vec![Path { hops, blinded_tail: None }], route_params: None },
+ &vec!(&nodes[2], &nodes[3], &nodes[1])[..], 1000000).0;
let mut hops = Vec::with_capacity(3);
hops.push(RouteHop {
short_channel_id: chan_4.0.contents.short_channel_id,
channel_features: ChannelFeatures::empty(),
fee_msat: 0,
- cltv_expiry_delta: chan_3.1.contents.cltv_expiry_delta as u32
+ cltv_expiry_delta: chan_3.1.contents.cltv_expiry_delta as u32,
+ maybe_announced_channel: true,
});
hops.push(RouteHop {
pubkey: nodes[2].node.get_our_node_id(),
short_channel_id: chan_3.0.contents.short_channel_id,
channel_features: ChannelFeatures::empty(),
fee_msat: 0,
- cltv_expiry_delta: chan_2.1.contents.cltv_expiry_delta as u32
+ cltv_expiry_delta: chan_2.1.contents.cltv_expiry_delta as u32,
+ maybe_announced_channel: true,
});
hops.push(RouteHop {
pubkey: nodes[1].node.get_our_node_id(),
channel_features: nodes[1].node.channel_features(),
fee_msat: 1000000,
cltv_expiry_delta: TEST_FINAL_CLTV,
+ maybe_announced_channel: true,
});
hops[1].fee_msat = chan_2.1.contents.fee_base_msat as u64 + chan_2.1.contents.fee_proportional_millionths as u64 * hops[2].fee_msat as u64 / 1000000;
hops[0].fee_msat = chan_3.1.contents.fee_base_msat as u64 + chan_3.1.contents.fee_proportional_millionths as u64 * hops[1].fee_msat as u64 / 1000000;
- let payment_hash_2 = send_along_route(&nodes[1], Route { paths: vec![Path { hops, blinded_tail: None }], payment_params: None }, &vec!(&nodes[3], &nodes[2], &nodes[1])[..], 1000000).1;
+ let payment_hash_2 = send_along_route(&nodes[1],
+ Route { paths: vec![Path { hops, blinded_tail: None }], route_params: None },
+ &vec!(&nodes[3], &nodes[2], &nodes[1])[..], 1000000).1;
// Claim the rebalances...
fail_payment(&nodes[1], &vec!(&nodes[3], &nodes[2], &nodes[1])[..], payment_hash_2);
// Close down the channels...
close_channel(&nodes[0], &nodes[1], &chan_1.2, chan_1.3, true);
- check_closed_event!(nodes[0], 1, ClosureReason::CooperativeClosure);
- check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure);
+ check_closed_event!(nodes[0], 1, ClosureReason::CooperativeClosure, [nodes[1].node.get_our_node_id()], 100000);
+ check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure, [nodes[0].node.get_our_node_id()], 100000);
close_channel(&nodes[1], &nodes[2], &chan_2.2, chan_2.3, false);
- check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure);
- check_closed_event!(nodes[2], 1, ClosureReason::CooperativeClosure);
+ check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure, [nodes[2].node.get_our_node_id()], 100000);
+ check_closed_event!(nodes[2], 1, ClosureReason::CooperativeClosure, [nodes[1].node.get_our_node_id()], 100000);
close_channel(&nodes[2], &nodes[3], &chan_3.2, chan_3.3, true);
- check_closed_event!(nodes[2], 1, ClosureReason::CooperativeClosure);
- check_closed_event!(nodes[3], 1, ClosureReason::CooperativeClosure);
+ check_closed_event!(nodes[2], 1, ClosureReason::CooperativeClosure, [nodes[3].node.get_our_node_id()], 100000);
+ check_closed_event!(nodes[3], 1, ClosureReason::CooperativeClosure, [nodes[2].node.get_our_node_id()], 100000);
close_channel(&nodes[1], &nodes[3], &chan_4.2, chan_4.3, false);
- check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure);
- check_closed_event!(nodes[3], 1, ClosureReason::CooperativeClosure);
+ check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure, [nodes[3].node.get_our_node_id()], 100000);
+ check_closed_event!(nodes[3], 1, ClosureReason::CooperativeClosure, [nodes[1].node.get_our_node_id()], 100000);
}
#[test]
create_announced_chan_between_nodes(&nodes, 3, 4);
create_announced_chan_between_nodes(&nodes, 3, 5);
- let (payment_preimage, payment_hash, _) = route_payment(&nodes[0], &vec!(&nodes[3], &nodes[4])[..], 1000000);
+ let (payment_preimage, payment_hash, ..) = route_payment(&nodes[0], &vec!(&nodes[3], &nodes[4])[..], 1000000);
*nodes[0].network_payment_count.borrow_mut() -= 1;
assert_eq!(route_payment(&nodes[1], &vec!(&nodes[3])[..], 1000000).0, payment_preimage);
// balancing
send_payment(&nodes[0], &vec!(&nodes[1])[..], 8000000);
- let (payment_preimage, payment_hash, _) = route_payment(&nodes[0], &vec!(&nodes[1])[..], 900_000);
+ let (payment_preimage, payment_hash, ..) = route_payment(&nodes[0], &vec!(&nodes[1])[..], 900_000);
let (route, _, _, _) = get_route_and_payment_hash!(nodes[1], nodes[0], 800_000);
let node_a_payment_secret = nodes[0].node.create_inbound_payment_for_hash(payment_hash, None, 7200, None).unwrap();
mine_transaction(&nodes[0], &remote_txn[0]);
check_added_monitors!(nodes[0], 1);
- check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed, [nodes[1].node.get_our_node_id()], 100000);
connect_blocks(&nodes[0], TEST_FINAL_CLTV); // Confirm blocks until the HTLC expires
let claim_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().clone();
const INITIAL_COMMITMENT_NUMBER: u64 = (1 << 48) - 1;
- // Get the EnforcingSigner for each channel, which will be used to (1) get the keys
+ // Get the TestChannelSigner for each channel, which will be used to (1) get the keys
// needed to sign the new commitment tx and (2) sign the new commitment tx.
let (local_revocation_basepoint, local_htlc_basepoint, local_secret, next_local_point, local_funding) = {
let per_peer_state = nodes[0].node.per_peer_state.read().unwrap();
let chan_lock = per_peer_state.get(&nodes[1].node.get_our_node_id()).unwrap().lock().unwrap();
- let local_chan = chan_lock.channel_by_id.get(&chan.2).unwrap();
+ let local_chan = chan_lock.channel_by_id.get(&chan.2).map(
+ |phase| if let ChannelPhase::Funded(chan) = phase { Some(chan) } else { None }
+ ).flatten().unwrap();
let chan_signer = local_chan.get_signer();
// Make the signer believe we validated another commitment, so we can release the secret
- chan_signer.get_enforcement_state().last_holder_commitment -= 1;
+ chan_signer.as_ecdsa().unwrap().get_enforcement_state().last_holder_commitment -= 1;
- let pubkeys = chan_signer.pubkeys();
+ let pubkeys = chan_signer.as_ref().pubkeys();
(pubkeys.revocation_basepoint, pubkeys.htlc_basepoint,
- chan_signer.release_commitment_secret(INITIAL_COMMITMENT_NUMBER),
- chan_signer.get_per_commitment_point(INITIAL_COMMITMENT_NUMBER - 2, &secp_ctx),
- chan_signer.pubkeys().funding_pubkey)
+ chan_signer.as_ref().release_commitment_secret(INITIAL_COMMITMENT_NUMBER),
+ chan_signer.as_ref().get_per_commitment_point(INITIAL_COMMITMENT_NUMBER - 2, &secp_ctx),
+ chan_signer.as_ref().pubkeys().funding_pubkey)
};
let (remote_delayed_payment_basepoint, remote_htlc_basepoint, remote_point, remote_funding) = {
let per_peer_state = nodes[1].node.per_peer_state.read().unwrap();
let chan_lock = per_peer_state.get(&nodes[0].node.get_our_node_id()).unwrap().lock().unwrap();
- let remote_chan = chan_lock.channel_by_id.get(&chan.2).unwrap();
+ let remote_chan = chan_lock.channel_by_id.get(&chan.2).map(
+ |phase| if let ChannelPhase::Funded(chan) = phase { Some(chan) } else { None }
+ ).flatten().unwrap();
let chan_signer = remote_chan.get_signer();
- let pubkeys = chan_signer.pubkeys();
+ let pubkeys = chan_signer.as_ref().pubkeys();
(pubkeys.delayed_payment_basepoint, pubkeys.htlc_basepoint,
- chan_signer.get_per_commitment_point(INITIAL_COMMITMENT_NUMBER - 1, &secp_ctx),
- chan_signer.pubkeys().funding_pubkey)
+ chan_signer.as_ref().get_per_commitment_point(INITIAL_COMMITMENT_NUMBER - 1, &secp_ctx),
+ chan_signer.as_ref().pubkeys().funding_pubkey)
};
// Assemble the set of keys we can use for signatures for our commitment_signed message.
let res = {
let per_peer_state = nodes[0].node.per_peer_state.read().unwrap();
let local_chan_lock = per_peer_state.get(&nodes[1].node.get_our_node_id()).unwrap().lock().unwrap();
- let local_chan = local_chan_lock.channel_by_id.get(&chan.2).unwrap();
+ let local_chan = local_chan_lock.channel_by_id.get(&chan.2).map(
+ |phase| if let ChannelPhase::Funded(chan) = phase { Some(chan) } else { None }
+ ).flatten().unwrap();
let local_chan_signer = local_chan.get_signer();
let commitment_tx = CommitmentTransaction::new_with_auxiliary_htlc_data(
commitment_number,
&mut vec![(accepted_htlc_info, ())],
&local_chan.context.channel_transaction_parameters.as_counterparty_broadcastable()
);
- local_chan_signer.sign_counterparty_commitment(&commitment_tx, Vec::new(), &secp_ctx).unwrap()
+ local_chan_signer.as_ecdsa().unwrap().sign_counterparty_commitment(&commitment_tx, Vec::new(), &secp_ctx).unwrap()
};
let commit_signed_msg = msgs::CommitmentSigned {
_ => panic!("Unexpected event"),
};
nodes[1].logger.assert_log("lightning::ln::channel".to_string(),
- format!("Attempting to fail HTLC due to fee spike buffer violation in channel {}. Rebalancing is required.", ::hex::encode(raa_msg.channel_id)), 1);
+ format!("Attempting to fail HTLC due to fee spike buffer violation in channel {}. Rebalancing is required.", raa_msg.channel_id), 1);
check_added_monitors!(nodes[1], 2);
}
let (route, our_payment_hash, _, our_payment_secret) = get_route_and_payment_hash!(nodes[1], nodes[0], 1_000_000);
// Sending exactly enough to hit the reserve amount should be accepted
for _ in 0..MIN_AFFORDABLE_HTLC_COUNT {
- let (_, _, _) = route_payment(&nodes[1], &[&nodes[0]], 1_000_000);
+ route_payment(&nodes[1], &[&nodes[0]], 1_000_000);
}
// However one more HTLC should be significantly over the reserve amount and fail.
// Send four HTLCs to cover the initial push_msat buffer we're required to include
for _ in 0..MIN_AFFORDABLE_HTLC_COUNT {
- let (_, _, _) = route_payment(&nodes[1], &[&nodes[0]], 1_000_000);
+ route_payment(&nodes[1], &[&nodes[0]], 1_000_000);
}
let (mut route, payment_hash, _, payment_secret) =
let err_msg = check_closed_broadcast!(nodes[0], true).unwrap();
assert_eq!(err_msg.data, "Cannot accept HTLC that would put our balance under counterparty-announced channel reserve value");
check_added_monitors!(nodes[0], 1);
- check_closed_event!(nodes[0], 1, ClosureReason::ProcessingError { err: "Cannot accept HTLC that would put our balance under counterparty-announced channel reserve value".to_string() });
+ check_closed_event!(nodes[0], 1, ClosureReason::ProcessingError { err: "Cannot accept HTLC that would put our balance under counterparty-announced channel reserve value".to_string() },
+ [nodes[1].node.get_our_node_id()], 100000);
}
#[test]
// In the previous code, routing this dust payment would cause nodes[0] to perceive a channel
// reserve violation even though it's a dust HTLC and therefore shouldn't count towards the
// commitment transaction fee.
- let (_, _, _) = route_payment(&nodes[1], &[&nodes[0]], dust_amt);
+ route_payment(&nodes[1], &[&nodes[0]], dust_amt);
// Send four HTLCs to cover the initial push_msat buffer we're required to include
for _ in 0..MIN_AFFORDABLE_HTLC_COUNT {
- let (_, _, _) = route_payment(&nodes[1], &[&nodes[0]], 1_000_000);
+ route_payment(&nodes[1], &[&nodes[0]], 1_000_000);
}
// One more than the dust amt should fail, however.
let payment_amt = 46000; // Dust amount
// In the previous code, these first four payments would succeed.
- let (_, _, _) = route_payment(&nodes[0], &[&nodes[1]], payment_amt);
- let (_, _, _) = route_payment(&nodes[0], &[&nodes[1]], payment_amt);
- let (_, _, _) = route_payment(&nodes[0], &[&nodes[1]], payment_amt);
- let (_, _, _) = route_payment(&nodes[0], &[&nodes[1]], payment_amt);
+ route_payment(&nodes[0], &[&nodes[1]], payment_amt);
+ route_payment(&nodes[0], &[&nodes[1]], payment_amt);
+ route_payment(&nodes[0], &[&nodes[1]], payment_amt);
+ route_payment(&nodes[0], &[&nodes[1]], payment_amt);
// Then these next 5 would be interpreted by nodes[1] as violating the fee spike buffer.
- let (_, _, _) = route_payment(&nodes[0], &[&nodes[1]], payment_amt);
- let (_, _, _) = route_payment(&nodes[0], &[&nodes[1]], payment_amt);
- let (_, _, _) = route_payment(&nodes[0], &[&nodes[1]], payment_amt);
- let (_, _, _) = route_payment(&nodes[0], &[&nodes[1]], payment_amt);
- let (_, _, _) = route_payment(&nodes[0], &[&nodes[1]], payment_amt);
+ route_payment(&nodes[0], &[&nodes[1]], payment_amt);
+ route_payment(&nodes[0], &[&nodes[1]], payment_amt);
+ route_payment(&nodes[0], &[&nodes[1]], payment_amt);
+ route_payment(&nodes[0], &[&nodes[1]], payment_amt);
+ route_payment(&nodes[0], &[&nodes[1]], payment_amt);
// And this last payment previously resulted in nodes[1] closing on its inbound-channel
// counterparty, because it counted all the previous dust HTLCs against nodes[0]'s commitment
// transaction fee and therefore perceived this next payment as a channel reserve violation.
- let (_, _, _) = route_payment(&nodes[0], &[&nodes[1]], payment_amt);
+ route_payment(&nodes[0], &[&nodes[1]], payment_amt);
}
#[test]
let err_msg = check_closed_broadcast!(nodes[1], true).unwrap();
assert_eq!(err_msg.data, "Remote HTLC add would put them under remote reserve value");
check_added_monitors!(nodes[1], 1);
- check_closed_event!(nodes[1], 1, ClosureReason::ProcessingError { err: "Remote HTLC add would put them under remote reserve value".to_string() });
+ check_closed_event!(nodes[1], 1, ClosureReason::ProcessingError { err: "Remote HTLC add would put them under remote reserve value".to_string() },
+ [nodes[0].node.get_our_node_id()], 100000);
}
#[test]
let b_chan_values = get_channel_value_stat!(nodes[1], nodes[0], chan_1.2);
// Route the first two HTLCs.
let payment_value_1 = b_chan_values.channel_reserve_msat - b_chan_values.value_to_self_msat - 10000;
- let (payment_preimage_1, payment_hash_1, _) = route_payment(&nodes[0], &[&nodes[1]], payment_value_1);
- let (payment_preimage_2, payment_hash_2, _) = route_payment(&nodes[0], &[&nodes[1]], 20_000);
+ let (payment_preimage_1, payment_hash_1, ..) = route_payment(&nodes[0], &[&nodes[1]], payment_value_1);
+ let (payment_preimage_2, payment_hash_2, ..) = route_payment(&nodes[0], &[&nodes[1]], 20_000);
// Start routing the third HTLC (this is just used to get everyone in the right state).
let (route, payment_hash_3, payment_preimage_3, payment_secret_3) = get_route_and_payment_hash!(nodes[0], nodes[1], 100000);
nodes[0].node.handle_commitment_signed(&nodes[1].node.get_our_node_id(), &bs_removes.commitment_signed);
check_added_monitors!(nodes[0], 1);
let as_raa = get_event_msg!(nodes[0], MessageSendEvent::SendRevokeAndACK, nodes[1].node.get_our_node_id());
- expect_payment_sent_without_paths!(nodes[0], payment_preimage_1);
+ expect_payment_sent(&nodes[0], payment_preimage_1, None, false, false);
nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &send_1.msgs[0]);
nodes[1].node.handle_commitment_signed(&nodes[0].node.get_our_node_id(), &send_1.commitment_msg);
nodes[0].node.handle_commitment_signed(&nodes[1].node.get_our_node_id(), &bs_cs.commitment_signed);
check_added_monitors!(nodes[0], 1);
let as_raa = get_event_msg!(nodes[0], MessageSendEvent::SendRevokeAndACK, nodes[1].node.get_our_node_id());
- expect_payment_sent_without_paths!(nodes[0], payment_preimage_2);
+ expect_payment_sent(&nodes[0], payment_preimage_2, None, false, false);
nodes[1].node.handle_revoke_and_ack(&nodes[0].node.get_our_node_id(), &as_raa);
check_added_monitors!(nodes[1], 1);
check_closed_broadcast!(nodes[0], true);
assert_eq!(nodes[0].node.list_channels().len(), 0);
assert_eq!(nodes[1].node.list_channels().len(), 1);
- check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed);
- check_closed_event!(nodes[1], 1, ClosureReason::HolderForceClosed);
+ check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed, [nodes[1].node.get_our_node_id()], 100000);
+ check_closed_event!(nodes[1], 1, ClosureReason::HolderForceClosed, [nodes[0].node.get_our_node_id()], 100000);
// One pending HTLC is discarded by the force-close:
- let (payment_preimage_1, payment_hash_1, _) = route_payment(&nodes[1], &[&nodes[2], &nodes[3]], 3_000_000);
+ let (payment_preimage_1, payment_hash_1, ..) = route_payment(&nodes[1], &[&nodes[2], &nodes[3]], 3_000_000);
// Simple case of one pending HTLC to HTLC-Timeout (note that the HTLC-Timeout is not
// broadcasted until we reach the timelock time).
check_closed_broadcast!(nodes[2], true);
assert_eq!(nodes[1].node.list_channels().len(), 0);
assert_eq!(nodes[2].node.list_channels().len(), 1);
- check_closed_event!(nodes[1], 1, ClosureReason::HolderForceClosed);
- check_closed_event!(nodes[2], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[1], 1, ClosureReason::HolderForceClosed, [nodes[2].node.get_our_node_id()], 100000);
+ check_closed_event!(nodes[2], 1, ClosureReason::CommitmentTxConfirmed, [nodes[1].node.get_our_node_id()], 100000);
macro_rules! claim_funds {
($node: expr, $prev_node: expr, $preimage: expr, $payment_hash: expr) => {
check_closed_broadcast!(nodes[3], true);
assert_eq!(nodes[2].node.list_channels().len(), 0);
assert_eq!(nodes[3].node.list_channels().len(), 1);
- check_closed_event!(nodes[2], 1, ClosureReason::HolderForceClosed);
- check_closed_event!(nodes[3], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[2], 1, ClosureReason::HolderForceClosed, [nodes[3].node.get_our_node_id()], 100000);
+ check_closed_event!(nodes[3], 1, ClosureReason::CommitmentTxConfirmed, [nodes[2].node.get_our_node_id()], 100000);
// Drop the ChannelMonitor for the previous channel to avoid it broadcasting transactions and
// confusing us in the following tests.
let chan_3_mon = nodes[3].chain_monitor.chain_monitor.remove_monitor(&OutPoint { txid: chan_3.3.txid(), index: 0 });
// One pending HTLC to time out:
- let (payment_preimage_2, payment_hash_2, _) = route_payment(&nodes[3], &[&nodes[4]], 3_000_000);
+ let (payment_preimage_2, payment_hash_2, ..) = route_payment(&nodes[3], &[&nodes[4]], 3_000_000);
// CLTV expires at TEST_FINAL_CLTV + 1 (current height) + 1 (added in send_payment for
// buffer space).
}
check_added_monitors!(nodes[4], 1);
test_txn_broadcast(&nodes[4], &chan_4, None, HTLCType::SUCCESS);
+ check_closed_event!(nodes[4], 1, ClosureReason::HolderForceClosed, [nodes[3].node.get_our_node_id()], 100000);
mine_transaction(&nodes[4], &node_txn[0]);
check_preimage_claim(&nodes[4], &node_txn);
assert_eq!(nodes[4].node.list_channels().len(), 0);
assert_eq!(nodes[3].chain_monitor.chain_monitor.watch_channel(OutPoint { txid: chan_3.3.txid(), index: 0 }, chan_3_mon),
- ChannelMonitorUpdateStatus::Completed);
- check_closed_event!(nodes[3], 1, ClosureReason::CommitmentTxConfirmed);
- check_closed_event!(nodes[4], 1, ClosureReason::CommitmentTxConfirmed);
+ Ok(ChannelMonitorUpdateStatus::Completed));
+ check_closed_event!(nodes[3], 1, ClosureReason::HolderForceClosed, [nodes[4].node.get_our_node_id()], 100000);
}
#[test]
node_txn.swap_remove(0);
}
check_added_monitors!(nodes[1], 1);
- check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [nodes[0].node.get_our_node_id()], 100000);
test_txn_broadcast(&nodes[1], &chan_5, Some(revoked_local_txn[0].clone()), HTLCType::NONE);
mine_transaction(&nodes[0], &revoked_local_txn[0]);
// Verify broadcast of revoked HTLC-timeout
let node_txn = test_txn_broadcast(&nodes[0], &chan_5, Some(revoked_local_txn[0].clone()), HTLCType::TIMEOUT);
check_added_monitors!(nodes[0], 1);
- check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed, [nodes[1].node.get_our_node_id()], 100000);
// Broadcast revoked HTLC-timeout on node 1
mine_transaction(&nodes[1], &node_txn[1]);
test_revoked_htlc_claim_txn_broadcast(&nodes[1], node_txn[1].clone(), revoked_local_txn[0].clone());
test_txn_broadcast(&nodes[0], &chan_6, Some(revoked_local_txn[0].clone()), HTLCType::NONE);
mine_transaction(&nodes[1], &revoked_local_txn[0]);
- check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [nodes[0].node.get_our_node_id()], 100000);
let node_txn = test_txn_broadcast(&nodes[1], &chan_6, Some(revoked_local_txn[0].clone()), HTLCType::SUCCESS);
check_added_monitors!(nodes[1], 1);
mine_transaction(&nodes[0], &node_txn[1]);
- check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed, [nodes[1].node.get_our_node_id()], 100000);
test_revoked_htlc_claim_txn_broadcast(&nodes[0], node_txn[1].clone(), revoked_local_txn[0].clone());
}
get_announce_close_broadcast_events(&nodes, 0, 1);
// Inform nodes[1] that nodes[0] broadcast a stale tx
mine_transaction(&nodes[1], &revoked_local_txn[0]);
check_added_monitors!(nodes[1], 1);
- check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [nodes[0].node.get_our_node_id()], 100000);
let node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().clone();
assert_eq!(node_txn.len(), 1); // ChannelMonitor: justice tx against revoked to_local output
mine_transaction(&nodes[0], &revoked_local_txn[0]);
get_announce_close_broadcast_events(&nodes, 0, 1);
check_added_monitors!(nodes[0], 1);
- check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed, [nodes[1].node.get_our_node_id()], 100000);
+}
+
+#[test]
+fn test_forming_justice_tx_from_monitor_updates() {
+ do_test_forming_justice_tx_from_monitor_updates(true);
+ do_test_forming_justice_tx_from_monitor_updates(false);
+}
+
+fn do_test_forming_justice_tx_from_monitor_updates(broadcast_initial_commitment: bool) {
+ // Simple test to make sure that the justice tx formed in WatchtowerPersister
+ // is properly formed and can be broadcasted/confirmed successfully in the event
+ // that a revoked commitment transaction is broadcasted
+ // (Similar to `revoked_output_claim` test but we get the justice tx + broadcast manually)
+ let chanmon_cfgs = create_chanmon_cfgs(2);
+ let destination_script0 = chanmon_cfgs[0].keys_manager.get_destination_script().unwrap();
+ let destination_script1 = chanmon_cfgs[1].keys_manager.get_destination_script().unwrap();
+ let persisters = vec![WatchtowerPersister::new(destination_script0),
+ WatchtowerPersister::new(destination_script1)];
+ let node_cfgs = create_node_cfgs_with_persisters(2, &chanmon_cfgs, persisters.iter().collect());
+ let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
+ let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+ let (_, _, channel_id, funding_tx) = create_announced_chan_between_nodes(&nodes, 0, 1);
+ let funding_txo = OutPoint { txid: funding_tx.txid(), index: 0 };
+
+ if !broadcast_initial_commitment {
+ // Send a payment to move the channel forward
+ send_payment(&nodes[0], &vec!(&nodes[1])[..], 5_000_000);
+ }
+
+ // node[0] is gonna to revoke an old state thus node[1] should be able to claim the revoked output.
+ // We'll keep this commitment transaction to broadcast once it's revoked.
+ let revoked_local_txn = get_local_commitment_txn!(nodes[0], channel_id);
+ assert_eq!(revoked_local_txn.len(), 1);
+ let revoked_commitment_tx = &revoked_local_txn[0];
+
+ // Send another payment, now revoking the previous commitment tx
+ send_payment(&nodes[0], &vec!(&nodes[1])[..], 5_000_000);
+
+ let justice_tx = persisters[1].justice_tx(funding_txo, &revoked_commitment_tx.txid()).unwrap();
+ check_spends!(justice_tx, revoked_commitment_tx);
+
+ mine_transactions(&nodes[1], &[revoked_commitment_tx, &justice_tx]);
+ mine_transactions(&nodes[0], &[revoked_commitment_tx, &justice_tx]);
+
+ check_added_monitors!(nodes[1], 1);
+ check_closed_event(&nodes[1], 1, ClosureReason::CommitmentTxConfirmed, false,
+ &[nodes[0].node.get_our_node_id()], 100_000);
+ get_announce_close_broadcast_events(&nodes, 1, 0);
+
+ check_added_monitors!(nodes[0], 1);
+ check_closed_event(&nodes[0], 1, ClosureReason::CommitmentTxConfirmed, false,
+ &[nodes[1].node.get_our_node_id()], 100_000);
+
+ // Check that the justice tx has sent the revoked output value to nodes[1]
+ let monitor = get_monitor!(nodes[1], channel_id);
+ let total_claimable_balance = monitor.get_claimable_balances().iter().fold(0, |sum, balance| {
+ match balance {
+ channelmonitor::Balance::ClaimableAwaitingConfirmations { amount_satoshis, .. } => sum + amount_satoshis,
+ _ => panic!("Unexpected balance type"),
+ }
+ });
+ // On the first commitment, node[1]'s balance was below dust so it didn't have an output
+ let node1_channel_balance = if broadcast_initial_commitment { 0 } else { revoked_commitment_tx.output[0].value };
+ let expected_claimable_balance = node1_channel_balance + justice_tx.output[0].value;
+ assert_eq!(total_claimable_balance, expected_claimable_balance);
}
+
#[test]
fn claim_htlc_outputs_shared_tx() {
// Node revoked old state, htlcs haven't time out yet, claim them in shared justice tx
send_payment(&nodes[0], &[&nodes[1]], 8_000_000);
// node[0] is gonna to revoke an old state thus node[1] should be able to claim both offered/received HTLC outputs on top of commitment tx
let payment_preimage_1 = route_payment(&nodes[0], &[&nodes[1]], 3_000_000).0;
- let (_payment_preimage_2, payment_hash_2, _) = route_payment(&nodes[1], &[&nodes[0]], 3_000_000);
+ let (_payment_preimage_2, payment_hash_2, ..) = route_payment(&nodes[1], &[&nodes[0]], 3_000_000);
// Get the will-be-revoked local txn from node[0]
let revoked_local_txn = get_local_commitment_txn!(nodes[0], chan_1.2);
{
mine_transaction(&nodes[0], &revoked_local_txn[0]);
check_added_monitors!(nodes[0], 1);
- check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed, [nodes[1].node.get_our_node_id()], 100000);
mine_transaction(&nodes[1], &revoked_local_txn[0]);
check_added_monitors!(nodes[1], 1);
- check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [nodes[0].node.get_our_node_id()], 100000);
connect_blocks(&nodes[1], ANTI_REORG_DELAY - 1);
assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
// node[0] is gonna to revoke an old state thus node[1] should be able to claim both offered/received HTLC outputs on top of commitment tx, but this
// time as two different claim transactions as we're gonna to timeout htlc with given a high current height
let payment_preimage_1 = route_payment(&nodes[0], &[&nodes[1]], 3_000_000).0;
- let (_payment_preimage_2, payment_hash_2, _payment_secret_2) = route_payment(&nodes[1], &[&nodes[0]], 3_000_000);
+ let (_payment_preimage_2, payment_hash_2, ..) = route_payment(&nodes[1], &[&nodes[0]], 3_000_000);
// Get the will-be-revoked local txn from node[0]
let revoked_local_txn = get_local_commitment_txn!(nodes[0], chan_1.2);
check_added_monitors!(nodes[0], 1);
confirm_transaction_at(&nodes[1], &revoked_local_txn[0], 100);
check_added_monitors!(nodes[1], 1);
- check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [nodes[0].node.get_our_node_id()], 100000);
let mut events = nodes[0].node.get_and_clear_pending_events();
expect_pending_htlcs_forwardable_from_events!(nodes[0], events[0..1], true);
match events.last().unwrap() {
send_payment(&nodes[0], &vec!(&nodes[1], &nodes[2])[..], 8000000);
send_payment(&nodes[0], &vec!(&nodes[1], &nodes[2])[..], 8000000);
- let (our_payment_preimage, payment_hash_1, _payment_secret) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 3_000_000);
- let (our_payment_preimage_2, payment_hash_2, _payment_secret_2) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 3_000_000);
+ let (our_payment_preimage, payment_hash_1, ..) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 3_000_000);
+ let (our_payment_preimage_2, payment_hash_2, ..) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 3_000_000);
// Broadcast legit commitment tx from C on B's chain
// Broadcast HTLC Success transaction by C on received output from C's commitment tx on B's chain
mine_transaction(&nodes[2], &commitment_tx[0]);
check_closed_broadcast!(nodes[2], true);
check_added_monitors!(nodes[2], 1);
- check_closed_event!(nodes[2], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[2], 1, ClosureReason::CommitmentTxConfirmed, [nodes[1].node.get_our_node_id()], 100000);
let node_txn = nodes[2].tx_broadcaster.txn_broadcasted.lock().unwrap().clone(); // ChannelMonitor: 2 (2 * HTLC-Success tx)
assert_eq!(node_txn.len(), 2);
check_spends!(node_txn[0], commitment_tx[0]);
mine_transaction(&nodes[1], &node_a_commitment_tx[0]);
check_closed_broadcast!(nodes[1], true);
check_added_monitors!(nodes[1], 1);
- check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [nodes[0].node.get_our_node_id()], 100000);
let node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().clone();
assert!(node_txn.len() == 1 || node_txn.len() == 3); // HTLC-Success, 2* RBF bumps of above HTLC txn
let commitment_spend =
send_payment(&nodes[0], &vec!(&nodes[1], &nodes[2])[..], 8000000);
send_payment(&nodes[0], &vec!(&nodes[1], &nodes[2])[..], 8000000);
- let (_payment_preimage, payment_hash, _payment_secret) = route_payment(&nodes[0], &vec!(&nodes[1], &nodes[2]), 3000000);
+ let (_payment_preimage, payment_hash, ..) = route_payment(&nodes[0], &vec!(&nodes[1], &nodes[2]), 3000000);
// Broadcast legit commitment tx from C on B's chain
let commitment_tx = get_local_commitment_txn!(nodes[2], chan_2.2);
mine_transaction(&nodes[2], &commitment_tx[0]);
check_closed_broadcast!(nodes[2], true);
check_added_monitors!(nodes[2], 1);
- check_closed_event!(nodes[2], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[2], 1, ClosureReason::CommitmentTxConfirmed, [nodes[1].node.get_our_node_id()], 100000);
let node_txn = nodes[2].tx_broadcaster.txn_broadcasted.lock().unwrap().clone();
assert_eq!(node_txn.len(), 0);
// Broadcast timeout transaction by B on received output from C's commitment tx on B's chain
// Verify that B's ChannelManager is able to detect that HTLC is timeout by its own tx and react backward in consequence
mine_transaction(&nodes[1], &commitment_tx[0]);
- check_closed_event(&nodes[1], 1, ClosureReason::CommitmentTxConfirmed, false);
+ check_closed_event!(&nodes[1], 1, ClosureReason::CommitmentTxConfirmed, false
+ , [nodes[2].node.get_our_node_id()], 100000);
connect_blocks(&nodes[1], 200 - nodes[2].best_block_info().1);
let timeout_tx = {
let mut txn = nodes[1].tx_broadcaster.txn_broadcast();
check_closed_broadcast!(nodes[0], true);
check_added_monitors!(nodes[0], 1);
- check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed, [nodes[1].node.get_our_node_id()], 100000);
let node_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().clone(); // 1 timeout tx
assert_eq!(node_txn.len(), 1);
check_spends!(node_txn[0], commitment_tx[0]);
create_announced_chan_between_nodes(&nodes, 0, 1);
let chan_2 = create_announced_chan_between_nodes(&nodes, 1, 2);
- let (payment_preimage, _payment_hash, _payment_secret) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 3000000);
+ let (payment_preimage, _payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 3000000);
// Get the will-be-revoked local txn from nodes[2]
let revoked_local_txn = get_local_commitment_txn!(nodes[2], chan_2.2);
// Revoke the old state
claim_payment(&nodes[0], &[&nodes[1], &nodes[2]], payment_preimage);
- let (_, payment_hash, _) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 3000000);
+ let (_, payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 3000000);
mine_transaction(&nodes[1], &revoked_local_txn[0]);
- check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [nodes[2].node.get_our_node_id()], 100000);
connect_blocks(&nodes[1], ANTI_REORG_DELAY - 1);
check_added_monitors!(nodes[1], 1);
check_closed_broadcast!(nodes[1], true);
create_announced_chan_between_nodes(&nodes, 0, 1);
let chan_2 = create_announced_chan_between_nodes(&nodes, 1, 2);
- let (payment_preimage, _payment_hash, _payment_secret) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], if no_to_remote { 10_000 } else { 3_000_000 });
+ let (payment_preimage, _payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], if no_to_remote { 10_000 } else { 3_000_000 });
// Get the will-be-revoked local txn from nodes[2]
let revoked_local_txn = get_local_commitment_txn!(nodes[2], chan_2.2);
assert_eq!(revoked_local_txn[0].output.len(), if no_to_remote { 1 } else { 2 });
// The dust limit applied to HTLC outputs considers the fee of the HTLC transaction as
// well, so HTLCs at exactly the dust limit will not be included in commitment txn.
nodes[2].node.per_peer_state.read().unwrap().get(&nodes[1].node.get_our_node_id())
- .unwrap().lock().unwrap().channel_by_id.get(&chan_2.2).unwrap().context.holder_dust_limit_satoshis * 1000
+ .unwrap().lock().unwrap().channel_by_id.get(&chan_2.2).unwrap().context().holder_dust_limit_satoshis * 1000
} else { 3000000 };
- let (_, first_payment_hash, _) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], value);
- let (_, second_payment_hash, _) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], value);
- let (_, third_payment_hash, _) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], value);
+ let (_, first_payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], value);
+ let (_, second_payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], value);
+ let (_, third_payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], value);
nodes[2].node.fail_htlc_backwards(&first_payment_hash);
expect_pending_htlcs_forwardable_and_htlc_handling_failed!(nodes[2], vec![HTLCDestination::FailedPayment { payment_hash: first_payment_hash }]);
connect_blocks(&nodes[0], TEST_FINAL_CLTV + LATENCY_GRACE_PERIOD_BLOCKS + 1);
check_closed_broadcast!(nodes[0], true);
check_added_monitors!(nodes[0], 1);
- check_closed_event!(nodes[0], 1, ClosureReason::HolderForceClosed);
+ check_closed_event!(nodes[0], 1, ClosureReason::HolderForceClosed, [nodes[1].node.get_our_node_id()], 100000);
let node_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
assert_eq!(node_txn.len(), 3);
connect_block(&nodes[1], &block);
check_closed_broadcast!(nodes[1], true);
check_added_monitors!(nodes[1], 1);
- check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [nodes[0].node.get_our_node_id()], 100000);
// Duplicate the connect_block call since this may happen due to other listeners
// registering new transactions
nodes[2].node.force_close_broadcasting_latest_txn(&payment_event.commitment_msg.channel_id, &nodes[1].node.get_our_node_id()).unwrap();
check_closed_broadcast!(nodes[2], true);
check_added_monitors!(nodes[2], 1);
- check_closed_event!(nodes[2], 1, ClosureReason::HolderForceClosed);
+ check_closed_event!(nodes[2], 1, ClosureReason::HolderForceClosed, [nodes[1].node.get_our_node_id()], 100000);
let tx = {
let mut node_txn = nodes[2].tx_broadcaster.txn_broadcasted.lock().unwrap();
// Note that we don't bother broadcasting the HTLC-Success transaction here as we don't
// Note no UpdateHTLCs event here from nodes[1] to nodes[0]!
check_closed_broadcast!(nodes[1], true);
check_added_monitors!(nodes[1], 1);
- check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [nodes[2].node.get_our_node_id()], 100000);
// Now check that if we add the preimage to ChannelMonitor it broadcasts our HTLC-Success..
{
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
create_announced_chan_between_nodes(&nodes, 0, 1);
- let (payment_preimage, payment_hash, _) = route_payment(&nodes[0], &[&nodes[1]], 1_000_000);
+ let (payment_preimage, payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1]], 1_000_000);
nodes[1].node.claim_funds(payment_preimage);
expect_payment_claimed!(nodes[1], payment_hash, 1_000_000);
check_added_monitors!(nodes[1], 1);
let claim_msgs = 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(), &claim_msgs.update_fulfill_htlcs[0]);
- expect_payment_sent_without_paths!(nodes[0], payment_preimage);
+ expect_payment_sent(&nodes[0], payment_preimage, None, false, false);
nodes[0].node.peer_disconnected(&nodes[1].node.get_our_node_id());
nodes[1].node.peer_disconnected(&nodes[0].node.get_our_node_id());
- reconnect_nodes(&nodes[0], &nodes[1], (false, false), (0, 0), (1, 0), (0, 0), (0, 0), (0, 0), (false, false));
+ let mut reconnect_args = ReconnectArgs::new(&nodes[0], &nodes[1]);
+ reconnect_args.pending_htlc_claims.0 = 1;
+ reconnect_nodes(reconnect_args);
expect_payment_path_successful!(nodes[0]);
}
nodes[0].node.peer_disconnected(&nodes[1].node.get_our_node_id());
nodes[1].node.peer_disconnected(&nodes[0].node.get_our_node_id());
- check_closed_event(&nodes[0], 1, ClosureReason::DisconnectedPeer, false);
- check_closed_event(&nodes[1], 1, ClosureReason::DisconnectedPeer, false);
+ check_closed_event!(&nodes[0], 2, ClosureReason::DisconnectedPeer, true
+ , [nodes[1].node.get_our_node_id()], 1000000);
+ check_closed_event!(&nodes[1], 1, ClosureReason::DisconnectedPeer, false
+ , [nodes[0].node.get_our_node_id()], 1000000);
}
#[test]
nodes[0].node.peer_disconnected(&nodes[1].node.get_our_node_id());
nodes[1].node.peer_disconnected(&nodes[0].node.get_our_node_id());
- reconnect_nodes(&nodes[0], &nodes[1], (true, true), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (false, false));
+ let mut reconnect_args = ReconnectArgs::new(&nodes[0], &nodes[1]);
+ reconnect_args.send_channel_ready = (true, true);
+ reconnect_nodes(reconnect_args);
let payment_preimage_1 = route_payment(&nodes[0], &vec!(&nodes[1], &nodes[2])[..], 1000000).0;
let payment_hash_2 = route_payment(&nodes[0], &vec!(&nodes[1], &nodes[2])[..], 1000000).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());
- reconnect_nodes(&nodes[0], &nodes[1], (false, false), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (false, false));
+ reconnect_nodes(ReconnectArgs::new(&nodes[0], &nodes[1]));
- let (payment_preimage_3, payment_hash_3, _) = route_payment(&nodes[0], &vec!(&nodes[1], &nodes[2])[..], 1000000);
+ let (payment_preimage_3, payment_hash_3, ..) = route_payment(&nodes[0], &vec!(&nodes[1], &nodes[2])[..], 1000000);
let payment_preimage_4 = route_payment(&nodes[0], &vec!(&nodes[1], &nodes[2])[..], 1000000).0;
let payment_hash_5 = route_payment(&nodes[0], &vec!(&nodes[1], &nodes[2])[..], 1000000).1;
let payment_hash_6 = route_payment(&nodes[0], &vec!(&nodes[1], &nodes[2])[..], 1000000).1;
claim_payment_along_route(&nodes[0], &[&[&nodes[1], &nodes[2]]], true, payment_preimage_3);
fail_payment_along_route(&nodes[0], &[&[&nodes[1], &nodes[2]]], true, payment_hash_5);
- reconnect_nodes(&nodes[0], &nodes[1], (false, false), (0, 0), (0, 0), (0, 0), (1, 0), (1, 0), (false, false));
+ let mut reconnect_args = ReconnectArgs::new(&nodes[0], &nodes[1]);
+ reconnect_args.pending_cell_htlc_fails.0 = 1;
+ reconnect_args.pending_cell_htlc_claims.0 = 1;
+ reconnect_nodes(reconnect_args);
{
let events = nodes[0].node.get_and_clear_pending_events();
assert_eq!(events.len(), 4);
_ => panic!("Unexpected event"),
}
}
+ check_added_monitors(&nodes[0], 1);
claim_payment(&nodes[0], &vec!(&nodes[1], &nodes[2]), payment_preimage_4);
fail_payment(&nodes[0], &vec!(&nodes[1], &nodes[2]), payment_hash_6);
}
// Even if the channel_ready messages get exchanged, as long as nothing further was
// received on either side, both sides will need to resend them.
- reconnect_nodes(&nodes[0], &nodes[1], (true, true), (0, 1), (0, 0), (0, 0), (0, 0), (0, 0), (false, false));
+ let mut reconnect_args = ReconnectArgs::new(&nodes[0], &nodes[1]);
+ reconnect_args.send_channel_ready = (true, true);
+ reconnect_args.pending_htlc_adds.1 = 1;
+ reconnect_nodes(reconnect_args);
} else if messages_delivered == 3 {
// nodes[0] still wants its RAA + commitment_signed
- reconnect_nodes(&nodes[0], &nodes[1], (false, false), (-1, 0), (0, 0), (0, 0), (0, 0), (0, 0), (true, false));
+ let mut reconnect_args = ReconnectArgs::new(&nodes[0], &nodes[1]);
+ reconnect_args.pending_responding_commitment_signed.0 = true;
+ reconnect_args.pending_raa.0 = true;
+ reconnect_nodes(reconnect_args);
} else if messages_delivered == 4 {
// nodes[0] still wants its commitment_signed
- reconnect_nodes(&nodes[0], &nodes[1], (false, false), (-1, 0), (0, 0), (0, 0), (0, 0), (0, 0), (false, false));
+ let mut reconnect_args = ReconnectArgs::new(&nodes[0], &nodes[1]);
+ reconnect_args.pending_responding_commitment_signed.0 = true;
+ reconnect_nodes(reconnect_args);
} else if messages_delivered == 5 {
// nodes[1] still wants its final RAA
- reconnect_nodes(&nodes[0], &nodes[1], (false, false), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (false, true));
+ let mut reconnect_args = ReconnectArgs::new(&nodes[0], &nodes[1]);
+ reconnect_args.pending_raa.1 = true;
+ reconnect_nodes(reconnect_args);
} else if messages_delivered == 6 {
// Everything was delivered...
- reconnect_nodes(&nodes[0], &nodes[1], (false, false), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (false, false));
+ reconnect_nodes(ReconnectArgs::new(&nodes[0], &nodes[1]));
}
let events_1 = nodes[1].node.get_and_clear_pending_events();
nodes[0].node.peer_disconnected(&nodes[1].node.get_our_node_id());
nodes[1].node.peer_disconnected(&nodes[0].node.get_our_node_id());
- reconnect_nodes(&nodes[0], &nodes[1], (false, false), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (false, false));
+ reconnect_nodes(ReconnectArgs::new(&nodes[0], &nodes[1]));
nodes[1].node.process_pending_htlc_forwards();
nodes[0].node.peer_disconnected(&nodes[1].node.get_our_node_id());
nodes[1].node.peer_disconnected(&nodes[0].node.get_our_node_id());
if messages_delivered < 2 {
- reconnect_nodes(&nodes[0], &nodes[1], (false, false), (0, 0), (1, 0), (0, 0), (0, 0), (0, 0), (false, false));
+ let mut reconnect_args = ReconnectArgs::new(&nodes[0], &nodes[1]);
+ reconnect_args.pending_htlc_claims.0 = 1;
+ reconnect_nodes(reconnect_args);
if messages_delivered < 1 {
expect_payment_sent!(nodes[0], payment_preimage_1);
} else {
}
} else if messages_delivered == 2 {
// nodes[0] still wants its RAA + commitment_signed
- reconnect_nodes(&nodes[0], &nodes[1], (false, false), (0, -1), (0, 0), (0, 0), (0, 0), (0, 0), (false, true));
+ let mut reconnect_args = ReconnectArgs::new(&nodes[0], &nodes[1]);
+ reconnect_args.pending_responding_commitment_signed.1 = true;
+ reconnect_args.pending_raa.1 = true;
+ reconnect_nodes(reconnect_args);
} else if messages_delivered == 3 {
// nodes[0] still wants its commitment_signed
- reconnect_nodes(&nodes[0], &nodes[1], (false, false), (0, -1), (0, 0), (0, 0), (0, 0), (0, 0), (false, false));
+ let mut reconnect_args = ReconnectArgs::new(&nodes[0], &nodes[1]);
+ reconnect_args.pending_responding_commitment_signed.1 = true;
+ reconnect_nodes(reconnect_args);
} else if messages_delivered == 4 {
// nodes[1] still wants its final RAA
- reconnect_nodes(&nodes[0], &nodes[1], (false, false), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (true, false));
+ let mut reconnect_args = ReconnectArgs::new(&nodes[0], &nodes[1]);
+ reconnect_args.pending_raa.0 = true;
+ reconnect_nodes(reconnect_args);
} else if messages_delivered == 5 {
// Everything was delivered...
- reconnect_nodes(&nodes[0], &nodes[1], (false, false), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (false, false));
+ reconnect_nodes(ReconnectArgs::new(&nodes[0], &nodes[1]));
}
if messages_delivered == 1 || messages_delivered == 2 {
nodes[0].node.peer_disconnected(&nodes[1].node.get_our_node_id());
nodes[1].node.peer_disconnected(&nodes[0].node.get_our_node_id());
}
- reconnect_nodes(&nodes[0], &nodes[1], (false, false), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (false, false));
+ reconnect_nodes(ReconnectArgs::new(&nodes[0], &nodes[1]));
if messages_delivered > 2 {
expect_payment_path_successful!(nodes[0]);
nodes[1].node.handle_channel_ready(&nodes[0].node.get_our_node_id(), &as_channel_ready);
}
+#[test]
+fn test_channel_monitor_skipping_block_when_channel_manager_is_leading() {
+ let chanmon_cfgs = create_chanmon_cfgs(2);
+ let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
+ let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
+ let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+
+ // Let channel_manager get ahead of chain_monitor by 1 block.
+ // This is to emulate race-condition where newly added channel_monitor skips processing 1 block,
+ // in case where client calls block_connect on channel_manager first and then on chain_monitor.
+ let height_1 = nodes[0].best_block_info().1 + 1;
+ let mut block_1 = create_dummy_block(nodes[0].best_block_hash(), height_1, Vec::new());
+
+ nodes[0].blocks.lock().unwrap().push((block_1.clone(), height_1));
+ nodes[0].node.block_connected(&block_1, height_1);
+
+ // Create channel, and it gets added to chain_monitor in funding_created.
+ let funding_tx = create_chan_between_nodes_with_value_init(&nodes[0], &nodes[1], 1_000_000, 0);
+
+ // Now, newly added channel_monitor in chain_monitor hasn't processed block_1,
+ // but it's best_block is block_1, since that was populated by channel_manager, and channel_manager
+ // was running ahead of chain_monitor at the time of funding_created.
+ // Later on, subsequent blocks are connected to both channel_manager and chain_monitor.
+ // Hence, this channel's channel_monitor skipped block_1, directly tries to process subsequent blocks.
+ confirm_transaction_at(&nodes[0], &funding_tx, nodes[0].best_block_info().1 + 1);
+ connect_blocks(&nodes[0], CHAN_CONFIRM_DEPTH);
+
+ // Ensure nodes[0] generates a channel_ready after the transactions_confirmed
+ let as_channel_ready = get_event_msg!(nodes[0], MessageSendEvent::SendChannelReady, nodes[1].node.get_our_node_id());
+ nodes[1].node.handle_channel_ready(&nodes[0].node.get_our_node_id(), &as_channel_ready);
+}
+
+#[test]
+fn test_channel_monitor_skipping_block_when_channel_manager_is_lagging() {
+ let chanmon_cfgs = create_chanmon_cfgs(2);
+ let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
+ let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
+ let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+
+ // Let chain_monitor get ahead of channel_manager by 1 block.
+ // This is to emulate race-condition where newly added channel_monitor skips processing 1 block,
+ // in case where client calls block_connect on chain_monitor first and then on channel_manager.
+ let height_1 = nodes[0].best_block_info().1 + 1;
+ let mut block_1 = create_dummy_block(nodes[0].best_block_hash(), height_1, Vec::new());
+
+ nodes[0].blocks.lock().unwrap().push((block_1.clone(), height_1));
+ nodes[0].chain_monitor.chain_monitor.block_connected(&block_1, height_1);
+
+ // Create channel, and it gets added to chain_monitor in funding_created.
+ let funding_tx = create_chan_between_nodes_with_value_init(&nodes[0], &nodes[1], 1_000_000, 0);
+
+ // channel_manager can't really skip block_1, it should get it eventually.
+ nodes[0].node.block_connected(&block_1, height_1);
+
+ // Now, newly added channel_monitor in chain_monitor hasn't processed block_1, it's best_block is
+ // the block before block_1, since that was populated by channel_manager, and channel_manager was
+ // running behind at the time of funding_created.
+ // Later on, subsequent blocks are connected to both channel_manager and chain_monitor.
+ // Hence, this channel's channel_monitor skipped block_1, directly tries to process subsequent blocks.
+ confirm_transaction_at(&nodes[0], &funding_tx, nodes[0].best_block_info().1 + 1);
+ connect_blocks(&nodes[0], CHAN_CONFIRM_DEPTH);
+
+ // Ensure nodes[0] generates a channel_ready after the transactions_confirmed
+ let as_channel_ready = get_event_msg!(nodes[0], MessageSendEvent::SendChannelReady, nodes[1].node.get_our_node_id());
+ nodes[1].node.handle_channel_ready(&nodes[0].node.get_our_node_id(), &as_channel_ready);
+}
+
#[test]
fn test_drop_messages_peer_disconnect_dual_htlc() {
// Test that we can handle reconnecting when both sides of a channel have pending
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
create_announced_chan_between_nodes(&nodes, 0, 1);
- let (payment_preimage_1, payment_hash_1, _) = route_payment(&nodes[0], &[&nodes[1]], 1_000_000);
+ let (payment_preimage_1, payment_hash_1, ..) = route_payment(&nodes[0], &[&nodes[1]], 1_000_000);
// Now try to send a second payment which will fail to send
let (route, payment_hash_2, payment_preimage_2, payment_secret_2) = get_route_and_payment_hash!(nodes[0], nodes[1], 1000000);
let secp_ctx = Secp256k1::new();
for event in events.drain(..) {
match event {
- Event::SpendableOutputs { mut outputs } => {
+ Event::SpendableOutputs { mut outputs, channel_id: _ } => {
for outp in outputs.drain(..) {
txn.push($keysinterface.backing.spend_spendable_outputs(&[&outp], Vec::new(), Builder::new().push_opcode(opcodes::all::OP_RETURN).into_script(), 253, None, &secp_ctx).unwrap());
all_outputs.push(outp);
nodes[1].node.force_close_broadcasting_latest_txn(&chan.2, &nodes[0].node.get_our_node_id()).unwrap();
check_closed_broadcast!(nodes[1], true);
check_added_monitors!(nodes[1], 1);
- check_closed_event!(nodes[1], 1, ClosureReason::HolderForceClosed);
+ check_closed_event!(nodes[1], 1, ClosureReason::HolderForceClosed, [nodes[0].node.get_our_node_id()], 100000);
let node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().clone();
assert_eq!(node_txn.len(), 1);
check_spends!(node_txn[0], chan.3);
nodes[0].node.force_close_broadcasting_latest_txn(&chan.2, &nodes[1].node.get_our_node_id()).unwrap();
check_closed_broadcast!(nodes[0], true);
check_added_monitors!(nodes[0], 1);
- check_closed_event!(nodes[0], 1, ClosureReason::HolderForceClosed);
+ check_closed_event!(nodes[0], 1, ClosureReason::HolderForceClosed, [nodes[1].node.get_our_node_id()], 100000);
let node_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
assert_eq!(node_txn.len(), 1);
mine_transaction(&nodes[1], &node_txn[0]);
check_closed_broadcast!(nodes[1], true);
check_added_monitors!(nodes[1], 1);
- check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [nodes[0].node.get_our_node_id()], 100000);
connect_blocks(&nodes[1], ANTI_REORG_DELAY - 1);
let spend_txn = check_spendable_outputs!(nodes[1], node_cfgs[1].keys_manager);
mine_transaction(&nodes[1], &revoked_local_txn[0]);
check_closed_broadcast!(nodes[1], true);
check_added_monitors!(nodes[1], 1);
- check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [nodes[0].node.get_our_node_id()], 100000);
let node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().clone();
mine_transaction(&nodes[1], &node_txn[0]);
// Create some initial channels
let chan_1 = create_announced_chan_between_nodes(&nodes, 0, 1);
- let (payment_preimage, payment_hash, _) = route_payment(&nodes[0], &[&nodes[1]], 3_000_000);
+ let (payment_preimage, payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1]], 3_000_000);
let commitment_tx = get_local_commitment_txn!(nodes[0], chan_1.2);
assert_eq!(commitment_tx[0].input.len(), 1);
assert_eq!(node_txn[0].input[0].witness.last().unwrap().len(), OFFERED_HTLC_SCRIPT_WEIGHT);
mine_transaction(&nodes[1], &node_txn[0]);
- check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [nodes[0].node.get_our_node_id()], 100000);
connect_blocks(&nodes[1], ANTI_REORG_DELAY - 1);
let spend_txn = check_spendable_outputs!(nodes[1], node_cfgs[1].keys_manager);
// Rebalance the network a bit by relaying one payment through all the channels ...
send_payment(&nodes[0], &vec!(&nodes[1])[..], 8000000);
- let (_, our_payment_hash, _) = route_payment(&nodes[1], &vec!(&nodes[0])[..], 3_000_000);
+ let (_, our_payment_hash, ..) = route_payment(&nodes[1], &vec!(&nodes[0])[..], 3_000_000);
let commitment_tx = get_local_commitment_txn!(nodes[0], chan_1.2);
assert_eq!(commitment_tx[0].input.len(), 1);
assert_eq!(node_txn[0].input[0].witness.last().unwrap().len(), ACCEPTED_HTLC_SCRIPT_WEIGHT);
mine_transaction(&nodes[1], &node_txn[0]);
- check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [nodes[0].node.get_our_node_id()], 100000);
connect_blocks(&nodes[1], ANTI_REORG_DELAY - 1);
expect_payment_failed!(nodes[1], our_payment_hash, false);
mine_transaction(&nodes[1], &revoked_local_txn[0]);
check_closed_broadcast!(nodes[1], true);
check_added_monitors!(nodes[1], 1);
- check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [nodes[0].node.get_our_node_id()], 100000);
let node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().clone();
assert_eq!(node_txn.len(), 1);
mine_transaction(&nodes[0], &revoked_local_txn[0]);
check_closed_broadcast!(nodes[0], true);
check_added_monitors!(nodes[0], 1);
- check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed, [nodes[1].node.get_our_node_id()], 100000);
connect_blocks(&nodes[0], TEST_FINAL_CLTV); // Confirm blocks until the HTLC expires
let revoked_htlc_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
connect_block(&nodes[1], &create_dummy_block(nodes[1].best_block_hash(), 42, vec![revoked_local_txn[0].clone(), revoked_htlc_txn[0].clone()]));
check_closed_broadcast!(nodes[1], true);
check_added_monitors!(nodes[1], 1);
- check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [nodes[0].node.get_our_node_id()], 100000);
let node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().clone();
assert_eq!(node_txn.len(), 2); // ChannelMonitor: bogus justice tx, justice tx on revoked outputs
mine_transaction(&nodes[1], &revoked_local_txn[0]);
check_closed_broadcast!(nodes[1], true);
check_added_monitors!(nodes[1], 1);
- check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [nodes[0].node.get_our_node_id()], 100000);
let revoked_htlc_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().clone();
assert_eq!(revoked_htlc_txn.len(), 1);
connect_block(&nodes[0], &create_dummy_block(nodes[0].best_block_hash(), 42, vec![revoked_local_txn[0].clone(), revoked_htlc_txn[0].clone()]));
check_closed_broadcast!(nodes[0], true);
check_added_monitors!(nodes[0], 1);
- check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed, [nodes[1].node.get_our_node_id()], 100000);
let node_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().clone();
assert_eq!(node_txn.len(), 2); // ChannelMonitor: justice tx on revoked commitment, justice tx on revoked HTLC-success
send_payment(&nodes[0], &vec!(&nodes[1], &nodes[2])[..], 8000000);
send_payment(&nodes[0], &vec!(&nodes[1], &nodes[2])[..], 8000000);
- let (payment_preimage, payment_hash, _payment_secret) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 3_000_000);
+ let (payment_preimage, payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 3_000_000);
let commitment_tx = get_local_commitment_txn!(nodes[2], chan_2.2);
check_spends!(commitment_tx[0], chan_2.3);
nodes[2].node.claim_funds(payment_preimage);
mine_transaction(&nodes[2], &commitment_tx[0]);
check_closed_broadcast!(nodes[2], true);
check_added_monitors!(nodes[2], 1);
- check_closed_event!(nodes[2], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[2], 1, ClosureReason::CommitmentTxConfirmed, [nodes[1].node.get_our_node_id()], 100000);
let c_txn = nodes[2].tx_broadcaster.txn_broadcasted.lock().unwrap().clone(); // ChannelMonitor: 1 (HTLC-Success tx)
assert_eq!(c_txn.len(), 1);
// Broadcast A's commitment tx on B's chain to see if we are able to claim inbound HTLC with our HTLC-Success tx
let commitment_tx = get_local_commitment_txn!(nodes[0], chan_1.2);
mine_transaction(&nodes[1], &commitment_tx[0]);
- check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [nodes[0].node.get_our_node_id()], 100000);
let b_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().clone();
// ChannelMonitor: HTLC-Success tx
assert_eq!(b_txn.len(), 1);
connect_blocks(&nodes[2], node_max_height - nodes[2].best_block_info().1);
connect_blocks(&nodes[3], node_max_height - nodes[3].best_block_info().1);
- let (our_payment_preimage, duplicate_payment_hash, _) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 900_000);
+ let (our_payment_preimage, duplicate_payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 900_000);
let payment_secret = nodes[3].node.create_inbound_payment_for_hash(duplicate_payment_hash, None, 7200, None).unwrap();
// We reduce the final CLTV here by a somewhat arbitrary constant to keep it under the one-byte
mine_transaction(&nodes[1], &commitment_txn[0]);
check_closed_broadcast!(nodes[1], true);
check_added_monitors!(nodes[1], 1);
- check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [nodes[2].node.get_our_node_id()], 100000);
connect_blocks(&nodes[1], TEST_FINAL_CLTV - 40 + MIN_CLTV_EXPIRY_DELTA as u32); // Confirm blocks until the HTLC expires
let htlc_timeout_tx;
mine_transaction(&nodes[2], &commitment_txn[0]);
check_added_monitors!(nodes[2], 2);
- check_closed_event!(nodes[2], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[2], 1, ClosureReason::CommitmentTxConfirmed, [nodes[1].node.get_our_node_id()], 100000);
let events = nodes[2].node.get_and_clear_pending_msg_events();
match events[0] {
MessageSendEvent::UpdateHTLCs { .. } => {},
nodes[0].node.handle_update_fulfill_htlc(&nodes[1].node.get_our_node_id(), &updates.update_fulfill_htlcs[0]);
commitment_signed_dance!(nodes[0], nodes[1], &updates.commitment_signed, false);
- expect_payment_sent(&nodes[0], our_payment_preimage, None, true);
+ expect_payment_sent(&nodes[0], our_payment_preimage, None, true, true);
}
#[test]
// Create some initial channels
let chan_1 = create_announced_chan_between_nodes(&nodes, 0, 1);
- let (payment_preimage, payment_hash, _) = route_payment(&nodes[0], &[&nodes[1]], 9_000_000);
+ let (payment_preimage, payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1]], 9_000_000);
let local_txn = get_local_commitment_txn!(nodes[1], chan_1.2);
assert_eq!(local_txn.len(), 1);
assert_eq!(local_txn[0].input.len(), 1);
mine_transaction(&nodes[1], &local_txn[0]);
check_added_monitors!(nodes[1], 1);
- check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [nodes[0].node.get_our_node_id()], 100000);
let events = nodes[1].node.get_and_clear_pending_msg_events();
match events[0] {
MessageSendEvent::UpdateHTLCs { .. } => {},
assert_eq!(get_local_commitment_txn!(nodes[3], chan_2_3.2)[0].output.len(), 2);
let ds_dust_limit = nodes[3].node.per_peer_state.read().unwrap().get(&nodes[2].node.get_our_node_id())
- .unwrap().lock().unwrap().channel_by_id.get(&chan_2_3.2).unwrap().context.holder_dust_limit_satoshis;
+ .unwrap().lock().unwrap().channel_by_id.get(&chan_2_3.2).unwrap().context().holder_dust_limit_satoshis;
// 0th HTLC:
- let (_, payment_hash_1, _) = route_payment(&nodes[0], &[&nodes[2], &nodes[3], &nodes[4]], ds_dust_limit*1000); // not added < dust limit + HTLC tx fee
+ let (_, payment_hash_1, ..) = route_payment(&nodes[0], &[&nodes[2], &nodes[3], &nodes[4]], ds_dust_limit*1000); // not added < dust limit + HTLC tx fee
// 1st HTLC:
- let (_, payment_hash_2, _) = route_payment(&nodes[0], &[&nodes[2], &nodes[3], &nodes[4]], ds_dust_limit*1000); // not added < dust limit + HTLC tx fee
+ let (_, payment_hash_2, ..) = route_payment(&nodes[0], &[&nodes[2], &nodes[3], &nodes[4]], ds_dust_limit*1000); // not added < dust limit + HTLC tx fee
let (route, _, _, _) = get_route_and_payment_hash!(nodes[1], nodes[5], ds_dust_limit*1000);
// 2nd HTLC:
send_along_route_with_secret(&nodes[1], route.clone(), &[&[&nodes[2], &nodes[3], &nodes[5]]], ds_dust_limit*1000, payment_hash_1, nodes[5].node.create_inbound_payment_for_hash(payment_hash_1, None, 7200, None).unwrap()); // not added < dust limit + HTLC tx fee
// 3rd HTLC:
send_along_route_with_secret(&nodes[1], route, &[&[&nodes[2], &nodes[3], &nodes[5]]], ds_dust_limit*1000, payment_hash_2, nodes[5].node.create_inbound_payment_for_hash(payment_hash_2, None, 7200, None).unwrap()); // not added < dust limit + HTLC tx fee
// 4th HTLC:
- let (_, payment_hash_3, _) = route_payment(&nodes[0], &[&nodes[2], &nodes[3], &nodes[4]], 1000000);
+ let (_, payment_hash_3, ..) = route_payment(&nodes[0], &[&nodes[2], &nodes[3], &nodes[4]], 1000000);
// 5th HTLC:
- let (_, payment_hash_4, _) = route_payment(&nodes[0], &[&nodes[2], &nodes[3], &nodes[4]], 1000000);
+ let (_, payment_hash_4, ..) = route_payment(&nodes[0], &[&nodes[2], &nodes[3], &nodes[4]], 1000000);
let (route, _, _, _) = get_route_and_payment_hash!(nodes[1], nodes[5], 1000000);
// 6th HTLC:
send_along_route_with_secret(&nodes[1], route.clone(), &[&[&nodes[2], &nodes[3], &nodes[5]]], 1000000, payment_hash_3, nodes[5].node.create_inbound_payment_for_hash(payment_hash_3, None, 7200, None).unwrap());
send_along_route_with_secret(&nodes[1], route, &[&[&nodes[2], &nodes[3], &nodes[5]]], 1000000, payment_hash_4, nodes[5].node.create_inbound_payment_for_hash(payment_hash_4, None, 7200, None).unwrap());
// 8th HTLC:
- let (_, payment_hash_5, _) = route_payment(&nodes[0], &[&nodes[2], &nodes[3], &nodes[4]], 1000000);
+ let (_, payment_hash_5, ..) = route_payment(&nodes[0], &[&nodes[2], &nodes[3], &nodes[4]], 1000000);
// 9th HTLC:
let (route, _, _, _) = get_route_and_payment_hash!(nodes[1], nodes[5], ds_dust_limit*1000);
send_along_route_with_secret(&nodes[1], route, &[&[&nodes[2], &nodes[3], &nodes[5]]], ds_dust_limit*1000, payment_hash_5, nodes[5].node.create_inbound_payment_for_hash(payment_hash_5, None, 7200, None).unwrap()); // not added < dust limit + HTLC tx fee
// 10th HTLC:
- let (_, payment_hash_6, _) = route_payment(&nodes[0], &[&nodes[2], &nodes[3], &nodes[4]], ds_dust_limit*1000); // not added < dust limit + HTLC tx fee
+ let (_, payment_hash_6, ..) = route_payment(&nodes[0], &[&nodes[2], &nodes[3], &nodes[4]], ds_dust_limit*1000); // not added < dust limit + HTLC tx fee
// 11th HTLC:
let (route, _, _, _) = get_route_and_payment_hash!(nodes[1], nodes[5], 1000000);
send_along_route_with_secret(&nodes[1], route, &[&[&nodes[2], &nodes[3], &nodes[5]]], 1000000, payment_hash_6, nodes[5].node.create_inbound_payment_for_hash(payment_hash_6, None, 7200, None).unwrap());
// Create some initial channels
let chan_1 = create_announced_chan_between_nodes(&nodes, 0, 1);
- let (_, our_payment_hash, _) = route_payment(&nodes[0], &vec!(&nodes[1])[..], 9000000);
+ let (_, our_payment_hash, ..) = route_payment(&nodes[0], &vec!(&nodes[1])[..], 9000000);
let local_txn = get_local_commitment_txn!(nodes[0], chan_1.2);
assert_eq!(local_txn[0].input.len(), 1);
check_spends!(local_txn[0], chan_1.3);
mine_transaction(&nodes[0], &local_txn[0]);
check_closed_broadcast!(nodes[0], true);
check_added_monitors!(nodes[0], 1);
- check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed, [nodes[1].node.get_our_node_id()], 100000);
connect_blocks(&nodes[0], TEST_FINAL_CLTV); // Confirm blocks until the HTLC expires
let htlc_timeout = {
let keys_manager = test_utils::TestKeysInterface::new(&seed, Network::Testnet);
let chain_monitor = test_utils::TestChainMonitor::new(Some(&chanmon_cfgs[0].chain_source), &chanmon_cfgs[0].tx_broadcaster, &chanmon_cfgs[0].logger, &chanmon_cfgs[0].fee_estimator, &chanmon_cfgs[0].persister, &keys_manager);
let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &chanmon_cfgs[0].logger));
- let scorer = Mutex::new(test_utils::TestScorer::new());
+ let scorer = RwLock::new(test_utils::TestScorer::new());
let router = test_utils::TestRouter::new(network_graph.clone(), &scorer);
let node = NodeCfg { chain_source: &chanmon_cfgs[0].chain_source, logger: &chanmon_cfgs[0].logger, tx_broadcaster: &chanmon_cfgs[0].tx_broadcaster, fee_estimator: &chanmon_cfgs[0].fee_estimator, router, chain_monitor, keys_manager: &keys_manager, network_graph, node_seed: seed, override_init_features: alloc::rc::Rc::new(core::cell::RefCell::new(None)) };
let mut node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
connect_blocks(&nodes[1], node_max_height - nodes[1].best_block_info().1);
connect_blocks(&nodes[2], node_max_height - nodes[2].best_block_info().1);
- let (_, our_payment_hash, _) = route_payment(&nodes[0], &vec!(&nodes[1])[..], 9000000);
+ let (_, our_payment_hash, ..) = route_payment(&nodes[0], &vec!(&nodes[1])[..], 9000000);
let local_txn_0 = get_local_commitment_txn!(nodes[0], chan_0.2);
let local_txn_1 = get_local_commitment_txn!(nodes[0], chan_1.2);
assert_eq!(local_txn_1[0].input.len(), 1);
connect_blocks(&nodes[0], TEST_FINAL_CLTV); // Confirm blocks until the HTLC expires
check_closed_broadcast!(nodes[0], true);
check_added_monitors!(nodes[0], 1);
- check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed, [nodes[1].node.get_our_node_id()], 100000);
let htlc_timeout = {
let node_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap();
let closing_tx = close_channel(&nodes[0], &nodes[1], &chan.2, chan.3, true).2;
mine_transaction(&nodes[0], &closing_tx);
- check_closed_event!(nodes[0], 1, ClosureReason::CooperativeClosure);
+ check_closed_event!(nodes[0], 1, ClosureReason::CooperativeClosure, [nodes[1].node.get_our_node_id()], 100000);
connect_blocks(&nodes[0], ANTI_REORG_DELAY - 1);
let spend_txn = check_spendable_outputs!(nodes[0], node_cfgs[0].keys_manager);
check_spends!(spend_txn[0], closing_tx);
mine_transaction(&nodes[1], &closing_tx);
- check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure);
+ check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure, [nodes[0].node.get_our_node_id()], 100000);
connect_blocks(&nodes[1], ANTI_REORG_DELAY - 1);
let spend_txn = check_spendable_outputs!(nodes[1], node_cfgs[1].keys_manager);
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
let chan = create_announced_chan_between_nodes(&nodes, 0, 1);
- let (payment_preimage, payment_hash, _) = route_payment(&nodes[0], &[&nodes[1]], if use_dust { 50000 } else { 3_000_000 });
+ let (payment_preimage, payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1]], if use_dust { 50000 } else { 3_000_000 });
// Claim the payment, but don't deliver A's commitment_signed, resulting in the HTLC only being
// present in B's local commitment transaction, but none of A's commitment transactions.
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]);
- expect_payment_sent_without_paths!(nodes[0], payment_preimage);
+ expect_payment_sent(&nodes[0], payment_preimage, None, false, false);
nodes[0].node.handle_commitment_signed(&nodes[1].node.get_our_node_id(), &bs_updates.commitment_signed);
check_added_monitors!(nodes[0], 1);
test_txn_broadcast(&nodes[1], &chan, None, if use_dust { HTLCType::NONE } else { HTLCType::SUCCESS });
check_closed_broadcast!(nodes[1], true);
check_added_monitors!(nodes[1], 1);
- check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[1], 1, ClosureReason::HolderForceClosed, [nodes[0].node.get_our_node_id()], 100000);
}
fn do_htlc_claim_current_remote_commitment_only(use_dust: bool) {
test_txn_broadcast(&nodes[0], &chan, None, HTLCType::NONE);
check_closed_broadcast!(nodes[0], true);
check_added_monitors!(nodes[0], 1);
- check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[0], 1, ClosureReason::HolderForceClosed, [nodes[1].node.get_our_node_id()], 100000);
}
fn do_htlc_claim_previous_remote_commitment_only(use_dust: bool, check_revoke_no_close: bool) {
// Also optionally test that we *don't* fail the channel in case the commitment transaction was
// actually revoked.
let htlc_value = if use_dust { 50000 } else { 3000000 };
- let (_, our_payment_hash, _) = route_payment(&nodes[0], &[&nodes[1]], htlc_value);
+ let (_, our_payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1]], htlc_value);
nodes[1].node.fail_htlc_backwards(&our_payment_hash);
expect_pending_htlcs_forwardable_and_htlc_handling_failed!(nodes[1], vec![HTLCDestination::FailedPayment { payment_hash: our_payment_hash }]);
check_added_monitors!(nodes[1], 1);
test_txn_broadcast(&nodes[0], &chan, None, HTLCType::NONE);
check_closed_broadcast!(nodes[0], true);
check_added_monitors!(nodes[0], 1);
- check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[0], 1, ClosureReason::HolderForceClosed, [nodes[1].node.get_our_node_id()], 100000);
} else {
expect_payment_failed!(nodes[0], our_payment_hash, true);
}
// us to surface its failure to the user.
chan_stat = get_channel_value_stat!(nodes[0], nodes[1], chan.2);
assert_eq!(chan_stat.holding_cell_outbound_amount_msat, 0);
- nodes[0].logger.assert_log("lightning::ln::channel".to_string(), format!("Freeing holding cell with 1 HTLC updates in channel {}", hex::encode(chan.2)), 1);
+ nodes[0].logger.assert_log("lightning::ln::channel".to_string(), format!("Freeing holding cell with 1 HTLC updates in channel {}", chan.2), 1);
// Check that the payment failed to be sent out.
let events = nodes[0].node.get_and_clear_pending_events();
// to surface its failure to the user. The first payment should succeed.
chan_stat = get_channel_value_stat!(nodes[0], nodes[1], chan.2);
assert_eq!(chan_stat.holding_cell_outbound_amount_msat, 0);
- nodes[0].logger.assert_log("lightning::ln::channel".to_string(), format!("Freeing holding cell with 2 HTLC updates in channel {}", hex::encode(chan.2)), 1);
+ nodes[0].logger.assert_log("lightning::ln::channel".to_string(), format!("Freeing holding cell with 2 HTLC updates in channel {}", chan.2), 1);
// Check that the second payment failed to be sent out.
let events = nodes[0].node.get_and_clear_pending_events();
nodes[1].logger.assert_log("lightning::ln::channelmanager".to_string(), "Remote side tried to send a 0-msat HTLC".to_string(), 1);
check_closed_broadcast!(nodes[1], true).unwrap();
check_added_monitors!(nodes[1], 1);
- check_closed_event!(nodes[1], 1, ClosureReason::ProcessingError { err: "Remote side tried to send a 0-msat HTLC".to_string() });
+ check_closed_event!(nodes[1], 1, ClosureReason::ProcessingError { err: "Remote side tried to send a 0-msat HTLC".to_string() },
+ [nodes[0].node.get_our_node_id()], 100000);
}
#[test]
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
let chan = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1000000, 0);
let max_accepted_htlcs = nodes[1].node.per_peer_state.read().unwrap().get(&nodes[0].node.get_our_node_id())
- .unwrap().lock().unwrap().channel_by_id.get(&chan.2).unwrap().context.counterparty_max_accepted_htlcs as u64;
+ .unwrap().lock().unwrap().channel_by_id.get(&chan.2).unwrap().context().counterparty_max_accepted_htlcs as u64;
// Fetch a route in advance as we will be unable to once we're unable to send.
let (route, our_payment_hash, _, our_payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[1], 100000);
let per_peer_state = nodes[0].node.per_peer_state.read().unwrap();
let chan_lock = per_peer_state.get(&nodes[1].node.get_our_node_id()).unwrap().lock().unwrap();
let channel = chan_lock.channel_by_id.get(&chan.2).unwrap();
- htlc_minimum_msat = channel.context.get_holder_htlc_minimum_msat();
+ htlc_minimum_msat = channel.context().get_holder_htlc_minimum_msat();
}
let (route, our_payment_hash, _, our_payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[1], htlc_minimum_msat);
let err_msg = check_closed_broadcast!(nodes[1], true).unwrap();
assert!(regex::Regex::new(r"Remote side tried to send less than our minimum HTLC value\. Lower limit: \(\d+\)\. Actual: \(\d+\)").unwrap().is_match(err_msg.data.as_str()));
check_added_monitors!(nodes[1], 1);
- check_closed_event!(nodes[1], 1, ClosureReason::ProcessingError { err: err_msg.data });
+ check_closed_event!(nodes[1], 1, ClosureReason::ProcessingError { err: err_msg.data }, [nodes[0].node.get_our_node_id()], 100000);
}
#[test]
let err_msg = check_closed_broadcast!(nodes[1], true).unwrap();
assert_eq!(err_msg.data, "Remote HTLC add would put them under remote reserve value");
check_added_monitors!(nodes[1], 1);
- check_closed_event!(nodes[1], 1, ClosureReason::ProcessingError { err: err_msg.data });
+ check_closed_event!(nodes[1], 1, ClosureReason::ProcessingError { err: err_msg.data }, [nodes[0].node.get_our_node_id()], 100000);
}
#[test]
let err_msg = check_closed_broadcast!(nodes[1], true).unwrap();
assert!(regex::Regex::new(r"Remote tried to push more than our max accepted HTLCs \(\d+\)").unwrap().is_match(err_msg.data.as_str()));
check_added_monitors!(nodes[1], 1);
- check_closed_event!(nodes[1], 1, ClosureReason::ProcessingError { err: err_msg.data });
+ check_closed_event!(nodes[1], 1, ClosureReason::ProcessingError { err: err_msg.data }, [nodes[0].node.get_our_node_id()], 100000);
}
#[test]
let err_msg = check_closed_broadcast!(nodes[1], true).unwrap();
assert!(regex::Regex::new("Remote HTLC add would put them over our max HTLC value").unwrap().is_match(err_msg.data.as_str()));
check_added_monitors!(nodes[1], 1);
- check_closed_event!(nodes[1], 1, ClosureReason::ProcessingError { err: err_msg.data });
+ check_closed_event!(nodes[1], 1, ClosureReason::ProcessingError { err: err_msg.data }, [nodes[0].node.get_our_node_id()], 1000000);
}
#[test]
let err_msg = check_closed_broadcast!(nodes[1], true).unwrap();
assert_eq!(err_msg.data,"Remote provided CLTV expiry in seconds instead of block height");
check_added_monitors!(nodes[1], 1);
- check_closed_event!(nodes[1], 1, ClosureReason::ProcessingError { err: err_msg.data });
+ check_closed_event!(nodes[1], 1, ClosureReason::ProcessingError { err: err_msg.data }, [nodes[0].node.get_our_node_id()], 100000);
}
#[test]
let err_msg = check_closed_broadcast!(nodes[1], true).unwrap();
assert!(regex::Regex::new(r"Remote skipped HTLC ID \(skipped ID: \d+\)").unwrap().is_match(err_msg.data.as_str()));
check_added_monitors!(nodes[1], 1);
- check_closed_event!(nodes[1], 1, ClosureReason::ProcessingError { err: err_msg.data });
+ check_closed_event!(nodes[1], 1, ClosureReason::ProcessingError { err: err_msg.data }, [nodes[0].node.get_our_node_id()], 100000);
}
#[test]
let err_msg = check_closed_broadcast!(nodes[0], true).unwrap();
assert!(regex::Regex::new(r"Remote tried to fulfill/fail HTLC \(\d+\) before it had been committed").unwrap().is_match(err_msg.data.as_str()));
check_added_monitors!(nodes[0], 1);
- check_closed_event!(nodes[0], 1, ClosureReason::ProcessingError { err: err_msg.data });
+ check_closed_event!(nodes[0], 1, ClosureReason::ProcessingError { err: err_msg.data }, [nodes[1].node.get_our_node_id()], 100000);
}
#[test]
let err_msg = check_closed_broadcast!(nodes[0], true).unwrap();
assert!(regex::Regex::new(r"Remote tried to fulfill/fail HTLC \(\d+\) before it had been committed").unwrap().is_match(err_msg.data.as_str()));
check_added_monitors!(nodes[0], 1);
- check_closed_event!(nodes[0], 1, ClosureReason::ProcessingError { err: err_msg.data });
+ check_closed_event!(nodes[0], 1, ClosureReason::ProcessingError { err: err_msg.data }, [nodes[1].node.get_our_node_id()], 100000);
}
#[test]
let err_msg = check_closed_broadcast!(nodes[0], true).unwrap();
assert!(regex::Regex::new(r"Remote tried to fulfill/fail HTLC \(\d+\) before it had been committed").unwrap().is_match(err_msg.data.as_str()));
check_added_monitors!(nodes[0], 1);
- check_closed_event!(nodes[0], 1, ClosureReason::ProcessingError { err: err_msg.data });
+ check_closed_event!(nodes[0], 1, ClosureReason::ProcessingError { err: err_msg.data }, [nodes[1].node.get_our_node_id()], 100000);
}
#[test]
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
create_announced_chan_between_nodes(&nodes, 0, 1);
- let (our_payment_preimage, our_payment_hash, _) = route_payment(&nodes[0], &[&nodes[1]], 100_000);
+ let (our_payment_preimage, our_payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1]], 100_000);
nodes[1].node.claim_funds(our_payment_preimage);
check_added_monitors!(nodes[1], 1);
let err_msg = check_closed_broadcast!(nodes[0], true).unwrap();
assert_eq!(err_msg.data, "Remote tried to fulfill/fail an HTLC we couldn't find");
check_added_monitors!(nodes[0], 1);
- check_closed_event!(nodes[0], 1, ClosureReason::ProcessingError { err: err_msg.data });
+ check_closed_event!(nodes[0], 1, ClosureReason::ProcessingError { err: err_msg.data }, [nodes[1].node.get_our_node_id()], 100000);
}
#[test]
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
create_announced_chan_between_nodes(&nodes, 0, 1);
- let (our_payment_preimage, our_payment_hash, _) = route_payment(&nodes[0], &[&nodes[1]], 100_000);
+ let (our_payment_preimage, our_payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1]], 100_000);
nodes[1].node.claim_funds(our_payment_preimage);
check_added_monitors!(nodes[1], 1);
let err_msg = check_closed_broadcast!(nodes[0], true).unwrap();
assert!(regex::Regex::new(r"Remote tried to fulfill HTLC \(\d+\) with an incorrect preimage").unwrap().is_match(err_msg.data.as_str()));
check_added_monitors!(nodes[0], 1);
- check_closed_event!(nodes[0], 1, ClosureReason::ProcessingError { err: err_msg.data });
+ check_closed_event!(nodes[0], 1, ClosureReason::ProcessingError { err: err_msg.data }, [nodes[1].node.get_our_node_id()], 100000);
}
#[test]
let err_msg = check_closed_broadcast!(nodes[0], true).unwrap();
assert_eq!(err_msg.data, "Got update_fail_malformed_htlc with BADONION not set");
check_added_monitors!(nodes[0], 1);
- check_closed_event!(nodes[0], 1, ClosureReason::ProcessingError { err: err_msg.data });
+ check_closed_event!(nodes[0], 1, ClosureReason::ProcessingError { err: err_msg.data }, [nodes[1].node.get_our_node_id()], 1000000);
}
#[test]
let chan =create_announced_chan_between_nodes(&nodes, 0, 1);
let bs_dust_limit = nodes[1].node.per_peer_state.read().unwrap().get(&nodes[0].node.get_our_node_id())
- .unwrap().lock().unwrap().channel_by_id.get(&chan.2).unwrap().context.holder_dust_limit_satoshis;
+ .unwrap().lock().unwrap().channel_by_id.get(&chan.2).unwrap().context().holder_dust_limit_satoshis;
// We route 2 dust-HTLCs between A and B
- let (_, payment_hash_1, _) = route_payment(&nodes[0], &[&nodes[1]], bs_dust_limit*1000);
- let (_, payment_hash_2, _) = route_payment(&nodes[0], &[&nodes[1]], bs_dust_limit*1000);
+ let (_, payment_hash_1, ..) = route_payment(&nodes[0], &[&nodes[1]], bs_dust_limit*1000);
+ let (_, payment_hash_2, ..) = route_payment(&nodes[0], &[&nodes[1]], bs_dust_limit*1000);
route_payment(&nodes[0], &[&nodes[1]], 1000000);
// Cache one local commitment tx as previous
check_closed_broadcast!(nodes[0], true);
check_added_monitors!(nodes[0], 1);
- check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed, [nodes[1].node.get_our_node_id()], 100000);
assert_eq!(nodes[0].node.get_and_clear_pending_events().len(), 0);
connect_blocks(&nodes[0], ANTI_REORG_DELAY - 1);
let chan = create_announced_chan_between_nodes(&nodes, 0, 1);
let bs_dust_limit = nodes[1].node.per_peer_state.read().unwrap().get(&nodes[0].node.get_our_node_id())
- .unwrap().lock().unwrap().channel_by_id.get(&chan.2).unwrap().context.holder_dust_limit_satoshis;
+ .unwrap().lock().unwrap().channel_by_id.get(&chan.2).unwrap().context().holder_dust_limit_satoshis;
- let (_payment_preimage_1, dust_hash, _payment_secret_1) = route_payment(&nodes[0], &[&nodes[1]], bs_dust_limit*1000);
- let (_payment_preimage_2, non_dust_hash, _payment_secret_2) = route_payment(&nodes[0], &[&nodes[1]], 1000000);
+ let (_payment_preimage_1, dust_hash, ..) = route_payment(&nodes[0], &[&nodes[1]], bs_dust_limit*1000);
+ let (_payment_preimage_2, non_dust_hash, ..) = route_payment(&nodes[0], &[&nodes[1]], 1000000);
let as_commitment_tx = get_local_commitment_txn!(nodes[0], chan.2);
let bs_commitment_tx = get_local_commitment_txn!(nodes[1], chan.2);
// We revoked bs_commitment_tx
if revoked {
- let (payment_preimage_3, _, _) = route_payment(&nodes[0], &[&nodes[1]], 1000000);
+ let (payment_preimage_3, ..) = route_payment(&nodes[0], &[&nodes[1]], 1000000);
claim_payment(&nodes[0], &vec!(&nodes[1])[..], payment_preimage_3);
}
if local {
// We fail dust-HTLC 1 by broadcast of local commitment tx
mine_transaction(&nodes[0], &as_commitment_tx[0]);
- check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed, [nodes[1].node.get_our_node_id()], 100000);
connect_blocks(&nodes[0], ANTI_REORG_DELAY - 1);
expect_payment_failed!(nodes[0], dust_hash, false);
mine_transaction(&nodes[0], &bs_commitment_tx[0]);
check_closed_broadcast!(nodes[0], true);
check_added_monitors!(nodes[0], 1);
- check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed, [nodes[1].node.get_our_node_id()], 100000);
assert_eq!(nodes[0].node.get_and_clear_pending_events().len(), 0);
connect_blocks(&nodes[0], TEST_FINAL_CLTV); // Confirm blocks until the HTLC expires
open_channel.to_self_delay = 200;
if let Err(error) = InboundV1Channel::new(&LowerBoundedFeeEstimator::new(&test_utils::TestFeeEstimator { sat_per_kw: Mutex::new(253) }),
&nodes[0].keys_manager, &nodes[0].keys_manager, nodes[1].node.get_our_node_id(), &nodes[0].node.channel_type_features(), &nodes[1].node.init_features(), &open_channel, 0,
- &low_our_to_self_config, 0, &nodes[0].logger, 42)
+ &low_our_to_self_config, 0, &nodes[0].logger, /*is_0conf=*/false)
{
match error {
ChannelError::Close(err) => { assert!(regex::Regex::new(r"Configured with an unreasonable our_to_self_delay \(\d+\) putting user funds at risks").unwrap().is_match(err.as_str())); },
_ => { panic!(); }
}
} else { panic!(); }
- check_closed_event!(nodes[0], 1, ClosureReason::ProcessingError { err: reason_msg });
+ check_closed_event!(nodes[0], 1, ClosureReason::ProcessingError { err: reason_msg }, [nodes[1].node.get_our_node_id()], 1000000);
// We test msg.to_self_delay <= config.their_to_self_delay is enforced in InboundV1Channel::new()
nodes[1].node.create_channel(nodes[0].node.get_our_node_id(), 1000000, 1000000, 42, None).unwrap();
open_channel.to_self_delay = 200;
if let Err(error) = InboundV1Channel::new(&LowerBoundedFeeEstimator::new(&test_utils::TestFeeEstimator { sat_per_kw: Mutex::new(253) }),
&nodes[0].keys_manager, &nodes[0].keys_manager, nodes[1].node.get_our_node_id(), &nodes[0].node.channel_type_features(), &nodes[1].node.init_features(), &open_channel, 0,
- &high_their_to_self_config, 0, &nodes[0].logger, 42)
+ &high_their_to_self_config, 0, &nodes[0].logger, /*is_0conf=*/false)
{
match error {
ChannelError::Close(err) => { assert!(regex::Regex::new(r"They wanted our payments to be delayed by a needlessly long period\. Upper limit: \d+\. Actual: \d+").unwrap().is_match(err.as_str())); },
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 route = get_route(&nodes[0].node.get_our_node_id(), &payment_params, &nodes[0].network_graph.read_only(), None, 10_000, nodes[0].logger, &scorer, &(), &random_seed_bytes).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();
+ 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 (_, our_payment_hash, _) = get_payment_preimage_hash!(nodes[0]);
let our_payment_secret = nodes[1].node.create_inbound_payment_for_hash(our_payment_hash, Some(100_000), 7200, None).unwrap();
nodes[0].node.send_payment_with_route(&route, our_payment_hash,
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 scorer = test_utils::TestScorer::new();
let random_seed_bytes = chanmon_cfgs[1].keys_manager.get_secure_random_bytes();
- let route = get_route(&nodes[0].node.get_our_node_id(), &payment_params, &nodes[0].network_graph.read_only(), None,
- 3_000_000, nodes[0].logger, &scorer, &(), &random_seed_bytes).unwrap();
+ 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 route = get_route(&nodes[1].node.get_our_node_id(), &payment_params, &nodes[1].network_graph.read_only(), None,
- 3_000_000, nodes[0].logger, &scorer, &(), &random_seed_bytes).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();
send_along_route(&nodes[1], route, &[&nodes[0]], 3_000_000);
let revoked_local_txn = get_local_commitment_txn!(nodes[1], chan.2);
connect_block(&nodes[1], &create_dummy_block(nodes[1].best_block_hash(), 42, vec![revoked_local_txn[0].clone()]));
check_closed_broadcast!(nodes[1], true);
check_added_monitors!(nodes[1], 1);
- check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [nodes[0].node.get_our_node_id()], 1000000);
connect_blocks(&nodes[1], 50); // Confirm blocks until the HTLC expires (note CLTV was explicitly 50 above)
let revoked_htlc_txn = {
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
let chan = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1000000, 59000000);
- let (payment_preimage, payment_hash, _) = route_payment(&nodes[0], &[&nodes[1]], 3_000_000);
+ let (payment_preimage, payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1]], 3_000_000);
route_payment(&nodes[1], &vec!(&nodes[0])[..], 3000000).0;
// Remote commitment txn with 4 outputs : to_local, to_remote, 1 outgoing HTLC, 1 incoming HTLC
// commitment transaction, we would have happily carried on and provided them the next
// commitment transaction based on one RAA forward. This would probably eventually have led to
// channel closure, but it would not have resulted in funds loss. Still, our
- // EnforcingSigner would have panicked as it doesn't like jumps into the future. Here, we
+ // TestChannelSigner would have panicked as it doesn't like jumps into the future. Here, we
// check simply that the channel is closed in response to such an RAA, but don't check whether
// we decide to punish our counterparty for revoking their funds (as we don't currently
// implement that).
{
let per_peer_state = nodes[0].node.per_peer_state.read().unwrap();
let mut guard = per_peer_state.get(&nodes[1].node.get_our_node_id()).unwrap().lock().unwrap();
- let keys = guard.channel_by_id.get_mut(&channel_id).unwrap().get_signer();
+ let keys = guard.channel_by_id.get_mut(&channel_id).map(
+ |phase| if let ChannelPhase::Funded(chan) = phase { Some(chan) } else { None }
+ ).flatten().unwrap().get_signer();
const INITIAL_COMMITMENT_NUMBER: u64 = (1 << 48) - 1;
// Make signer believe we got a counterparty signature, so that it allows the revocation
- keys.get_enforcement_state().last_holder_commitment -= 1;
- per_commitment_secret = keys.release_commitment_secret(INITIAL_COMMITMENT_NUMBER);
+ keys.as_ecdsa().unwrap().get_enforcement_state().last_holder_commitment -= 1;
+ per_commitment_secret = keys.as_ref().release_commitment_secret(INITIAL_COMMITMENT_NUMBER);
// Must revoke without gaps
- keys.get_enforcement_state().last_holder_commitment -= 1;
- keys.release_commitment_secret(INITIAL_COMMITMENT_NUMBER - 1);
+ keys.as_ecdsa().unwrap().get_enforcement_state().last_holder_commitment -= 1;
+ keys.as_ref().release_commitment_secret(INITIAL_COMMITMENT_NUMBER - 1);
- keys.get_enforcement_state().last_holder_commitment -= 1;
+ keys.as_ecdsa().unwrap().get_enforcement_state().last_holder_commitment -= 1;
next_per_commitment_point = PublicKey::from_secret_key(&Secp256k1::new(),
- &SecretKey::from_slice(&keys.release_commitment_secret(INITIAL_COMMITMENT_NUMBER - 2)).unwrap());
+ &SecretKey::from_slice(&keys.as_ref().release_commitment_secret(INITIAL_COMMITMENT_NUMBER - 2)).unwrap());
}
nodes[1].node.handle_revoke_and_ack(&nodes[0].node.get_our_node_id(),
});
assert_eq!(check_closed_broadcast!(nodes[1], true).unwrap().data, "Received an unexpected revoke_and_ack");
check_added_monitors!(nodes[1], 1);
- check_closed_event!(nodes[1], 1, ClosureReason::ProcessingError { err: "Received an unexpected revoke_and_ack".to_string() });
+ check_closed_event!(nodes[1], 1, ClosureReason::ProcessingError { err: "Received an unexpected revoke_and_ack".to_string() }
+ , [nodes[0].node.get_our_node_id()], 100000);
}
#[test]
let chan = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1000000, 59000000);
// Lock HTLC in both directions
- let (payment_preimage_1, _, _) = route_payment(&nodes[0], &vec!(&nodes[1])[..], 9_000_000);
- let (_, payment_hash_2, _) = route_payment(&nodes[1], &vec!(&nodes[0])[..], 9_000_000);
+ let (payment_preimage_1, ..) = route_payment(&nodes[0], &vec!(&nodes[1])[..], 9_000_000);
+ let (_, payment_hash_2, ..) = route_payment(&nodes[1], &vec!(&nodes[0])[..], 9_000_000);
let revoked_local_txn = get_local_commitment_txn!(nodes[1], chan.2);
assert_eq!(revoked_local_txn[0].input.len(), 1);
mine_transaction(&nodes[0], &revoked_local_txn[0]);
check_closed_broadcast!(nodes[0], true);
check_added_monitors!(nodes[0], 1);
- check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed, [nodes[1].node.get_our_node_id()], 1000000);
let penalty_txn = {
let mut node_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap();
assert_eq!(node_txn.len(), 3); //ChannelMonitor: justice txn * 3
connect_blocks(&nodes[1], 1);
check_added_monitors!(nodes[1], 1);
- check_closed_event!(nodes[1], 1, ClosureReason::FundingTimedOut);
+ check_closed_event!(nodes[1], 1, ClosureReason::FundingTimedOut, [nodes[0].node.get_our_node_id()], 1000000);
let close_ev = nodes[1].node.get_and_clear_pending_msg_events();
assert_eq!(close_ev.len(), 1);
match close_ev[0] {
}
_ => panic!("Unexpected event"),
}
- check_closed_event!(nodes[1], 1, ClosureReason::HolderForceClosed);
-}
-
-#[test]
-fn test_reject_funding_before_inbound_channel_accepted() {
- // This tests that when `UserConfig::manually_accept_inbound_channels` is set to true, inbound
- // channels must to be manually accepted through `ChannelManager::accept_inbound_channel` by
- // the node operator before the counterparty sends a `FundingCreated` message. If a
- // `FundingCreated` message is received before the channel is accepted, it should be rejected
- // and the channel should be closed.
- let mut manually_accept_conf = UserConfig::default();
- manually_accept_conf.manually_accept_inbound_channels = true;
- let chanmon_cfgs = create_chanmon_cfgs(2);
- let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
- let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, Some(manually_accept_conf.clone())]);
- let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
-
- nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 100000, 10001, 42, Some(manually_accept_conf)).unwrap();
- let res = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, nodes[1].node.get_our_node_id());
- let temp_channel_id = res.temporary_channel_id;
-
- nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &res);
-
- // Assert that `nodes[1]` has no `MessageSendEvent::SendAcceptChannel` in the `msg_events`.
- assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
-
- // Clear the `Event::OpenChannelRequest` event without responding to the request.
- nodes[1].node.get_and_clear_pending_events();
-
- // Get the `AcceptChannel` message of `nodes[1]` without calling
- // `ChannelManager::accept_inbound_channel`, which generates a
- // `MessageSendEvent::SendAcceptChannel` event. The message is passed to `nodes[0]`
- // `handle_accept_channel`, which is required in order for `create_funding_transaction` to
- // succeed when `nodes[0]` is passed to it.
- let accept_chan_msg = {
- let mut node_1_per_peer_lock;
- let mut node_1_peer_state_lock;
- let channel = get_inbound_v1_channel_ref!(&nodes[1], nodes[0], node_1_per_peer_lock, node_1_peer_state_lock, temp_channel_id);
- channel.get_accept_channel_message()
- };
- nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), &accept_chan_msg);
-
- let (temporary_channel_id, tx, _) = create_funding_transaction(&nodes[0], &nodes[1].node.get_our_node_id(), 100000, 42);
-
- nodes[0].node.funding_transaction_generated(&temporary_channel_id, &nodes[1].node.get_our_node_id(), tx.clone()).unwrap();
- let funding_created_msg = get_event_msg!(nodes[0], MessageSendEvent::SendFundingCreated, nodes[1].node.get_our_node_id());
-
- // The `funding_created_msg` should be rejected by `nodes[1]` as it hasn't accepted the channel
- nodes[1].node.handle_funding_created(&nodes[0].node.get_our_node_id(), &funding_created_msg);
-
- let close_msg_ev = nodes[1].node.get_and_clear_pending_msg_events();
- assert_eq!(close_msg_ev.len(), 1);
-
- let expected_err = "FundingCreated message received before the channel was accepted";
- match close_msg_ev[0] {
- MessageSendEvent::HandleError { action: ErrorAction::SendErrorMessage { ref msg }, ref node_id, } => {
- assert_eq!(msg.channel_id, temp_channel_id);
- assert_eq!(*node_id, nodes[0].node.get_our_node_id());
- assert_eq!(msg.data, expected_err);
- }
- _ => panic!("Unexpected event"),
- }
- check_closed_event!(nodes[1], 1, ClosureReason::ProcessingError { err: expected_err.to_string() });
+ // There should be no more events to process, as the channel was never opened.
+ assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
}
#[test]
let api_res = nodes[1].node.accept_inbound_channel(&temporary_channel_id, &nodes[0].node.get_our_node_id(), 0);
match api_res {
Err(APIError::APIMisuseError { err }) => {
- assert_eq!(err, "The channel isn't currently awaiting to be accepted.");
+ assert_eq!(err, "No such channel awaiting to be accepted.");
},
Ok(_) => panic!("Channel shouldn't be possible to be accepted twice"),
- Err(_) => panic!("Unexpected Error"),
+ Err(e) => panic!("Unexpected Error {:?}", e),
}
}
_ => panic!("Unexpected event"),
let node_chanmgr = create_node_chanmgrs(2, &node_cfg, &[None, None]);
let nodes = create_network(2, &node_cfg, &node_chanmgr);
- let unknown_channel_id = [0; 32];
+ let unknown_channel_id = ChannelId::new_zero();
let api_res = nodes[0].node.accept_inbound_channel(&unknown_channel_id, &nodes[1].node.get_our_node_id(), 0);
match api_res {
- Err(APIError::ChannelUnavailable { err }) => {
- assert_eq!(err, format!("Channel with id {} not found for the passed counterparty node_id {}", log_bytes!(unknown_channel_id), nodes[1].node.get_our_node_id()));
+ Err(APIError::APIMisuseError { err }) => {
+ assert_eq!(err, "No such channel awaiting to be accepted.");
},
Ok(_) => panic!("It shouldn't be possible to accept an unkown channel"),
- Err(_) => panic!("Unexpected Error"),
+ Err(e) => panic!("Unexpected Error: {:?}", e),
}
}
RecipientOnionFields::secret_only(our_payment_secret), height + 1, &None).unwrap();
// Edit amt_to_forward to simulate the sender having set
// the final amount and the routing node taking less fee
- onion_payloads[1].amt_to_forward = 99_000;
+ if let msgs::OutboundOnionPayload::Receive { ref mut amt_msat, .. } = onion_payloads[1] {
+ *amt_msat = 99_000;
+ } else { panic!() }
let new_onion_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &our_payment_hash).unwrap();
payment_event.msgs[0].onion_routing_packet = new_onion_packet;
}
send_payment(&nodes[0], &vec!(&nodes[1])[..], 10_000_000);
// Route a HTLC from node 0 to node 1 (but don't settle)
- let (preimage, payment_hash, _) = route_payment(&nodes[0], &[&nodes[1]], 9_000_000);
+ let (preimage, payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1]], 9_000_000);
// Copy ChainMonitor to simulate a watchtower and update block height of node 0 until its ChannelMonitor timeout HTLC onchain
let chain_source = test_utils::TestChainSource::new(Network::Testnet);
let watchtower = {
let new_monitor = {
let monitor = nodes[0].chain_monitor.chain_monitor.get_monitor(outpoint).unwrap();
- let new_monitor = <(BlockHash, channelmonitor::ChannelMonitor<EnforcingSigner>)>::read(
+ let new_monitor = <(BlockHash, channelmonitor::ChannelMonitor<TestChannelSigner>)>::read(
&mut io::Cursor::new(&monitor.encode()), (nodes[0].keys_manager, nodes[0].keys_manager)).unwrap().1;
assert!(new_monitor == *monitor);
new_monitor
};
let watchtower = test_utils::TestChainMonitor::new(Some(&chain_source), &chanmon_cfgs[0].tx_broadcaster, &logger, &chanmon_cfgs[0].fee_estimator, &persister, &node_cfgs[0].keys_manager);
- assert_eq!(watchtower.watch_channel(outpoint, new_monitor), ChannelMonitorUpdateStatus::Completed);
+ assert_eq!(watchtower.watch_channel(outpoint, new_monitor), Ok(ChannelMonitorUpdateStatus::Completed));
watchtower
};
let block = create_dummy_block(BlockHash::all_zeros(), 42, Vec::new());
{
let mut node_0_per_peer_lock;
let mut node_0_peer_state_lock;
- let mut channel = get_channel_ref!(nodes[0], nodes[1], node_0_per_peer_lock, node_0_peer_state_lock, chan_1.2);
- if let Ok(Some(update)) = channel.commitment_signed(&updates.commitment_signed, &node_cfgs[0].logger) {
- assert_eq!(watchtower.chain_monitor.update_channel(outpoint, &update), ChannelMonitorUpdateStatus::PermanentFailure);
- assert_eq!(nodes[0].chain_monitor.update_channel(outpoint, &update), ChannelMonitorUpdateStatus::Completed);
- } else { assert!(false); }
+ if let ChannelPhase::Funded(ref mut channel) = get_channel_ref!(nodes[0], nodes[1], node_0_per_peer_lock, node_0_peer_state_lock, chan_1.2) {
+ if let Ok(Some(update)) = channel.commitment_signed(&updates.commitment_signed, &node_cfgs[0].logger) {
+ assert_eq!(watchtower.chain_monitor.update_channel(outpoint, &update), ChannelMonitorUpdateStatus::InProgress);
+ assert_eq!(nodes[0].chain_monitor.update_channel(outpoint, &update), ChannelMonitorUpdateStatus::Completed);
+ } else { assert!(false); }
+ } else {
+ assert!(false);
+ }
}
// Our local monitor is in-sync and hasn't processed yet timeout
check_added_monitors!(nodes[0], 1);
let watchtower_alice = {
let new_monitor = {
let monitor = nodes[0].chain_monitor.chain_monitor.get_monitor(outpoint).unwrap();
- let new_monitor = <(BlockHash, channelmonitor::ChannelMonitor<EnforcingSigner>)>::read(
+ let new_monitor = <(BlockHash, channelmonitor::ChannelMonitor<TestChannelSigner>)>::read(
&mut io::Cursor::new(&monitor.encode()), (nodes[0].keys_manager, nodes[0].keys_manager)).unwrap().1;
assert!(new_monitor == *monitor);
new_monitor
};
let watchtower = test_utils::TestChainMonitor::new(Some(&chain_source), &alice_broadcaster, &logger, &chanmon_cfgs[0].fee_estimator, &persister, &node_cfgs[0].keys_manager);
- assert_eq!(watchtower.watch_channel(outpoint, new_monitor), ChannelMonitorUpdateStatus::Completed);
+ assert_eq!(watchtower.watch_channel(outpoint, new_monitor), Ok(ChannelMonitorUpdateStatus::Completed));
watchtower
};
let block = create_dummy_block(BlockHash::all_zeros(), 42, Vec::new());
let watchtower_bob = {
let new_monitor = {
let monitor = nodes[0].chain_monitor.chain_monitor.get_monitor(outpoint).unwrap();
- let new_monitor = <(BlockHash, channelmonitor::ChannelMonitor<EnforcingSigner>)>::read(
+ let new_monitor = <(BlockHash, channelmonitor::ChannelMonitor<TestChannelSigner>)>::read(
&mut io::Cursor::new(&monitor.encode()), (nodes[0].keys_manager, nodes[0].keys_manager)).unwrap().1;
assert!(new_monitor == *monitor);
new_monitor
};
let watchtower = test_utils::TestChainMonitor::new(Some(&chain_source), &bob_broadcaster, &logger, &chanmon_cfgs[0].fee_estimator, &persister, &node_cfgs[0].keys_manager);
- assert_eq!(watchtower.watch_channel(outpoint, new_monitor), ChannelMonitorUpdateStatus::Completed);
+ assert_eq!(watchtower.watch_channel(outpoint, new_monitor), Ok(ChannelMonitorUpdateStatus::Completed));
watchtower
};
watchtower_bob.chain_monitor.block_connected(&create_dummy_block(BlockHash::all_zeros(), 42, Vec::new()), HTLC_TIMEOUT_BROADCAST - 1);
{
let mut node_0_per_peer_lock;
let mut node_0_peer_state_lock;
- let mut channel = get_channel_ref!(nodes[0], nodes[1], node_0_per_peer_lock, node_0_peer_state_lock, chan_1.2);
- if let Ok(Some(update)) = channel.commitment_signed(&updates.commitment_signed, &node_cfgs[0].logger) {
- // Watchtower Alice should already have seen the block and reject the update
- assert_eq!(watchtower_alice.chain_monitor.update_channel(outpoint, &update), ChannelMonitorUpdateStatus::PermanentFailure);
- assert_eq!(watchtower_bob.chain_monitor.update_channel(outpoint, &update), ChannelMonitorUpdateStatus::Completed);
- assert_eq!(nodes[0].chain_monitor.update_channel(outpoint, &update), ChannelMonitorUpdateStatus::Completed);
- } else { assert!(false); }
+ if let ChannelPhase::Funded(ref mut channel) = get_channel_ref!(nodes[0], nodes[1], node_0_per_peer_lock, node_0_peer_state_lock, chan_1.2) {
+ if let Ok(Some(update)) = channel.commitment_signed(&updates.commitment_signed, &node_cfgs[0].logger) {
+ // Watchtower Alice should already have seen the block and reject the update
+ assert_eq!(watchtower_alice.chain_monitor.update_channel(outpoint, &update), ChannelMonitorUpdateStatus::InProgress);
+ assert_eq!(watchtower_bob.chain_monitor.update_channel(outpoint, &update), ChannelMonitorUpdateStatus::Completed);
+ assert_eq!(nodes[0].chain_monitor.update_channel(outpoint, &update), ChannelMonitorUpdateStatus::Completed);
+ } else { assert!(false); }
+ } else {
+ assert!(false);
+ }
}
// Our local monitor is in-sync and hasn't processed yet timeout
check_added_monitors!(nodes[0], 1);
let height = HTLC_TIMEOUT_BROADCAST + 1;
connect_blocks(&nodes[0], height - nodes[0].best_block_info().1);
check_closed_broadcast(&nodes[0], 1, true);
- check_closed_event(&nodes[0], 1, ClosureReason::CommitmentTxConfirmed, false);
+ check_closed_event!(&nodes[0], 1, ClosureReason::HolderForceClosed, false,
+ [nodes[1].node.get_our_node_id()], 100000);
watchtower_alice.chain_monitor.block_connected(&create_dummy_block(BlockHash::all_zeros(), 42, vec![bob_state_y.clone()]), height);
check_added_monitors(&nodes[0], 1);
{
let channel_id = crate::chain::transaction::OutPoint { txid: funding_created_msg.funding_txid, index: funding_created_msg.funding_output_index }.to_channel_id();
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);
+ check_closed_event!(nodes[0], 2, ClosureReason::CounterpartyForceClosed { peer_msg: UntrustedString("Hi".to_string()) }, true,
+ [nodes[1].node.get_our_node_id(); 2], 100000);
}
#[test]
let chan_1 = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100000, 10001);
send_payment(&nodes[0], &vec!(&nodes[1])[..], 1_000_000);
- let (_, our_payment_hash, _) = route_payment(&nodes[0], &vec!(&nodes[1])[..], 2_000_000);
+ let (_, our_payment_hash, ..) = route_payment(&nodes[0], &vec!(&nodes[1])[..], 2_000_000);
let local_txn = get_local_commitment_txn!(nodes[0], chan_1.2);
assert_eq!(local_txn[0].input.len(), 1);
assert_eq!(local_txn[0].output.len(), 3);
chain::Listen::block_connected(&nodes[0].chain_monitor.chain_monitor, &block, nodes[0].best_block_info().1 + 1);
check_closed_broadcast!(nodes[0], true);
check_added_monitors!(nodes[0], 1);
- check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed, [nodes[1].node.get_our_node_id()], 100000);
connect_blocks(&nodes[0], TEST_FINAL_CLTV);
let htlc_timeout = {
// Steps (1) and (2):
// Send an HTLC Alice --> Bob --> Carol, but Carol doesn't settle the HTLC back.
- let (payment_preimage, payment_hash, _payment_secret) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 3_000_000);
+ let (payment_preimage, payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 3_000_000);
// Check that Alice's commitment transaction now contains an output for this HTLC.
let alice_txn = get_local_commitment_txn!(nodes[0], chan_ab.2);
nodes[force_closing_node].node.force_close_broadcasting_latest_txn(&chan_ab.2, &nodes[counterparty_node].node.get_our_node_id()).unwrap();
check_closed_broadcast!(nodes[force_closing_node], true);
check_added_monitors!(nodes[force_closing_node], 1);
- check_closed_event!(nodes[force_closing_node], 1, ClosureReason::HolderForceClosed);
+ check_closed_event!(nodes[force_closing_node], 1, ClosureReason::HolderForceClosed, [nodes[counterparty_node].node.get_our_node_id()], 100000);
if go_onchain_before_fulfill {
let txn_to_broadcast = match broadcast_alice {
true => alice_txn.clone(),
if broadcast_alice {
check_closed_broadcast!(nodes[1], true);
check_added_monitors!(nodes[1], 1);
- check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [nodes[0].node.get_our_node_id()], 100000);
}
}
assert_eq!(carol_updates.update_fulfill_htlcs.len(), 1);
nodes[1].node.handle_update_fulfill_htlc(&nodes[2].node.get_our_node_id(), &carol_updates.update_fulfill_htlcs[0]);
- expect_payment_forwarded!(nodes[1], nodes[0], nodes[2], if go_onchain_before_fulfill || force_closing_node == 1 { None } else { Some(1000) }, false, false);
+ let went_onchain = go_onchain_before_fulfill || force_closing_node == 1;
+ expect_payment_forwarded!(nodes[1], nodes[0], nodes[2], if went_onchain { None } else { Some(1000) }, went_onchain, false);
// If Alice broadcasted but Bob doesn't know yet, here he prepares to tell her about the preimage.
if !go_onchain_before_fulfill && broadcast_alice {
let events = nodes[1].node.get_and_clear_pending_msg_events();
if broadcast_alice {
check_closed_broadcast!(nodes[1], true);
check_added_monitors!(nodes[1], 1);
- check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [nodes[0].node.get_our_node_id()], 100000);
}
let mut bob_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap();
if broadcast_alice {
let (_, funding_created) = {
let per_peer_state = nodes[0].node.per_peer_state.read().unwrap();
let mut a_peer_state = per_peer_state.get(&nodes[1].node.get_our_node_id()).unwrap().lock().unwrap();
- // Once we call `get_outbound_funding_created` the channel has a duplicate channel_id as
+ // Once we call `get_funding_created` the channel has a duplicate channel_id as
// another channel in the ChannelManager - an invalid state. Thus, we'd panic later when we
// try to create another channel. Instead, we drop the channel entirely here (leaving the
// channelmanager in a possibly nonsense state instead).
- let mut as_chan = a_peer_state.outbound_v1_channel_by_id.remove(&open_chan_2_msg.temporary_channel_id).unwrap();
- let logger = test_utils::TestLogger::new();
- as_chan.get_outbound_funding_created(tx.clone(), funding_outpoint, &&logger).map_err(|_| ()).unwrap()
+ match a_peer_state.channel_by_id.remove(&open_chan_2_msg.temporary_channel_id).unwrap() {
+ ChannelPhase::UnfundedOutboundV1(chan) => {
+ let logger = test_utils::TestLogger::new();
+ chan.get_funding_created(tx.clone(), funding_outpoint, false, &&logger).map_err(|_| ()).unwrap()
+ },
+ _ => panic!("Unexpected ChannelPhase variant"),
+ }
};
check_added_monitors!(nodes[0], 0);
nodes[1].node.handle_funding_created(&nodes[0].node.get_our_node_id(), &funding_created);
nodes[0].node.handle_error(&nodes[1].node.get_our_node_id(), &msgs::ErrorMessage { channel_id: chan_2.2, data: "ERR".to_owned() });
check_added_monitors!(nodes[0], 1);
check_closed_broadcast!(nodes[0], false);
- check_closed_event!(nodes[0], 1, ClosureReason::CounterpartyForceClosed { peer_msg: UntrustedString("ERR".to_string()) });
+ check_closed_event!(nodes[0], 1, ClosureReason::CounterpartyForceClosed { peer_msg: UntrustedString("ERR".to_string()) },
+ [nodes[1].node.get_our_node_id()], 100000);
assert_eq!(nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0).len(), 1);
assert_eq!(nodes[0].node.list_usable_channels().len(), 2);
assert!(nodes[0].node.list_usable_channels()[0].channel_id == chan_1.2 || nodes[0].node.list_usable_channels()[1].channel_id == chan_1.2);
// A null channel ID should close all channels
let _chan_4 = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100000, 10001);
- nodes[0].node.handle_error(&nodes[1].node.get_our_node_id(), &msgs::ErrorMessage { channel_id: [0; 32], data: "ERR".to_owned() });
+ nodes[0].node.handle_error(&nodes[1].node.get_our_node_id(), &msgs::ErrorMessage { channel_id: ChannelId::new_zero(), data: "ERR".to_owned() });
check_added_monitors!(nodes[0], 2);
- check_closed_event!(nodes[0], 2, ClosureReason::CounterpartyForceClosed { peer_msg: UntrustedString("ERR".to_string()) });
+ check_closed_event!(nodes[0], 2, ClosureReason::CounterpartyForceClosed { peer_msg: UntrustedString("ERR".to_string()) },
+ [nodes[1].node.get_our_node_id(); 2], 100000);
let events = nodes[0].node.get_and_clear_pending_msg_events();
assert_eq!(events.len(), 2);
match events[0] {
let expected_err = "funding tx had wrong script/value or output index";
confirm_transaction_at(&nodes[1], &tx, 1);
- check_closed_event!(nodes[1], 1, ClosureReason::ProcessingError { err: expected_err.to_string() });
+ check_closed_event!(nodes[1], 1, ClosureReason::ProcessingError { err: expected_err.to_string() },
+ [nodes[0].node.get_our_node_id()], 100000);
check_added_monitors!(nodes[1], 1);
let events_2 = nodes[1].node.get_and_clear_pending_msg_events();
assert_eq!(events_2.len(), 1);
mine_transaction(&nodes[1], &spend_tx);
}
+#[test]
+fn test_coinbase_funding_tx() {
+ // Miners are able to fund channels directly from coinbase transactions, however
+ // by consensus rules, outputs of a coinbase transaction are encumbered by a 100
+ // block maturity timelock. To ensure that a (non-0conf) channel like this is enforceable
+ // on-chain, the minimum depth is updated to 100 blocks for coinbase funding transactions.
+ //
+ // Note that 0conf channels with coinbase funding transactions are unaffected and are
+ // immediately operational after opening.
+ let chanmon_cfgs = create_chanmon_cfgs(2);
+ let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
+ let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
+ let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+
+ nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 100000, 10001, 42, None).unwrap();
+ let open_channel = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, nodes[1].node.get_our_node_id());
+
+ nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &open_channel);
+ let accept_channel = get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, nodes[0].node.get_our_node_id());
+
+ nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), &accept_channel);
+
+ // Create the coinbase funding transaction.
+ let (temporary_channel_id, tx, _) = create_coinbase_funding_transaction(&nodes[0], &nodes[1].node.get_our_node_id(), 100000, 42);
+
+ nodes[0].node.funding_transaction_generated(&temporary_channel_id, &nodes[1].node.get_our_node_id(), tx.clone()).unwrap();
+ check_added_monitors!(nodes[0], 0);
+ let funding_created = get_event_msg!(nodes[0], MessageSendEvent::SendFundingCreated, nodes[1].node.get_our_node_id());
+
+ nodes[1].node.handle_funding_created(&nodes[0].node.get_our_node_id(), &funding_created);
+ check_added_monitors!(nodes[1], 1);
+ expect_channel_pending_event(&nodes[1], &nodes[0].node.get_our_node_id());
+
+ let funding_signed = get_event_msg!(nodes[1], MessageSendEvent::SendFundingSigned, nodes[0].node.get_our_node_id());
+
+ nodes[0].node.handle_funding_signed(&nodes[1].node.get_our_node_id(), &funding_signed);
+ check_added_monitors!(nodes[0], 1);
+
+ expect_channel_pending_event(&nodes[0], &nodes[1].node.get_our_node_id());
+ assert!(nodes[0].node.get_and_clear_pending_events().is_empty());
+
+ // Starting at height 0, we "confirm" the coinbase at height 1.
+ confirm_transaction_at(&nodes[0], &tx, 1);
+ // We connect 98 more blocks to have 99 confirmations for the coinbase transaction.
+ connect_blocks(&nodes[0], COINBASE_MATURITY - 2);
+ // Check that we have no pending message events (we have not queued a `channel_ready` yet).
+ assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
+ // Now connect one more block which results in 100 confirmations of the coinbase transaction.
+ connect_blocks(&nodes[0], 1);
+ // There should now be a `channel_ready` which can be handled.
+ let _ = &nodes[1].node.handle_channel_ready(&nodes[0].node.get_our_node_id(), &get_event_msg!(&nodes[0], MessageSendEvent::SendChannelReady, nodes[1].node.get_our_node_id()));
+
+ confirm_transaction_at(&nodes[1], &tx, 1);
+ connect_blocks(&nodes[1], COINBASE_MATURITY - 2);
+ assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
+ connect_blocks(&nodes[1], 1);
+ expect_channel_ready_event(&nodes[1], &nodes[0].node.get_our_node_id());
+ create_chan_between_nodes_with_value_confirm_second(&nodes[0], &nodes[1]);
+}
+
fn do_test_tx_confirmed_skipping_blocks_immediate_broadcast(test_height_before_timelock: bool) {
// In the first version of the chain::Confirm interface, after a refactor was made to not
// broadcast CSV-locked transactions until their CSV lock is up, we wouldn't reliably broadcast
create_announced_chan_between_nodes(&nodes, 0, 1);
let (chan_announce, _, channel_id, _) = create_announced_chan_between_nodes(&nodes, 1, 2);
- let (_, payment_hash, _) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 1_000_000);
+ let (_, payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 1_000_000);
nodes[1].node.peer_disconnected(&nodes[2].node.get_our_node_id());
nodes[2].node.peer_disconnected(&nodes[1].node.get_our_node_id());
nodes[1].node.force_close_broadcasting_latest_txn(&channel_id, &nodes[2].node.get_our_node_id()).unwrap();
check_closed_broadcast!(nodes[1], true);
- check_closed_event!(nodes[1], 1, ClosureReason::HolderForceClosed);
+ check_closed_event!(nodes[1], 1, ClosureReason::HolderForceClosed, [nodes[2].node.get_our_node_id()], 100000);
check_added_monitors!(nodes[1], 1);
let node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
assert_eq!(node_txn.len(), 1);
pass_along_path(&nodes[0], &[&nodes[2], &nodes[3]], 15_000_000, our_payment_hash, Some(our_payment_secret), events.pop().unwrap(), true, None);
do_claim_payment_along_route(&nodes[0], &[&[&nodes[1], &nodes[3]], &[&nodes[2], &nodes[3]]], false, our_payment_preimage);
- expect_payment_sent(&nodes[0], our_payment_preimage, Some(None), true);
-}
-
-#[test]
-fn test_keysend_payments_to_public_node() {
- let chanmon_cfgs = create_chanmon_cfgs(2);
- let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
- let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
- let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
-
- let _chan = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100000, 10001);
- let network_graph = nodes[0].network_graph.clone();
- let payer_pubkey = nodes[0].node.get_our_node_id();
- let payee_pubkey = nodes[1].node.get_our_node_id();
- let route_params = RouteParameters {
- payment_params: PaymentParameters::for_keysend(payee_pubkey, 40, false),
- final_value_msat: 10000,
- };
- let scorer = test_utils::TestScorer::new();
- let random_seed_bytes = chanmon_cfgs[1].keys_manager.get_secure_random_bytes();
- let route = find_route(&payer_pubkey, &route_params, &network_graph, None, nodes[0].logger, &scorer, &(), &random_seed_bytes).unwrap();
-
- let test_preimage = PaymentPreimage([42; 32]);
- let payment_hash = nodes[0].node.send_spontaneous_payment(&route, Some(test_preimage),
- RecipientOnionFields::spontaneous_empty(), PaymentId(test_preimage.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 event = events.pop().unwrap();
- let path = vec![&nodes[1]];
- pass_along_path(&nodes[0], &path, 10000, payment_hash, None, event, true, Some(test_preimage));
- claim_payment(&nodes[0], &path, test_preimage);
-}
-
-#[test]
-fn test_keysend_payments_to_private_node() {
- let chanmon_cfgs = create_chanmon_cfgs(2);
- let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
- let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
- let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
-
- let payer_pubkey = nodes[0].node.get_our_node_id();
- let payee_pubkey = nodes[1].node.get_our_node_id();
-
- let _chan = create_chan_between_nodes(&nodes[0], &nodes[1]);
- let route_params = RouteParameters {
- payment_params: PaymentParameters::for_keysend(payee_pubkey, 40, false),
- final_value_msat: 10000,
- };
- let network_graph = nodes[0].network_graph.clone();
- let first_hops = nodes[0].node.list_usable_channels();
- let scorer = test_utils::TestScorer::new();
- let random_seed_bytes = chanmon_cfgs[1].keys_manager.get_secure_random_bytes();
- let route = find_route(
- &payer_pubkey, &route_params, &network_graph, Some(&first_hops.iter().collect::<Vec<_>>()),
- nodes[0].logger, &scorer, &(), &random_seed_bytes
- ).unwrap();
-
- let test_preimage = PaymentPreimage([42; 32]);
- let payment_hash = nodes[0].node.send_spontaneous_payment(&route, Some(test_preimage),
- RecipientOnionFields::spontaneous_empty(), PaymentId(test_preimage.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 event = events.pop().unwrap();
- let path = vec![&nodes[1]];
- pass_along_path(&nodes[0], &path, 10000, payment_hash, None, event, true, Some(test_preimage));
- claim_payment(&nodes[0], &path, test_preimage);
+ expect_payment_sent(&nodes[0], our_payment_preimage, Some(None), true, true);
}
#[test]
AtUpdateFeeOutbound,
}
-fn do_test_max_dust_htlc_exposure(dust_outbound_balance: bool, exposure_breach_event: ExposureEvent, on_holder_tx: bool) {
+fn do_test_max_dust_htlc_exposure(dust_outbound_balance: bool, exposure_breach_event: ExposureEvent, on_holder_tx: bool, multiplier_dust_limit: bool) {
// Test that we properly reject dust HTLC violating our `max_dust_htlc_exposure_msat`
// policy.
//
let chanmon_cfgs = create_chanmon_cfgs(2);
let mut config = test_default_channel_config();
- config.channel_config.max_dust_htlc_exposure_msat = 5_000_000; // default setting value
+ config.channel_config.max_dust_htlc_exposure = if multiplier_dust_limit {
+ // Default test fee estimator rate is 253 sat/kw, so we set the multiplier to 5_000_000 / 253
+ // to get roughly the same initial value as the default setting when this test was
+ // originally written.
+ MaxDustHTLCExposure::FeeRateMultiplier(5_000_000 / 253)
+ } else { MaxDustHTLCExposure::FixedLimitMsat(5_000_000) }; // initial default setting value
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config), None]);
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
if on_holder_tx {
let mut node_0_per_peer_lock;
let mut node_0_peer_state_lock;
- let mut chan = get_outbound_v1_channel_ref!(nodes[0], nodes[1], node_0_per_peer_lock, node_0_peer_state_lock, temporary_channel_id);
- chan.context.holder_dust_limit_satoshis = 546;
+ match get_channel_ref!(nodes[0], nodes[1], node_0_per_peer_lock, node_0_peer_state_lock, temporary_channel_id) {
+ ChannelPhase::UnfundedOutboundV1(chan) => {
+ chan.context.holder_dust_limit_satoshis = 546;
+ },
+ _ => panic!("Unexpected ChannelPhase variant"),
+ }
}
nodes[0].node.funding_transaction_generated(&temporary_channel_id, &nodes[1].node.get_our_node_id(), tx.clone()).unwrap();
let (mut route, payment_hash, _, payment_secret) =
get_route_and_payment_hash!(nodes[0], nodes[1], 1000);
- let dust_buffer_feerate = {
+ let (dust_buffer_feerate, max_dust_htlc_exposure_msat) = {
let per_peer_state = nodes[0].node.per_peer_state.read().unwrap();
let chan_lock = per_peer_state.get(&nodes[1].node.get_our_node_id()).unwrap().lock().unwrap();
let chan = chan_lock.channel_by_id.get(&channel_id).unwrap();
- chan.context.get_dust_buffer_feerate(None) as u64
+ (chan.context().get_dust_buffer_feerate(None) as u64,
+ chan.context().get_max_dust_htlc_exposure_msat(&LowerBoundedFeeEstimator(nodes[0].fee_estimator)))
};
let dust_outbound_htlc_on_holder_tx_msat: u64 = (dust_buffer_feerate * htlc_timeout_tx_weight(&channel_type_features) / 1000 + open_channel.dust_limit_satoshis - 1) * 1000;
- let dust_outbound_htlc_on_holder_tx: u64 = config.channel_config.max_dust_htlc_exposure_msat / dust_outbound_htlc_on_holder_tx_msat;
+ let dust_outbound_htlc_on_holder_tx: u64 = max_dust_htlc_exposure_msat / dust_outbound_htlc_on_holder_tx_msat;
let dust_inbound_htlc_on_holder_tx_msat: u64 = (dust_buffer_feerate * htlc_success_tx_weight(&channel_type_features) / 1000 + open_channel.dust_limit_satoshis - 1) * 1000;
- let dust_inbound_htlc_on_holder_tx: u64 = config.channel_config.max_dust_htlc_exposure_msat / dust_inbound_htlc_on_holder_tx_msat;
+ let dust_inbound_htlc_on_holder_tx: u64 = max_dust_htlc_exposure_msat / dust_inbound_htlc_on_holder_tx_msat;
let dust_htlc_on_counterparty_tx: u64 = 4;
- let dust_htlc_on_counterparty_tx_msat: u64 = config.channel_config.max_dust_htlc_exposure_msat / dust_htlc_on_counterparty_tx;
+ let dust_htlc_on_counterparty_tx_msat: u64 = max_dust_htlc_exposure_msat / dust_htlc_on_counterparty_tx;
if on_holder_tx {
if dust_outbound_balance {
), true, APIError::ChannelUnavailable { .. }, {});
}
} else if exposure_breach_event == ExposureEvent::AtHTLCReception {
- let (route, payment_hash, _, payment_secret) = get_route_and_payment_hash!(nodes[1], nodes[0], if on_holder_tx { dust_inbound_htlc_on_holder_tx_msat } else { dust_htlc_on_counterparty_tx_msat + 1 });
+ let (route, payment_hash, _, payment_secret) = get_route_and_payment_hash!(nodes[1], nodes[0], if on_holder_tx { dust_inbound_htlc_on_holder_tx_msat } else { dust_htlc_on_counterparty_tx_msat + 4 });
nodes[1].node.send_payment_with_route(&route, payment_hash,
RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_hash.0)).unwrap();
check_added_monitors!(nodes[1], 1);
// Outbound dust balance: 6399 sats
let dust_inbound_overflow = dust_inbound_htlc_on_holder_tx_msat * (dust_inbound_htlc_on_holder_tx + 1);
let dust_outbound_overflow = dust_outbound_htlc_on_holder_tx_msat * dust_outbound_htlc_on_holder_tx + dust_inbound_htlc_on_holder_tx_msat;
- nodes[0].logger.assert_log("lightning::ln::channel".to_string(), format!("Cannot accept value that would put our exposure to dust HTLCs at {} over the limit {} on holder commitment tx", if dust_outbound_balance { dust_outbound_overflow } else { dust_inbound_overflow }, config.channel_config.max_dust_htlc_exposure_msat), 1);
+ nodes[0].logger.assert_log("lightning::ln::channel".to_string(), format!("Cannot accept value that would put our exposure to dust HTLCs at {} over the limit {} on holder commitment tx", if dust_outbound_balance { dust_outbound_overflow } else { dust_inbound_overflow }, max_dust_htlc_exposure_msat), 1);
} else {
// Outbound dust balance: 5200 sats
nodes[0].logger.assert_log("lightning::ln::channel".to_string(),
format!("Cannot accept value that would put our exposure to dust HTLCs at {} over the limit {} on counterparty commitment tx",
- dust_htlc_on_counterparty_tx_msat * (dust_htlc_on_counterparty_tx - 1) + dust_htlc_on_counterparty_tx_msat + 1,
- config.channel_config.max_dust_htlc_exposure_msat), 1);
+ dust_htlc_on_counterparty_tx_msat * (dust_htlc_on_counterparty_tx - 1) + dust_htlc_on_counterparty_tx_msat + 4,
+ max_dust_htlc_exposure_msat), 1);
}
} else if exposure_breach_event == ExposureEvent::AtUpdateFeeOutbound {
route.paths[0].hops.last_mut().unwrap().fee_msat = 2_500_000;
- nodes[0].node.send_payment_with_route(&route, payment_hash,
- RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_hash.0)).unwrap();
+ // For the multiplier dust exposure limit, since it scales with feerate,
+ // we need to add a lot of HTLCs that will become dust at the new feerate
+ // to cross the threshold.
+ for _ in 0..20 {
+ let (_, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[1], Some(1_000), None);
+ nodes[0].node.send_payment_with_route(&route, payment_hash,
+ RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_hash.0)).unwrap();
+ }
{
let mut feerate_lock = chanmon_cfgs[0].fee_estimator.sat_per_kw.lock().unwrap();
*feerate_lock = *feerate_lock * 10;
added_monitors.clear();
}
+fn do_test_max_dust_htlc_exposure_by_threshold_type(multiplier_dust_limit: bool) {
+ do_test_max_dust_htlc_exposure(true, ExposureEvent::AtHTLCForward, true, multiplier_dust_limit);
+ do_test_max_dust_htlc_exposure(false, ExposureEvent::AtHTLCForward, true, multiplier_dust_limit);
+ do_test_max_dust_htlc_exposure(false, ExposureEvent::AtHTLCReception, true, multiplier_dust_limit);
+ do_test_max_dust_htlc_exposure(false, ExposureEvent::AtHTLCReception, false, multiplier_dust_limit);
+ do_test_max_dust_htlc_exposure(true, ExposureEvent::AtHTLCForward, false, multiplier_dust_limit);
+ do_test_max_dust_htlc_exposure(true, ExposureEvent::AtHTLCReception, false, multiplier_dust_limit);
+ do_test_max_dust_htlc_exposure(true, ExposureEvent::AtHTLCReception, true, multiplier_dust_limit);
+ do_test_max_dust_htlc_exposure(false, ExposureEvent::AtHTLCForward, false, multiplier_dust_limit);
+ do_test_max_dust_htlc_exposure(true, ExposureEvent::AtUpdateFeeOutbound, true, multiplier_dust_limit);
+ do_test_max_dust_htlc_exposure(true, ExposureEvent::AtUpdateFeeOutbound, false, multiplier_dust_limit);
+ do_test_max_dust_htlc_exposure(false, ExposureEvent::AtUpdateFeeOutbound, false, multiplier_dust_limit);
+ do_test_max_dust_htlc_exposure(false, ExposureEvent::AtUpdateFeeOutbound, true, multiplier_dust_limit);
+}
+
#[test]
fn test_max_dust_htlc_exposure() {
- do_test_max_dust_htlc_exposure(true, ExposureEvent::AtHTLCForward, true);
- do_test_max_dust_htlc_exposure(false, ExposureEvent::AtHTLCForward, true);
- do_test_max_dust_htlc_exposure(false, ExposureEvent::AtHTLCReception, true);
- do_test_max_dust_htlc_exposure(false, ExposureEvent::AtHTLCReception, false);
- do_test_max_dust_htlc_exposure(true, ExposureEvent::AtHTLCForward, false);
- do_test_max_dust_htlc_exposure(true, ExposureEvent::AtHTLCReception, false);
- do_test_max_dust_htlc_exposure(true, ExposureEvent::AtHTLCReception, true);
- do_test_max_dust_htlc_exposure(false, ExposureEvent::AtHTLCForward, false);
- do_test_max_dust_htlc_exposure(true, ExposureEvent::AtUpdateFeeOutbound, true);
- do_test_max_dust_htlc_exposure(true, ExposureEvent::AtUpdateFeeOutbound, false);
- do_test_max_dust_htlc_exposure(false, ExposureEvent::AtUpdateFeeOutbound, false);
- do_test_max_dust_htlc_exposure(false, ExposureEvent::AtUpdateFeeOutbound, true);
+ do_test_max_dust_htlc_exposure_by_threshold_type(false);
+ do_test_max_dust_htlc_exposure_by_threshold_type(true);
}
#[test]
},
_ => panic!()
}
+ let events = nodes[0].node.get_and_clear_pending_events();
+ assert_eq!(events.len(), 1);
+ match events[0] {
+ Event::ChannelClosed { channel_id, .. } => {
+ assert_eq!(channel_id, temp_channel_id);
+ },
+ _ => panic!("Unexpected event"),
+ }
+}
+
+#[test]
+fn test_non_final_funding_tx_within_headroom() {
+ let chanmon_cfgs = create_chanmon_cfgs(2);
+ let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
+ let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
+ let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+
+ let temp_channel_id = nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 100_000, 0, 42, None).unwrap();
+ let open_channel_message = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, nodes[1].node.get_our_node_id());
+ nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &open_channel_message);
+ let accept_channel_message = get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, nodes[0].node.get_our_node_id());
+ nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), &accept_channel_message);
+
+ let best_height = nodes[0].node.best_block.read().unwrap().height();
+
+ let chan_id = *nodes[0].network_chan_count.borrow();
+ let events = nodes[0].node.get_and_clear_pending_events();
+ let input = TxIn { previous_output: BitcoinOutPoint::null(), script_sig: bitcoin::Script::new(), sequence: Sequence(1), witness: Witness::from_vec(vec!(vec!(1))) };
+ assert_eq!(events.len(), 1);
+ let mut tx = match events[0] {
+ Event::FundingGenerationReady { ref channel_value_satoshis, ref output_script, .. } => {
+ // Timelock the transaction within a +1 headroom from the best block.
+ Transaction { version: chan_id as i32, lock_time: PackedLockTime(best_height + 1), input: vec![input], output: vec![TxOut {
+ value: *channel_value_satoshis, script_pubkey: output_script.clone(),
+ }]}
+ },
+ _ => panic!("Unexpected event"),
+ };
- // However, transaction should be accepted if it's in a +1 headroom from best block.
- tx.lock_time = PackedLockTime(tx.lock_time.0 - 1);
+ // Transaction should be accepted if it's in a +1 headroom from best block.
assert!(nodes[0].node.funding_transaction_generated(&temp_channel_id, &nodes[1].node.get_our_node_id(), tx.clone()).is_ok());
get_event_msg!(nodes[0], MessageSendEvent::SendFundingCreated, nodes[1].node.get_our_node_id());
}
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 (- 250)".to_owned() },
+ [nodes[0].node.get_our_node_id()], 100000);
check_closed_broadcast!(nodes[1], true);
check_added_monitors!(nodes[1], 1);
},
}
}
}
+
+#[test]
+fn test_remove_expired_outbound_unfunded_channels() {
+ let chanmon_cfgs = create_chanmon_cfgs(2);
+ let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
+ let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
+ let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+
+ let temp_channel_id = nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 100_000, 0, 42, None).unwrap();
+ let open_channel_message = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, nodes[1].node.get_our_node_id());
+ nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &open_channel_message);
+ let accept_channel_message = get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, nodes[0].node.get_our_node_id());
+ nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), &accept_channel_message);
+
+ let events = nodes[0].node.get_and_clear_pending_events();
+ assert_eq!(events.len(), 1);
+ match events[0] {
+ Event::FundingGenerationReady { .. } => (),
+ _ => panic!("Unexpected event"),
+ };
+
+ // Asserts the outbound channel has been removed from a nodes[0]'s peer state map.
+ let check_outbound_channel_existence = |should_exist: bool| {
+ let per_peer_state = nodes[0].node.per_peer_state.read().unwrap();
+ let chan_lock = per_peer_state.get(&nodes[1].node.get_our_node_id()).unwrap().lock().unwrap();
+ assert_eq!(chan_lock.channel_by_id.contains_key(&temp_channel_id), should_exist);
+ };
+
+ // Channel should exist without any timer ticks.
+ check_outbound_channel_existence(true);
+
+ // Channel should exist with 1 timer tick less than required.
+ for _ in 0..UNFUNDED_CHANNEL_AGE_LIMIT_TICKS - 1 {
+ nodes[0].node.timer_tick_occurred();
+ check_outbound_channel_existence(true)
+ }
+
+ // Remove channel after reaching the required ticks.
+ nodes[0].node.timer_tick_occurred();
+ check_outbound_channel_existence(false);
+
+ let msg_events = nodes[0].node.get_and_clear_pending_msg_events();
+ assert_eq!(msg_events.len(), 1);
+ match msg_events[0] {
+ MessageSendEvent::HandleError { action: ErrorAction::SendErrorMessage { ref msg }, node_id: _ } => {
+ assert_eq!(msg.data, "Force-closing pending channel due to timeout awaiting establishment handshake");
+ },
+ _ => panic!("Unexpected event"),
+ }
+ check_closed_event(&nodes[0], 1, ClosureReason::HolderForceClosed, false, &[nodes[1].node.get_our_node_id()], 100000);
+}
+
+#[test]
+fn test_remove_expired_inbound_unfunded_channels() {
+ let chanmon_cfgs = create_chanmon_cfgs(2);
+ let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
+ let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
+ let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+
+ let temp_channel_id = nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 100_000, 0, 42, None).unwrap();
+ let open_channel_message = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, nodes[1].node.get_our_node_id());
+ nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &open_channel_message);
+ let accept_channel_message = get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, nodes[0].node.get_our_node_id());
+ nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), &accept_channel_message);
+
+ let events = nodes[0].node.get_and_clear_pending_events();
+ assert_eq!(events.len(), 1);
+ match events[0] {
+ Event::FundingGenerationReady { .. } => (),
+ _ => panic!("Unexpected event"),
+ };
+
+ // Asserts the inbound channel has been removed from a nodes[1]'s peer state map.
+ let check_inbound_channel_existence = |should_exist: bool| {
+ let per_peer_state = nodes[1].node.per_peer_state.read().unwrap();
+ let chan_lock = per_peer_state.get(&nodes[0].node.get_our_node_id()).unwrap().lock().unwrap();
+ assert_eq!(chan_lock.channel_by_id.contains_key(&temp_channel_id), should_exist);
+ };
+
+ // Channel should exist without any timer ticks.
+ check_inbound_channel_existence(true);
+
+ // Channel should exist with 1 timer tick less than required.
+ for _ in 0..UNFUNDED_CHANNEL_AGE_LIMIT_TICKS - 1 {
+ nodes[1].node.timer_tick_occurred();
+ check_inbound_channel_existence(true)
+ }
+
+ // Remove channel after reaching the required ticks.
+ nodes[1].node.timer_tick_occurred();
+ check_inbound_channel_existence(false);
+
+ let msg_events = nodes[1].node.get_and_clear_pending_msg_events();
+ assert_eq!(msg_events.len(), 1);
+ match msg_events[0] {
+ MessageSendEvent::HandleError { action: ErrorAction::SendErrorMessage { ref msg }, node_id: _ } => {
+ assert_eq!(msg.data, "Force-closing pending channel due to timeout awaiting establishment handshake");
+ },
+ _ => panic!("Unexpected event"),
+ }
+ check_closed_event(&nodes[1], 1, ClosureReason::HolderForceClosed, false, &[nodes[0].node.get_our_node_id()], 100000);
+}
+
+fn do_test_multi_post_event_actions(do_reload: bool) {
+ // Tests handling multiple post-Event actions at once.
+ // There is specific code in ChannelManager to handle channels where multiple post-Event
+ // `ChannelMonitorUpdates` are pending at once. This test exercises that code.
+ //
+ // Specifically, we test calling `get_and_clear_pending_events` while there are two
+ // PaymentSents from different channels and one channel has two pending `ChannelMonitorUpdate`s
+ // - one from an RAA and one from an inbound commitment_signed.
+ let chanmon_cfgs = create_chanmon_cfgs(3);
+ let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
+ let (persister, chain_monitor);
+ let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
+ let nodes_0_deserialized;
+ let mut nodes = create_network(3, &node_cfgs, &node_chanmgrs);
+
+ let chan_id = create_announced_chan_between_nodes(&nodes, 0, 1).2;
+ let chan_id_2 = create_announced_chan_between_nodes(&nodes, 0, 2).2;
+
+ send_payment(&nodes[0], &[&nodes[1]], 1_000_000);
+ send_payment(&nodes[0], &[&nodes[2]], 1_000_000);
+
+ let (our_payment_preimage, our_payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1]], 1_000_000);
+ let (payment_preimage_2, payment_hash_2, ..) = route_payment(&nodes[0], &[&nodes[2]], 1_000_000);
+
+ nodes[1].node.claim_funds(our_payment_preimage);
+ check_added_monitors!(nodes[1], 1);
+ expect_payment_claimed!(nodes[1], our_payment_hash, 1_000_000);
+
+ nodes[2].node.claim_funds(payment_preimage_2);
+ check_added_monitors!(nodes[2], 1);
+ expect_payment_claimed!(nodes[2], payment_hash_2, 1_000_000);
+
+ for dest in &[1, 2] {
+ let htlc_fulfill_updates = get_htlc_update_msgs!(nodes[*dest], nodes[0].node.get_our_node_id());
+ nodes[0].node.handle_update_fulfill_htlc(&nodes[*dest].node.get_our_node_id(), &htlc_fulfill_updates.update_fulfill_htlcs[0]);
+ commitment_signed_dance!(nodes[0], nodes[*dest], htlc_fulfill_updates.commitment_signed, false);
+ check_added_monitors(&nodes[0], 0);
+ }
+
+ let (route, payment_hash_3, _, payment_secret_3) =
+ get_route_and_payment_hash!(nodes[1], nodes[0], 100_000);
+ let payment_id = PaymentId(payment_hash_3.0);
+ nodes[1].node.send_payment_with_route(&route, payment_hash_3,
+ RecipientOnionFields::secret_only(payment_secret_3), payment_id).unwrap();
+ check_added_monitors(&nodes[1], 1);
+
+ let send_event = SendEvent::from_node(&nodes[1]);
+ nodes[0].node.handle_update_add_htlc(&nodes[1].node.get_our_node_id(), &send_event.msgs[0]);
+ nodes[0].node.handle_commitment_signed(&nodes[1].node.get_our_node_id(), &send_event.commitment_msg);
+ assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
+
+ if do_reload {
+ let nodes_0_serialized = nodes[0].node.encode();
+ let chan_0_monitor_serialized = get_monitor!(nodes[0], chan_id).encode();
+ let chan_1_monitor_serialized = get_monitor!(nodes[0], chan_id_2).encode();
+ reload_node!(nodes[0], test_default_channel_config(), &nodes_0_serialized, &[&chan_0_monitor_serialized, &chan_1_monitor_serialized], persister, chain_monitor, nodes_0_deserialized);
+
+ nodes[1].node.peer_disconnected(&nodes[0].node.get_our_node_id());
+ nodes[2].node.peer_disconnected(&nodes[0].node.get_our_node_id());
+
+ reconnect_nodes(ReconnectArgs::new(&nodes[0], &nodes[1]));
+ reconnect_nodes(ReconnectArgs::new(&nodes[0], &nodes[2]));
+ }
+
+ let events = nodes[0].node.get_and_clear_pending_events();
+ assert_eq!(events.len(), 4);
+ if let Event::PaymentSent { payment_preimage, .. } = events[0] {
+ assert!(payment_preimage == our_payment_preimage || payment_preimage == payment_preimage_2);
+ } else { panic!(); }
+ if let Event::PaymentSent { payment_preimage, .. } = events[1] {
+ assert!(payment_preimage == our_payment_preimage || payment_preimage == payment_preimage_2);
+ } else { panic!(); }
+ if let Event::PaymentPathSuccessful { .. } = events[2] {} else { panic!(); }
+ if let Event::PaymentPathSuccessful { .. } = events[3] {} else { panic!(); }
+
+ // After the events are processed, the ChannelMonitorUpdates will be released and, upon their
+ // completion, we'll respond to nodes[1] with an RAA + CS.
+ get_revoke_commit_msgs(&nodes[0], &nodes[1].node.get_our_node_id());
+ check_added_monitors(&nodes[0], 3);
+}
+
+#[test]
+fn test_multi_post_event_actions() {
+ do_test_multi_post_event_actions(true);
+ do_test_multi_post_event_actions(false);
+}
+
+#[test]
+fn test_batch_channel_open() {
+ let chanmon_cfgs = create_chanmon_cfgs(3);
+ let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
+ let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
+ let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
+
+ // Initiate channel opening and create the batch channel funding transaction.
+ let (tx, funding_created_msgs) = create_batch_channel_funding(&nodes[0], &[
+ (&nodes[1], 100_000, 0, 42, None),
+ (&nodes[2], 200_000, 0, 43, None),
+ ]);
+
+ // Go through the funding_created and funding_signed flow with node 1.
+ nodes[1].node.handle_funding_created(&nodes[0].node.get_our_node_id(), &funding_created_msgs[0]);
+ check_added_monitors(&nodes[1], 1);
+ expect_channel_pending_event(&nodes[1], &nodes[0].node.get_our_node_id());
+
+ let funding_signed_msg = get_event_msg!(nodes[1], MessageSendEvent::SendFundingSigned, nodes[0].node.get_our_node_id());
+ nodes[0].node.handle_funding_signed(&nodes[1].node.get_our_node_id(), &funding_signed_msg);
+ check_added_monitors(&nodes[0], 1);
+
+ // The transaction should not have been broadcast before all channels are ready.
+ assert_eq!(nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().len(), 0);
+
+ // Go through the funding_created and funding_signed flow with node 2.
+ nodes[2].node.handle_funding_created(&nodes[0].node.get_our_node_id(), &funding_created_msgs[1]);
+ check_added_monitors(&nodes[2], 1);
+ expect_channel_pending_event(&nodes[2], &nodes[0].node.get_our_node_id());
+
+ let funding_signed_msg = get_event_msg!(nodes[2], MessageSendEvent::SendFundingSigned, nodes[0].node.get_our_node_id());
+ chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::InProgress);
+ nodes[0].node.handle_funding_signed(&nodes[2].node.get_our_node_id(), &funding_signed_msg);
+ check_added_monitors(&nodes[0], 1);
+
+ // The transaction should not have been broadcast before persisting all monitors has been
+ // completed.
+ assert_eq!(nodes[0].tx_broadcaster.txn_broadcast().len(), 0);
+ assert_eq!(nodes[0].node.get_and_clear_pending_events().len(), 0);
+
+ // Complete the persistence of the monitor.
+ nodes[0].chain_monitor.complete_sole_pending_chan_update(
+ &OutPoint { txid: tx.txid(), index: 1 }.to_channel_id()
+ );
+ let events = nodes[0].node.get_and_clear_pending_events();
+
+ // The transaction should only have been broadcast now.
+ let broadcasted_txs = nodes[0].tx_broadcaster.txn_broadcast();
+ assert_eq!(broadcasted_txs.len(), 1);
+ assert_eq!(broadcasted_txs[0], tx);
+
+ assert_eq!(events.len(), 2);
+ assert!(events.iter().any(|e| matches!(
+ *e,
+ crate::events::Event::ChannelPending {
+ ref counterparty_node_id,
+ ..
+ } if counterparty_node_id == &nodes[1].node.get_our_node_id(),
+ )));
+ assert!(events.iter().any(|e| matches!(
+ *e,
+ crate::events::Event::ChannelPending {
+ ref counterparty_node_id,
+ ..
+ } if counterparty_node_id == &nodes[2].node.get_our_node_id(),
+ )));
+}
+
+#[test]
+fn test_disconnect_in_funding_batch() {
+ let chanmon_cfgs = create_chanmon_cfgs(3);
+ let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
+ let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
+ let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
+
+ // Initiate channel opening and create the batch channel funding transaction.
+ let (tx, funding_created_msgs) = create_batch_channel_funding(&nodes[0], &[
+ (&nodes[1], 100_000, 0, 42, None),
+ (&nodes[2], 200_000, 0, 43, None),
+ ]);
+
+ // Go through the funding_created and funding_signed flow with node 1.
+ nodes[1].node.handle_funding_created(&nodes[0].node.get_our_node_id(), &funding_created_msgs[0]);
+ check_added_monitors(&nodes[1], 1);
+ expect_channel_pending_event(&nodes[1], &nodes[0].node.get_our_node_id());
+
+ let funding_signed_msg = get_event_msg!(nodes[1], MessageSendEvent::SendFundingSigned, nodes[0].node.get_our_node_id());
+ nodes[0].node.handle_funding_signed(&nodes[1].node.get_our_node_id(), &funding_signed_msg);
+ check_added_monitors(&nodes[0], 1);
+
+ // The transaction should not have been broadcast before all channels are ready.
+ assert_eq!(nodes[0].tx_broadcaster.txn_broadcast().len(), 0);
+
+ // The remaining peer in the batch disconnects.
+ nodes[0].node.peer_disconnected(&nodes[2].node.get_our_node_id());
+
+ // 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);
+
+ // The monitor should become closed.
+ check_added_monitors(&nodes[0], 1);
+ {
+ let mut monitor_updates = nodes[0].chain_monitor.monitor_updates.lock().unwrap();
+ let monitor_updates_1 = monitor_updates.get(&channel_id_1).unwrap();
+ assert_eq!(monitor_updates_1.len(), 1);
+ assert_eq!(monitor_updates_1[0].update_id, CLOSED_CHANNEL_UPDATE_ID);
+ }
+
+ // The funding transaction should not have been broadcast, and therefore, we don't need
+ // to broadcast a force-close transaction for the closed monitor.
+ assert_eq!(nodes[0].tx_broadcaster.txn_broadcast().len(), 0);
+
+ // Ensure the channels don't exist anymore.
+ assert!(nodes[0].node.list_channels().is_empty());
+}
+
+#[test]
+fn test_batch_funding_close_after_funding_signed() {
+ let chanmon_cfgs = create_chanmon_cfgs(3);
+ let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
+ let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
+ let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
+
+ // Initiate channel opening and create the batch channel funding transaction.
+ let (tx, funding_created_msgs) = create_batch_channel_funding(&nodes[0], &[
+ (&nodes[1], 100_000, 0, 42, None),
+ (&nodes[2], 200_000, 0, 43, None),
+ ]);
+
+ // Go through the funding_created and funding_signed flow with node 1.
+ nodes[1].node.handle_funding_created(&nodes[0].node.get_our_node_id(), &funding_created_msgs[0]);
+ check_added_monitors(&nodes[1], 1);
+ expect_channel_pending_event(&nodes[1], &nodes[0].node.get_our_node_id());
+
+ let funding_signed_msg = get_event_msg!(nodes[1], MessageSendEvent::SendFundingSigned, nodes[0].node.get_our_node_id());
+ nodes[0].node.handle_funding_signed(&nodes[1].node.get_our_node_id(), &funding_signed_msg);
+ check_added_monitors(&nodes[0], 1);
+
+ // Go through the funding_created and funding_signed flow with node 2.
+ nodes[2].node.handle_funding_created(&nodes[0].node.get_our_node_id(), &funding_created_msgs[1]);
+ check_added_monitors(&nodes[2], 1);
+ expect_channel_pending_event(&nodes[2], &nodes[0].node.get_our_node_id());
+
+ let funding_signed_msg = get_event_msg!(nodes[2], MessageSendEvent::SendFundingSigned, nodes[0].node.get_our_node_id());
+ chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::InProgress);
+ nodes[0].node.handle_funding_signed(&nodes[2].node.get_our_node_id(), &funding_signed_msg);
+ check_added_monitors(&nodes[0], 1);
+
+ // The transaction should not have been broadcast before all channels are ready.
+ assert_eq!(nodes[0].tx_broadcaster.txn_broadcast().len(), 0);
+
+ // Force-close the channel for which we've completed the initial monitor.
+ let 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();
+ nodes[0].node.force_close_broadcasting_latest_txn(&channel_id_1, &nodes[1].node.get_our_node_id()).unwrap();
+ check_added_monitors(&nodes[0], 2);
+ {
+ let mut monitor_updates = nodes[0].chain_monitor.monitor_updates.lock().unwrap();
+ let monitor_updates_1 = monitor_updates.get(&channel_id_1).unwrap();
+ assert_eq!(monitor_updates_1.len(), 1);
+ assert_eq!(monitor_updates_1[0].update_id, CLOSED_CHANNEL_UPDATE_ID);
+ let monitor_updates_2 = monitor_updates.get(&channel_id_2).unwrap();
+ assert_eq!(monitor_updates_2.len(), 1);
+ assert_eq!(monitor_updates_2[0].update_id, CLOSED_CHANNEL_UPDATE_ID);
+ }
+ let msg_events = nodes[0].node.get_and_clear_pending_msg_events();
+ match msg_events[0] {
+ MessageSendEvent::HandleError { .. } => (),
+ _ => panic!("Unexpected message."),
+ }
+
+ // We broadcast the commitment transaction as part of the force-close.
+ {
+ let broadcasted_txs = nodes[0].tx_broadcaster.txn_broadcast();
+ assert_eq!(broadcasted_txs.len(), 1);
+ assert!(broadcasted_txs[0].txid() != tx.txid());
+ assert_eq!(broadcasted_txs[0].input.len(), 1);
+ assert_eq!(broadcasted_txs[0].input[0].previous_output.txid, tx.txid());
+ }
+
+ // 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);
+
+ // Ensure the channels don't exist anymore.
+ assert!(nodes[0].node.list_channels().is_empty());
+}
use crate::ln::msgs;
use crate::ln::msgs::MAX_VALUE_MSAT;
use crate::util::chacha20::ChaCha20;
-use crate::util::crypto::hkdf_extract_expand_4x;
+use crate::util::crypto::hkdf_extract_expand_5x;
use crate::util::errors::APIError;
use crate::util::logger::Logger;
user_pmt_hash_key: [u8; 32],
/// The base key used to derive signing keys and authenticate messages for BOLT 12 Offers.
offers_base_key: [u8; 32],
+ /// The key used to encrypt message metadata for BOLT 12 Offers.
+ offers_encryption_key: [u8; 32],
}
impl ExpandedKey {
///
/// It is recommended to cache this value and not regenerate it for each new inbound payment.
pub fn new(key_material: &KeyMaterial) -> ExpandedKey {
- let (metadata_key, ldk_pmt_hash_key, user_pmt_hash_key, offers_base_key) =
- hkdf_extract_expand_4x(b"LDK Inbound Payment Key Expansion", &key_material.0);
+ let (
+ metadata_key,
+ ldk_pmt_hash_key,
+ user_pmt_hash_key,
+ offers_base_key,
+ offers_encryption_key,
+ ) = hkdf_extract_expand_5x(b"LDK Inbound Payment Key Expansion", &key_material.0);
Self {
metadata_key,
ldk_pmt_hash_key,
user_pmt_hash_key,
offers_base_key,
+ offers_encryption_key,
}
}
/// Returns an [`HmacEngine`] used to construct [`Offer::metadata`].
///
/// [`Offer::metadata`]: crate::offers::offer::Offer::metadata
- #[allow(unused)]
pub(crate) fn hmac_for_offer(
&self, nonce: Nonce, iv_bytes: &[u8; IV_LEN]
) -> HmacEngine<Sha256> {
hmac.input(&nonce.0);
hmac
}
+
+ /// Encrypts or decrypts the given `bytes`. Used for data included in an offer message's
+ /// metadata (e.g., payment id).
+ pub(crate) fn crypt_for_offer(&self, mut bytes: [u8; 32], nonce: Nonce) -> [u8; 32] {
+ ChaCha20::encrypt_single_block_in_place(&self.offers_encryption_key, &nonce.0, &mut bytes);
+ bytes
+ }
}
/// A 128-bit number used only once.
///
/// [`Offer::metadata`]: crate::offers::offer::Offer::metadata
/// [`Offer::signing_pubkey`]: crate::offers::offer::Offer::signing_pubkey
-#[allow(unused)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) struct Nonce(pub(crate) [u8; Self::LENGTH]);
let (iv_slice, encrypted_metadata_slice) = payment_secret_bytes.split_at_mut(IV_LEN);
iv_slice.copy_from_slice(iv_bytes);
- let chacha_block = ChaCha20::get_single_block(metadata_key, iv_bytes);
- for i in 0..METADATA_LEN {
- encrypted_metadata_slice[i] = chacha_block[i] ^ metadata_bytes[i];
- }
+ ChaCha20::encrypt_single_block(
+ metadata_key, iv_bytes, encrypted_metadata_slice, metadata_bytes
+ );
PaymentSecret(payment_secret_bytes)
}
hmac.input(&metadata_bytes[..]);
hmac.input(&payment_hash.0);
if !fixed_time_eq(&iv_bytes, &Hmac::from_engine(hmac).into_inner().split_at_mut(IV_LEN).0) {
- log_trace!(logger, "Failing HTLC with user-generated payment_hash {}: unexpected payment_secret", log_bytes!(payment_hash.0));
+ log_trace!(logger, "Failing HTLC with user-generated payment_hash {}: unexpected payment_secret", &payment_hash);
return Err(())
}
},
match derive_ldk_payment_preimage(payment_hash, &iv_bytes, &metadata_bytes, keys) {
Ok(preimage) => payment_preimage = Some(preimage),
Err(bad_preimage_bytes) => {
- log_trace!(logger, "Failing HTLC with payment_hash {} due to mismatching preimage {}", log_bytes!(payment_hash.0), log_bytes!(bad_preimage_bytes));
+ log_trace!(logger, "Failing HTLC with payment_hash {} due to mismatching preimage {}", &payment_hash, log_bytes!(bad_preimage_bytes));
return Err(())
}
}
},
Err(unknown_bits) => {
- log_trace!(logger, "Failing HTLC with payment hash {} due to unknown payment type {}", log_bytes!(payment_hash.0), unknown_bits);
+ log_trace!(logger, "Failing HTLC with payment hash {} due to unknown payment type {}", &payment_hash, unknown_bits);
return Err(());
}
}
let expiry = u64::from_be_bytes(expiry_bytes.try_into().unwrap());
if payment_data.total_msat < min_amt_msat {
- log_trace!(logger, "Failing HTLC with payment_hash {} due to total_msat {} being less than the minimum amount of {} msat", log_bytes!(payment_hash.0), payment_data.total_msat, min_amt_msat);
+ log_trace!(logger, "Failing HTLC with payment_hash {} due to total_msat {} being less than the minimum amount of {} msat", &payment_hash, payment_data.total_msat, min_amt_msat);
return Err(())
}
if expiry < highest_seen_timestamp {
- log_trace!(logger, "Failing HTLC with payment_hash {}: expired payment", log_bytes!(payment_hash.0));
+ log_trace!(logger, "Failing HTLC with payment_hash {}: expired payment", &payment_hash);
return Err(())
}
Ok(Method::LdkPaymentHash) | Ok(Method::LdkPaymentHashCustomFinalCltv) => {
derive_ldk_payment_preimage(payment_hash, &iv_bytes, &metadata_bytes, keys)
.map_err(|bad_preimage_bytes| APIError::APIMisuseError {
- err: format!("Payment hash {} did not match decoded preimage {}", log_bytes!(payment_hash.0), log_bytes!(bad_preimage_bytes))
+ err: format!("Payment hash {} did not match decoded preimage {}", &payment_hash, log_bytes!(bad_preimage_bytes))
})
},
Ok(Method::UserPaymentHash) | Ok(Method::UserPaymentHashCustomFinalCltv) => Err(APIError::APIMisuseError {
let (iv_slice, encrypted_metadata_bytes) = payment_secret.0.split_at(IV_LEN);
iv_bytes.copy_from_slice(iv_slice);
- let chacha_block = ChaCha20::get_single_block(&keys.metadata_key, &iv_bytes);
let mut metadata_bytes: [u8; METADATA_LEN] = [0; METADATA_LEN];
- for i in 0..METADATA_LEN {
- metadata_bytes[i] = chacha_block[i] ^ encrypted_metadata_bytes[i];
- }
+ ChaCha20::encrypt_single_block(
+ &keys.metadata_key, &iv_bytes, &mut metadata_bytes, encrypted_metadata_bytes
+ );
(iv_bytes, metadata_bytes)
}
pub mod chan_utils;
pub mod features;
pub mod script;
+mod channel_id;
#[cfg(fuzzing)]
pub mod peer_channel_encryptor;
#[cfg(not(fuzzing))]
pub(crate) mod channel;
+// Re-export ChannelId
+pub use channel_id::ChannelId;
+
pub(crate) mod onion_utils;
mod outbound_payment;
pub mod wire;
// without the node parameter being mut. This is incorrect, and thus newer rustcs will complain
// about an unnecessary mut. Thus, we silence the unused_mut warning in two test modules below.
+#[cfg(test)]
+#[allow(unused_mut)]
+mod blinded_payment_tests;
#[cfg(test)]
#[allow(unused_mut)]
mod functional_tests;
/// This is not exported to bindings users as we just use [u8; 32] directly
#[derive(Hash, Copy, Clone, PartialEq, Eq, Debug, Ord, PartialOrd)]
pub struct PaymentHash(pub [u8; 32]);
+
+impl core::fmt::Display for PaymentHash {
+ fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
+ crate::util::logger::DebugBytes(&self.0).fmt(f)
+ }
+}
+
/// payment_preimage type, use to route payment between hop
///
/// This is not exported to bindings users as we just use [u8; 32] directly
#[derive(Hash, Copy, Clone, PartialEq, Eq, Debug, Ord, PartialOrd)]
pub struct PaymentPreimage(pub [u8; 32]);
+
+impl core::fmt::Display for PaymentPreimage {
+ fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
+ crate::util::logger::DebugBytes(&self.0).fmt(f)
+ }
+}
+
/// payment_secret type, use to authenticate sender to the receiver and tie MPP HTLCs together
///
/// This is not exported to bindings users as we just use [u8; 32] directly
//! Further functional tests which test blockchain reorganizations.
-use crate::sign::{ChannelSigner, EcdsaChannelSigner};
+use crate::sign::EcdsaChannelSigner;
use crate::chain::channelmonitor::{ANTI_REORG_DELAY, LATENCY_GRACE_PERIOD_BLOCKS, Balance};
use crate::chain::transaction::OutPoint;
-use crate::chain::chaininterface::LowerBoundedFeeEstimator;
-use crate::events::bump_transaction::BumpTransactionEvent;
+use crate::chain::chaininterface::{LowerBoundedFeeEstimator, compute_feerate_sat_per_1000_weight};
+use crate::events::bump_transaction::{BumpTransactionEvent, WalletSource};
use crate::events::{Event, MessageSendEvent, MessageSendEventsProvider, ClosureReason, HTLCDestination};
use crate::ln::channel;
-use crate::ln::chan_utils;
-use crate::ln::channelmanager::{BREAKDOWN_TIMEOUT, ChannelManager, PaymentId, RecipientOnionFields};
+use crate::ln::channelmanager::{BREAKDOWN_TIMEOUT, PaymentId, RecipientOnionFields};
use crate::ln::msgs::ChannelMessageHandler;
use crate::util::config::UserConfig;
use crate::util::crypto::sign;
mine_transaction(&nodes[1], &bs_txn[0]);
check_added_monitors!(nodes[1], 1);
check_closed_broadcast!(nodes[1], true);
- check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [nodes[2].node.get_our_node_id()], 100000);
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
connect_blocks(&nodes[1], ANTI_REORG_DELAY - 1);
fn test_spendable_output<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, spendable_tx: &Transaction) {
let mut spendable = node.chain_monitor.chain_monitor.get_and_clear_pending_events();
assert_eq!(spendable.len(), 1);
- if let Event::SpendableOutputs { outputs } = spendable.pop().unwrap() {
+ if let Event::SpendableOutputs { outputs, .. } = spendable.pop().unwrap() {
assert_eq!(outputs.len(), 1);
let spend_tx = node.keys_manager.backing.spend_spendable_outputs(&[&outputs[0]], Vec::new(),
Builder::new().push_opcode(opcodes::all::OP_RETURN).into_script(), 253, None, &Secp256k1::new()).unwrap();
// Confirm the revoked commitment transaction, closing the channel.
mine_transaction(&nodes[1], &revoked_local_txn[0]);
check_added_monitors!(nodes[1], 1);
- check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [nodes[0].node.get_our_node_id()], 1000000);
check_closed_broadcast!(nodes[1], true);
let bs_spend_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
let channel_type_features = get_channel_type_features!(nodes[0], nodes[1], chan_id);
assert_eq!(vec![Balance::ClaimableOnChannelClose {
- claimable_amount_satoshis: 1_000_000 - 1_000 - chan_feerate * channel::commitment_tx_base_weight(&channel_type_features) / 1000
+ amount_satoshis: 1_000_000 - 1_000 - chan_feerate * channel::commitment_tx_base_weight(&channel_type_features) / 1000
}],
nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances());
- assert_eq!(vec![Balance::ClaimableOnChannelClose { claimable_amount_satoshis: 1_000, }],
+ assert_eq!(vec![Balance::ClaimableOnChannelClose { amount_satoshis: 1_000, }],
nodes[1].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances());
nodes[0].node.close_channel(&chan_id, &nodes[1].node.get_our_node_id()).unwrap();
assert!(nodes[1].chain_monitor.chain_monitor.get_and_clear_pending_events().is_empty());
assert_eq!(vec![Balance::ClaimableAwaitingConfirmations {
- claimable_amount_satoshis: 1_000_000 - 1_000 - chan_feerate * channel::commitment_tx_base_weight(&channel_type_features) / 1000,
+ amount_satoshis: 1_000_000 - 1_000 - chan_feerate * channel::commitment_tx_base_weight(&channel_type_features) / 1000,
confirmation_height: nodes[0].best_block_info().1 + ANTI_REORG_DELAY - 1,
}],
nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances());
assert_eq!(vec![Balance::ClaimableAwaitingConfirmations {
- claimable_amount_satoshis: 1000,
+ amount_satoshis: 1000,
confirmation_height: nodes[1].best_block_info().1 + ANTI_REORG_DELAY - 1,
}],
nodes[1].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances());
test_spendable_output(&nodes[0], &shutdown_tx[0]);
test_spendable_output(&nodes[1], &shutdown_tx[0]);
- check_closed_event!(nodes[0], 1, ClosureReason::CooperativeClosure);
- check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure);
+ check_closed_event!(nodes[0], 1, ClosureReason::CooperativeClosure, [nodes[1].node.get_our_node_id()], 1000000);
+ check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure, [nodes[0].node.get_our_node_id()], 1000000);
}
fn sorted_vec<T: Ord>(mut v: Vec<T>) -> Vec<T> {
assert_eq!(funding_outpoint.to_channel_id(), chan_id);
// This HTLC is immediately claimed, giving node B the preimage
- let (payment_preimage, payment_hash, _) = route_payment(&nodes[0], &[&nodes[1]], 3_000_000);
+ let (payment_preimage, payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1]], 3_000_000);
// This HTLC is allowed to time out, letting A claim it. However, in order to test claimable
// balances more fully we also give B the preimage for this HTLC.
- let (timeout_payment_preimage, timeout_payment_hash, _) = route_payment(&nodes[0], &[&nodes[1]], 4_000_000);
+ let (timeout_payment_preimage, timeout_payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1]], 4_000_000);
// This HTLC will be dust, and not be claimable at all:
- let (dust_payment_preimage, dust_payment_hash, _) = route_payment(&nodes[0], &[&nodes[1]], 3_000);
+ let (dust_payment_preimage, dust_payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1]], 3_000);
let htlc_cltv_timeout = nodes[0].best_block_info().1 + TEST_FINAL_CLTV + 1; // Note ChannelManager adds one to CLTV timeouts for safety
let remote_txn = get_local_commitment_txn!(nodes[1], chan_id);
let sent_htlc_balance = Balance::MaybeTimeoutClaimableHTLC {
- claimable_amount_satoshis: 3_000,
+ amount_satoshis: 3_000,
claimable_height: htlc_cltv_timeout,
payment_hash,
};
let sent_htlc_timeout_balance = Balance::MaybeTimeoutClaimableHTLC {
- claimable_amount_satoshis: 4_000,
+ amount_satoshis: 4_000,
claimable_height: htlc_cltv_timeout,
payment_hash: timeout_payment_hash,
};
let received_htlc_balance = Balance::MaybePreimageClaimableHTLC {
- claimable_amount_satoshis: 3_000,
+ amount_satoshis: 3_000,
expiry_height: htlc_cltv_timeout,
payment_hash,
};
let received_htlc_timeout_balance = Balance::MaybePreimageClaimableHTLC {
- claimable_amount_satoshis: 4_000,
+ amount_satoshis: 4_000,
expiry_height: htlc_cltv_timeout,
payment_hash: timeout_payment_hash,
};
let received_htlc_claiming_balance = Balance::ContentiousClaimable {
- claimable_amount_satoshis: 3_000,
+ amount_satoshis: 3_000,
timeout_height: htlc_cltv_timeout,
payment_hash,
payment_preimage,
};
let received_htlc_timeout_claiming_balance = Balance::ContentiousClaimable {
- claimable_amount_satoshis: 4_000,
+ amount_satoshis: 4_000,
timeout_height: htlc_cltv_timeout,
payment_hash: timeout_payment_hash,
payment_preimage: timeout_payment_preimage,
// Before B receives the payment preimage, it only suggests the push_msat value of 1_000 sats
// as claimable. A lists both its to-self balance and the (possibly-claimable) HTLCs.
assert_eq!(sorted_vec(vec![Balance::ClaimableOnChannelClose {
- claimable_amount_satoshis: 1_000_000 - 3_000 - 4_000 - 1_000 - 3 - chan_feerate *
+ amount_satoshis: 1_000_000 - 3_000 - 4_000 - 1_000 - 3 - chan_feerate *
(channel::commitment_tx_base_weight(&channel_type_features) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
}, sent_htlc_balance.clone(), sent_htlc_timeout_balance.clone()]),
sorted_vec(nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()));
assert_eq!(sorted_vec(vec![Balance::ClaimableOnChannelClose {
- claimable_amount_satoshis: 1_000,
+ amount_satoshis: 1_000,
}, received_htlc_balance.clone(), received_htlc_timeout_balance.clone()]),
sorted_vec(nodes[1].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()));
if prev_commitment_tx {
// To build a previous commitment transaction, deliver one round of commitment messages.
nodes[0].node.handle_update_fulfill_htlc(&nodes[1].node.get_our_node_id(), &b_htlc_msgs.update_fulfill_htlcs[0]);
- expect_payment_sent_without_paths!(nodes[0], payment_preimage);
+ expect_payment_sent(&nodes[0], payment_preimage, None, false, false);
nodes[0].node.handle_commitment_signed(&nodes[1].node.get_our_node_id(), &b_htlc_msgs.commitment_signed);
check_added_monitors!(nodes[0], 1);
let (as_raa, as_cs) = get_revoke_commit_msgs!(nodes[0], nodes[1].node.get_our_node_id());
// Once B has received the payment preimage, it includes the value of the HTLC in its
// "claimable if you were to close the channel" balance.
let mut a_expected_balances = vec![Balance::ClaimableOnChannelClose {
- claimable_amount_satoshis: 1_000_000 - // Channel funding value in satoshis
+ amount_satoshis: 1_000_000 - // Channel funding value in satoshis
4_000 - // The to-be-failed HTLC value in satoshis
3_000 - // The claimed HTLC value in satoshis
1_000 - // The push_msat value in satoshis
assert_eq!(sorted_vec(a_expected_balances),
sorted_vec(nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()));
assert_eq!(vec![Balance::ClaimableOnChannelClose {
- claimable_amount_satoshis: 1_000 + 3_000 + 4_000,
+ amount_satoshis: 1_000 + 3_000 + 4_000,
}],
nodes[1].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances());
assert!(nodes[0].node.list_channels().is_empty());
check_closed_broadcast!(nodes[0], true);
check_added_monitors!(nodes[0], 1);
- check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed, [nodes[1].node.get_our_node_id()], 1000000);
assert!(nodes[1].node.list_channels().is_empty());
check_closed_broadcast!(nodes[1], true);
check_added_monitors!(nodes[1], 1);
- check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [nodes[0].node.get_our_node_id()], 1000000);
assert!(nodes[0].node.get_and_clear_pending_events().is_empty());
assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
assert!(nodes[1].chain_monitor.chain_monitor.get_and_clear_pending_events().is_empty());
assert_eq!(sorted_vec(vec![Balance::ClaimableAwaitingConfirmations {
- claimable_amount_satoshis: 1_000_000 - 3_000 - 4_000 - 1_000 - 3 - chan_feerate *
+ amount_satoshis: 1_000_000 - 3_000 - 4_000 - 1_000 - 3 - chan_feerate *
(channel::commitment_tx_base_weight(&channel_type_features) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
confirmation_height: nodes[0].best_block_info().1 + ANTI_REORG_DELAY - 1,
}, sent_htlc_balance.clone(), sent_htlc_timeout_balance.clone()]),
// The main non-HTLC balance is just awaiting confirmations, but the claimable height is the
// CSV delay, not ANTI_REORG_DELAY.
assert_eq!(sorted_vec(vec![Balance::ClaimableAwaitingConfirmations {
- claimable_amount_satoshis: 1_000,
+ amount_satoshis: 1_000,
confirmation_height: node_b_commitment_claimable,
},
// Both HTLC balances are "contentious" as our counterparty could claim them if we wait too
assert_eq!(sorted_vec(vec![sent_htlc_balance.clone(), sent_htlc_timeout_balance.clone()]),
sorted_vec(nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()));
assert_eq!(sorted_vec(vec![Balance::ClaimableAwaitingConfirmations {
- claimable_amount_satoshis: 1_000,
+ amount_satoshis: 1_000,
confirmation_height: node_b_commitment_claimable,
}, received_htlc_claiming_balance.clone(), received_htlc_timeout_claiming_balance.clone()]),
sorted_vec(nodes[1].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()));
if prev_commitment_tx {
expect_payment_path_successful!(nodes[0]);
} else {
- expect_payment_sent!(nodes[0], payment_preimage);
+ expect_payment_sent(&nodes[0], payment_preimage, None, true, false);
}
assert_eq!(sorted_vec(vec![sent_htlc_balance.clone(), sent_htlc_timeout_balance.clone()]),
sorted_vec(nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()));
mine_transaction(&nodes[0], &a_broadcast_txn[1]);
assert!(nodes[0].node.get_and_clear_pending_events().is_empty());
assert_eq!(vec![Balance::ClaimableAwaitingConfirmations {
- claimable_amount_satoshis: 4_000,
+ amount_satoshis: 4_000,
confirmation_height: nodes[0].best_block_info().1 + ANTI_REORG_DELAY - 1,
}],
nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances());
mine_transaction(&nodes[1], &b_broadcast_txn[0]);
assert_eq!(sorted_vec(vec![Balance::ClaimableAwaitingConfirmations {
- claimable_amount_satoshis: 1_000,
+ amount_satoshis: 1_000,
confirmation_height: node_b_commitment_claimable,
}, Balance::ClaimableAwaitingConfirmations {
- claimable_amount_satoshis: 3_000,
+ amount_satoshis: 3_000,
confirmation_height: node_b_htlc_claimable,
}, received_htlc_timeout_claiming_balance.clone()]),
sorted_vec(nodes[1].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()));
test_spendable_output(&nodes[1], &remote_txn[0]);
assert_eq!(sorted_vec(vec![Balance::ClaimableAwaitingConfirmations {
- claimable_amount_satoshis: 3_000,
+ amount_satoshis: 3_000,
confirmation_height: node_b_htlc_claimable,
}, received_htlc_timeout_claiming_balance.clone()]),
sorted_vec(nodes[1].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()));
mine_transaction(&nodes[0], &as_txn[0]);
check_added_monitors!(nodes[0], 1);
check_closed_broadcast!(nodes[0], true);
- check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed, [nodes[1].node.get_our_node_id()], 1000000);
let htlc_balance_known_preimage = Balance::MaybeTimeoutClaimableHTLC {
- claimable_amount_satoshis: 10_000,
+ amount_satoshis: 10_000,
claimable_height: htlc_cltv_timeout,
payment_hash,
};
let htlc_balance_unknown_preimage = Balance::MaybeTimeoutClaimableHTLC {
- claimable_amount_satoshis: 20_000,
+ amount_satoshis: 20_000,
claimable_height: htlc_cltv_timeout,
payment_hash: payment_hash_2,
};
assert_eq!(sorted_vec(vec![Balance::ClaimableAwaitingConfirmations {
- claimable_amount_satoshis: 1_000_000 - 10_000 - 20_000 - chan_feerate *
+ amount_satoshis: 1_000_000 - 10_000 - 20_000 - chan_feerate *
(channel::commitment_tx_base_weight(&channel_type_features) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
confirmation_height: node_a_commitment_claimable,
}, htlc_balance_known_preimage.clone(), htlc_balance_unknown_preimage.clone()]),
mine_transaction(&nodes[1], &as_txn[0]);
check_added_monitors!(nodes[1], 1);
check_closed_broadcast!(nodes[1], true);
- check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [nodes[0].node.get_our_node_id()], 1000000);
let bs_htlc_claim_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
assert_eq!(bs_htlc_claim_txn.len(), 1);
check_spends!(bs_htlc_claim_txn[0], as_txn[0]);
// transaction.
connect_blocks(&nodes[0], TEST_FINAL_CLTV - 1);
assert_eq!(sorted_vec(vec![Balance::ClaimableAwaitingConfirmations {
- claimable_amount_satoshis: 1_000_000 - 10_000 - 20_000 - chan_feerate *
+ amount_satoshis: 1_000_000 - 10_000 - 20_000 - chan_feerate *
(channel::commitment_tx_base_weight(&channel_type_features) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
confirmation_height: node_a_commitment_claimable,
}, htlc_balance_known_preimage.clone(), htlc_balance_unknown_preimage.clone()]),
// balance) check failed. With this check removed, the code panicked in the `connect_blocks`
// call, as described, two hunks down.
assert_eq!(sorted_vec(vec![Balance::ClaimableAwaitingConfirmations {
- claimable_amount_satoshis: 1_000_000 - 10_000 - 20_000 - chan_feerate *
+ amount_satoshis: 1_000_000 - 10_000 - 20_000 - chan_feerate *
(channel::commitment_tx_base_weight(&channel_type_features) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
confirmation_height: node_a_commitment_claimable,
}, Balance::ClaimableAwaitingConfirmations {
- claimable_amount_satoshis: 10_000,
+ amount_satoshis: 10_000,
confirmation_height: node_a_htlc_claimable,
}, htlc_balance_unknown_preimage.clone()]),
sorted_vec(nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()));
// Now confirm nodes[1]'s HTLC claim, giving nodes[0] the preimage. Note that the "maybe
// claimable" balance remains until we see ANTI_REORG_DELAY blocks.
mine_transaction(&nodes[0], &bs_htlc_claim_txn[0]);
- expect_payment_sent!(nodes[0], payment_preimage_2);
+ expect_payment_sent(&nodes[0], payment_preimage_2, None, true, false);
assert_eq!(sorted_vec(vec![Balance::ClaimableAwaitingConfirmations {
- claimable_amount_satoshis: 1_000_000 - 10_000 - 20_000 - chan_feerate *
+ amount_satoshis: 1_000_000 - 10_000 - 20_000 - chan_feerate *
(channel::commitment_tx_base_weight(&channel_type_features) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
confirmation_height: node_a_commitment_claimable,
}, Balance::ClaimableAwaitingConfirmations {
- claimable_amount_satoshis: 10_000,
+ amount_satoshis: 10_000,
confirmation_height: node_a_htlc_claimable,
}, htlc_balance_unknown_preimage.clone()]),
sorted_vec(nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()));
expect_payment_failed!(nodes[0], payment_hash, false);
assert_eq!(sorted_vec(vec![Balance::ClaimableAwaitingConfirmations {
- claimable_amount_satoshis: 1_000_000 - 10_000 - 20_000 - chan_feerate *
+ amount_satoshis: 1_000_000 - 10_000 - 20_000 - chan_feerate *
(channel::commitment_tx_base_weight(&channel_type_features) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
confirmation_height: node_a_commitment_claimable,
}, Balance::ClaimableAwaitingConfirmations {
- claimable_amount_satoshis: 10_000,
+ amount_satoshis: 10_000,
confirmation_height: node_a_htlc_claimable,
}]),
sorted_vec(nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()));
// `SpendableOutputs` event and removing the claimable balance entry.
connect_blocks(&nodes[0], node_a_commitment_claimable - nodes[0].best_block_info().1);
assert_eq!(vec![Balance::ClaimableAwaitingConfirmations {
- claimable_amount_satoshis: 10_000,
+ amount_satoshis: 10_000,
confirmation_height: node_a_htlc_claimable,
}],
nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances());
let channel_type_features = get_channel_type_features!(nodes[0], nodes[1], chan_id);
let a_sent_htlc_balance = Balance::MaybeTimeoutClaimableHTLC {
- claimable_amount_satoshis: 10_000,
+ amount_satoshis: 10_000,
claimable_height: htlc_cltv_timeout,
payment_hash: to_b_failed_payment_hash,
};
let a_received_htlc_balance = Balance::MaybePreimageClaimableHTLC {
- claimable_amount_satoshis: 20_000,
+ amount_satoshis: 20_000,
expiry_height: htlc_cltv_timeout,
payment_hash: to_a_failed_payment_hash,
};
let b_received_htlc_balance = Balance::MaybePreimageClaimableHTLC {
- claimable_amount_satoshis: 10_000,
+ amount_satoshis: 10_000,
expiry_height: htlc_cltv_timeout,
payment_hash: to_b_failed_payment_hash,
};
let b_sent_htlc_balance = Balance::MaybeTimeoutClaimableHTLC {
- claimable_amount_satoshis: 20_000,
+ amount_satoshis: 20_000,
claimable_height: htlc_cltv_timeout,
payment_hash: to_a_failed_payment_hash,
};
// HTLC output is spent.
assert_eq!(sorted_vec(vec![Balance::ClaimableOnChannelClose {
- claimable_amount_satoshis: 1_000_000 - 500_000 - 10_000 - chan_feerate *
+ amount_satoshis: 1_000_000 - 500_000 - 10_000 - chan_feerate *
(channel::commitment_tx_base_weight(&channel_type_features) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
}, a_received_htlc_balance.clone(), a_sent_htlc_balance.clone()]),
sorted_vec(nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()));
assert_eq!(sorted_vec(vec![Balance::ClaimableOnChannelClose {
- claimable_amount_satoshis: 500_000 - 20_000,
+ amount_satoshis: 500_000 - 20_000,
}, b_received_htlc_balance.clone(), b_sent_htlc_balance.clone()]),
sorted_vec(nodes[1].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()));
// claimable balances remain the same except for the non-HTLC balance changing variant.
let node_a_commitment_claimable = nodes[0].best_block_info().1 + BREAKDOWN_TIMEOUT as u32;
let as_pre_spend_claims = sorted_vec(vec![Balance::ClaimableAwaitingConfirmations {
- claimable_amount_satoshis: 1_000_000 - 500_000 - 10_000 - chan_feerate *
+ amount_satoshis: 1_000_000 - 500_000 - 10_000 - chan_feerate *
(channel::commitment_tx_base_weight(&channel_type_features) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
confirmation_height: node_a_commitment_claimable,
}, a_received_htlc_balance.clone(), a_sent_htlc_balance.clone()]);
nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().clear();
check_added_monitors!(nodes[0], 1);
check_closed_broadcast!(nodes[0], true);
- check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed, [nodes[1].node.get_our_node_id()], 1000000);
assert_eq!(as_pre_spend_claims,
sorted_vec(nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()));
mine_transaction(&nodes[1], &as_txn[0]);
check_added_monitors!(nodes[1], 1);
check_closed_broadcast!(nodes[1], true);
- check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [nodes[0].node.get_our_node_id()], 1000000);
let node_b_commitment_claimable = nodes[1].best_block_info().1 + ANTI_REORG_DELAY - 1;
let mut bs_pre_spend_claims = sorted_vec(vec![Balance::ClaimableAwaitingConfirmations {
- claimable_amount_satoshis: 500_000 - 20_000,
+ amount_satoshis: 500_000 - 20_000,
confirmation_height: node_b_commitment_claimable,
}, b_received_htlc_balance.clone(), b_sent_htlc_balance.clone()]);
assert_eq!(bs_pre_spend_claims,
mine_transaction(&nodes[0], &as_htlc_timeout_claim[0]);
let as_timeout_claimable_height = nodes[0].best_block_info().1 + (BREAKDOWN_TIMEOUT as u32) - 1;
assert_eq!(sorted_vec(vec![Balance::ClaimableAwaitingConfirmations {
- claimable_amount_satoshis: 1_000_000 - 500_000 - 10_000 - chan_feerate *
+ amount_satoshis: 1_000_000 - 500_000 - 10_000 - chan_feerate *
(channel::commitment_tx_base_weight(&channel_type_features) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
confirmation_height: node_a_commitment_claimable,
}, a_received_htlc_balance.clone(), Balance::ClaimableAwaitingConfirmations {
- claimable_amount_satoshis: 10_000,
+ amount_satoshis: 10_000,
confirmation_height: as_timeout_claimable_height,
}]),
sorted_vec(nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()));
mine_transaction(&nodes[0], &bs_htlc_timeout_claim[0]);
assert_eq!(sorted_vec(vec![Balance::ClaimableAwaitingConfirmations {
- claimable_amount_satoshis: 1_000_000 - 500_000 - 10_000 - chan_feerate *
+ amount_satoshis: 1_000_000 - 500_000 - 10_000 - chan_feerate *
(channel::commitment_tx_base_weight(&channel_type_features) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
confirmation_height: node_a_commitment_claimable,
}, a_received_htlc_balance.clone(), Balance::ClaimableAwaitingConfirmations {
- claimable_amount_satoshis: 10_000,
+ amount_satoshis: 10_000,
confirmation_height: as_timeout_claimable_height,
}]),
sorted_vec(nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()));
connect_blocks(&nodes[0], 1);
assert_eq!(sorted_vec(vec![Balance::ClaimableAwaitingConfirmations {
- claimable_amount_satoshis: 1_000_000 - 500_000 - 10_000 - chan_feerate *
+ amount_satoshis: 1_000_000 - 500_000 - 10_000 - chan_feerate *
(channel::commitment_tx_base_weight(&channel_type_features) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
confirmation_height: node_a_commitment_claimable,
}, Balance::ClaimableAwaitingConfirmations {
- claimable_amount_satoshis: 10_000,
+ amount_satoshis: 10_000,
confirmation_height: core::cmp::max(as_timeout_claimable_height, htlc_cltv_timeout),
}]),
sorted_vec(nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()));
connect_blocks(&nodes[0], node_a_commitment_claimable - nodes[0].best_block_info().1);
assert_eq!(vec![Balance::ClaimableAwaitingConfirmations {
- claimable_amount_satoshis: 10_000,
+ amount_satoshis: 10_000,
confirmation_height: core::cmp::max(as_timeout_claimable_height, htlc_cltv_timeout),
}],
nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances());
mine_transaction(&nodes[1], &bs_htlc_timeout_claim[0]);
let bs_timeout_claimable_height = nodes[1].best_block_info().1 + ANTI_REORG_DELAY - 1;
assert_eq!(sorted_vec(vec![b_received_htlc_balance.clone(), Balance::ClaimableAwaitingConfirmations {
- claimable_amount_satoshis: 20_000,
+ amount_satoshis: 20_000,
confirmation_height: bs_timeout_claimable_height,
}]),
sorted_vec(nodes[1].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()));
mine_transaction(&nodes[1], &as_htlc_timeout_claim[0]);
assert_eq!(sorted_vec(vec![b_received_htlc_balance.clone(), Balance::ClaimableAwaitingConfirmations {
- claimable_amount_satoshis: 20_000,
+ amount_satoshis: 20_000,
confirmation_height: bs_timeout_claimable_height,
}]),
sorted_vec(nodes[1].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()));
assert!(failed_payments.is_empty());
if let Event::PendingHTLCsForwardable { .. } = events[0] {} else { panic!(); }
match &events[1] {
- Event::ChannelClosed { reason: ClosureReason::CommitmentTxConfirmed, .. } => {},
+ Event::ChannelClosed { reason: ClosureReason::HolderForceClosed, .. } => {},
_ => panic!(),
}
connect_blocks(&nodes[1], htlc_cltv_timeout + 1 - 10);
check_closed_broadcast!(nodes[1], true);
check_added_monitors!(nodes[1], 1);
- check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[1], 1, ClosureReason::HolderForceClosed, [nodes[0].node.get_our_node_id()], 1000000);
// Prior to channel closure, B considers the preimage HTLC as its own, and otherwise only
// lists the two on-chain timeout-able HTLCs as claimable balances.
assert_eq!(sorted_vec(vec![Balance::ClaimableOnChannelClose {
- claimable_amount_satoshis: 100_000 - 5_000 - 4_000 - 3 - 2_000 + 3_000,
+ amount_satoshis: 100_000 - 5_000 - 4_000 - 3 - 2_000 + 3_000,
}, Balance::MaybeTimeoutClaimableHTLC {
- claimable_amount_satoshis: 2_000,
+ amount_satoshis: 2_000,
claimable_height: missing_htlc_cltv_timeout,
payment_hash: missing_htlc_payment_hash,
}, Balance::MaybeTimeoutClaimableHTLC {
- claimable_amount_satoshis: 4_000,
+ amount_satoshis: 4_000,
claimable_height: htlc_cltv_timeout,
payment_hash: timeout_payment_hash,
}, Balance::MaybeTimeoutClaimableHTLC {
- claimable_amount_satoshis: 5_000,
+ amount_satoshis: 5_000,
claimable_height: live_htlc_cltv_timeout,
payment_hash: live_payment_hash,
}]),
// claim balances separated out.
let expected_balance = vec![Balance::ClaimableAwaitingConfirmations {
// to_remote output in A's revoked commitment
- claimable_amount_satoshis: 100_000 - 5_000 - 4_000 - 3,
+ amount_satoshis: 100_000 - 5_000 - 4_000 - 3,
confirmation_height: nodes[1].best_block_info().1 + 5,
}, Balance::CounterpartyRevokedOutputClaimable {
- claimable_amount_satoshis: 3_000,
+ amount_satoshis: 3_000,
}, Balance::CounterpartyRevokedOutputClaimable {
- claimable_amount_satoshis: 4_000,
+ amount_satoshis: 4_000,
}];
let to_self_unclaimed_balance = Balance::CounterpartyRevokedOutputClaimable {
- claimable_amount_satoshis: 1_000_000 - 100_000 - 3_000 - chan_feerate *
+ amount_satoshis: 1_000_000 - 100_000 - 3_000 - chan_feerate *
(channel::commitment_tx_base_weight(&channel_type_features) + 3 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
};
let to_self_claimed_avail_height;
let largest_htlc_unclaimed_balance = Balance::CounterpartyRevokedOutputClaimable {
- claimable_amount_satoshis: 5_000,
+ amount_satoshis: 5_000,
};
let largest_htlc_claimed_avail_height;
}
let largest_htlc_claimed_balance = Balance::ClaimableAwaitingConfirmations {
- claimable_amount_satoshis: 5_000 - chan_feerate * INBOUND_HTLC_CLAIM_EXP_WEIGHT as u64 / 1000,
+ amount_satoshis: 5_000 - chan_feerate * INBOUND_HTLC_CLAIM_EXP_WEIGHT as u64 / 1000,
confirmation_height: largest_htlc_claimed_avail_height,
};
let to_self_claimed_balance = Balance::ClaimableAwaitingConfirmations {
- claimable_amount_satoshis: 1_000_000 - 100_000 - 3_000 - chan_feerate *
+ amount_satoshis: 1_000_000 - 100_000 - 3_000 - chan_feerate *
(channel::commitment_tx_base_weight(&channel_type_features) + 3 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000
- chan_feerate * claim_txn[3].weight() as u64 / 1000,
confirmation_height: to_self_claimed_avail_height,
assert_eq!(sorted_vec(vec![Balance::ClaimableAwaitingConfirmations {
// to_remote output in A's revoked commitment
- claimable_amount_satoshis: 100_000 - 5_000 - 4_000 - 3,
+ amount_satoshis: 100_000 - 5_000 - 4_000 - 3,
confirmation_height: nodes[1].best_block_info().1 + 1,
}, Balance::ClaimableAwaitingConfirmations {
- claimable_amount_satoshis: 1_000_000 - 100_000 - 3_000 - chan_feerate *
+ amount_satoshis: 1_000_000 - 100_000 - 3_000 - chan_feerate *
(channel::commitment_tx_base_weight(&channel_type_features) + 3 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000
- chan_feerate * claim_txn[3].weight() as u64 / 1000,
confirmation_height: to_self_claimed_avail_height,
}, Balance::ClaimableAwaitingConfirmations {
- claimable_amount_satoshis: 3_000 - chan_feerate * OUTBOUND_HTLC_CLAIM_EXP_WEIGHT as u64 / 1000,
+ amount_satoshis: 3_000 - chan_feerate * OUTBOUND_HTLC_CLAIM_EXP_WEIGHT as u64 / 1000,
confirmation_height: nodes[1].best_block_info().1 + 4,
}, Balance::ClaimableAwaitingConfirmations {
- claimable_amount_satoshis: 4_000 - chan_feerate * INBOUND_HTLC_CLAIM_EXP_WEIGHT as u64 / 1000,
+ amount_satoshis: 4_000 - chan_feerate * INBOUND_HTLC_CLAIM_EXP_WEIGHT as u64 / 1000,
confirmation_height: nodes[1].best_block_info().1 + 5,
}, Balance::ClaimableAwaitingConfirmations {
- claimable_amount_satoshis: 5_000 - chan_feerate * INBOUND_HTLC_CLAIM_EXP_WEIGHT as u64 / 1000,
+ amount_satoshis: 5_000 - chan_feerate * INBOUND_HTLC_CLAIM_EXP_WEIGHT as u64 / 1000,
confirmation_height: largest_htlc_claimed_avail_height,
}]),
sorted_vec(nodes[1].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()));
mine_transaction(&nodes[1], &revoked_local_txn[0]);
check_closed_broadcast!(nodes[1], true);
check_added_monitors!(nodes[1], 1);
- check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [nodes[0].node.get_our_node_id()], 1000000);
let revoked_htlc_success = {
let mut txn = nodes[1].tx_broadcaster.txn_broadcast();
assert_eq!(txn.len(), 1);
mine_transaction(&nodes[0], &revoked_local_txn[0]);
check_closed_broadcast!(nodes[0], true);
check_added_monitors!(nodes[0], 1);
- check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed, [nodes[1].node.get_our_node_id()], 1000000);
let to_remote_conf_height = nodes[0].best_block_info().1 + ANTI_REORG_DELAY - 1;
let as_commitment_claim_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
// `CounterpartyRevokedOutputClaimable` entry doesn't change.
let as_balances = sorted_vec(vec![Balance::ClaimableAwaitingConfirmations {
// to_remote output in B's revoked commitment
- claimable_amount_satoshis: 1_000_000 - 11_000 - 3_000 - chan_feerate *
+ amount_satoshis: 1_000_000 - 11_000 - 3_000 - chan_feerate *
(channel::commitment_tx_base_weight(&channel_type_features) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
confirmation_height: to_remote_conf_height,
}, Balance::CounterpartyRevokedOutputClaimable {
// to_self output in B's revoked commitment
- claimable_amount_satoshis: 10_000,
+ amount_satoshis: 10_000,
}, Balance::CounterpartyRevokedOutputClaimable { // HTLC 1
- claimable_amount_satoshis: 3_000,
+ amount_satoshis: 3_000,
}, Balance::CounterpartyRevokedOutputClaimable { // HTLC 2
- claimable_amount_satoshis: 1_000,
+ amount_satoshis: 1_000,
}]);
assert_eq!(as_balances,
sorted_vec(nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()));
mine_transaction(&nodes[0], &as_htlc_claim_tx[0]);
assert_eq!(sorted_vec(vec![Balance::ClaimableAwaitingConfirmations {
// to_remote output in B's revoked commitment
- claimable_amount_satoshis: 1_000_000 - 11_000 - 3_000 - chan_feerate *
+ amount_satoshis: 1_000_000 - 11_000 - 3_000 - chan_feerate *
(channel::commitment_tx_base_weight(&channel_type_features) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
confirmation_height: to_remote_conf_height,
}, Balance::CounterpartyRevokedOutputClaimable {
// to_self output in B's revoked commitment
- claimable_amount_satoshis: 10_000,
+ amount_satoshis: 10_000,
}, Balance::CounterpartyRevokedOutputClaimable { // HTLC 2
- claimable_amount_satoshis: 1_000,
+ amount_satoshis: 1_000,
}, Balance::ClaimableAwaitingConfirmations {
- claimable_amount_satoshis: as_htlc_claim_tx[0].output[0].value,
+ amount_satoshis: as_htlc_claim_tx[0].output[0].value,
confirmation_height: nodes[0].best_block_info().1 + ANTI_REORG_DELAY - 1,
}]),
sorted_vec(nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()));
test_spendable_output(&nodes[0], &revoked_local_txn[0]);
assert_eq!(sorted_vec(vec![Balance::CounterpartyRevokedOutputClaimable {
// to_self output to B
- claimable_amount_satoshis: 10_000,
+ amount_satoshis: 10_000,
}, Balance::CounterpartyRevokedOutputClaimable { // HTLC 2
- claimable_amount_satoshis: 1_000,
+ amount_satoshis: 1_000,
}, Balance::ClaimableAwaitingConfirmations {
- claimable_amount_satoshis: as_htlc_claim_tx[0].output[0].value,
+ amount_satoshis: as_htlc_claim_tx[0].output[0].value,
confirmation_height: nodes[0].best_block_info().1 + 2,
}]),
sorted_vec(nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()));
test_spendable_output(&nodes[0], &as_htlc_claim_tx[0]);
assert_eq!(sorted_vec(vec![Balance::CounterpartyRevokedOutputClaimable {
// to_self output in B's revoked commitment
- claimable_amount_satoshis: 10_000,
+ amount_satoshis: 10_000,
}, Balance::CounterpartyRevokedOutputClaimable { // HTLC 2
- claimable_amount_satoshis: 1_000,
+ amount_satoshis: 1_000,
}]),
sorted_vec(nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()));
connect_blocks(&nodes[0], ANTI_REORG_DELAY - 1);
assert_eq!(sorted_vec(vec![Balance::CounterpartyRevokedOutputClaimable {
// to_self output in B's revoked commitment
- claimable_amount_satoshis: 10_000,
+ amount_satoshis: 10_000,
}, Balance::CounterpartyRevokedOutputClaimable { // HTLC 2
- claimable_amount_satoshis: 1_000,
+ amount_satoshis: 1_000,
}]),
sorted_vec(nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()));
mine_transaction(&nodes[0], &as_second_htlc_claim_tx[0]);
assert_eq!(sorted_vec(vec![Balance::CounterpartyRevokedOutputClaimable {
// to_self output in B's revoked commitment
- claimable_amount_satoshis: 10_000,
+ amount_satoshis: 10_000,
}, Balance::ClaimableAwaitingConfirmations {
- claimable_amount_satoshis: as_second_htlc_claim_tx[0].output[0].value,
+ amount_satoshis: as_second_htlc_claim_tx[0].output[0].value,
confirmation_height: nodes[0].best_block_info().1 + ANTI_REORG_DELAY - 1,
}]),
sorted_vec(nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()));
mine_transaction(&nodes[0], &as_second_htlc_claim_tx[1]);
assert_eq!(sorted_vec(vec![Balance::ClaimableAwaitingConfirmations {
// to_self output in B's revoked commitment
- claimable_amount_satoshis: as_second_htlc_claim_tx[1].output[0].value,
+ amount_satoshis: as_second_htlc_claim_tx[1].output[0].value,
confirmation_height: nodes[0].best_block_info().1 + ANTI_REORG_DELAY - 1,
}, Balance::ClaimableAwaitingConfirmations {
- claimable_amount_satoshis: as_second_htlc_claim_tx[0].output[0].value,
+ amount_satoshis: as_second_htlc_claim_tx[0].output[0].value,
confirmation_height: nodes[0].best_block_info().1 + ANTI_REORG_DELAY - 2,
}]),
sorted_vec(nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()));
let _a_htlc_msgs = get_htlc_update_msgs!(&nodes[0], nodes[1].node.get_our_node_id());
assert_eq!(sorted_vec(vec![Balance::ClaimableOnChannelClose {
- claimable_amount_satoshis: 100_000 - 4_000 - 3_000,
+ amount_satoshis: 100_000 - 4_000 - 3_000,
}, Balance::MaybeTimeoutClaimableHTLC {
- claimable_amount_satoshis: 4_000,
+ amount_satoshis: 4_000,
claimable_height: htlc_cltv_timeout,
payment_hash: revoked_payment_hash,
}, Balance::MaybeTimeoutClaimableHTLC {
- claimable_amount_satoshis: 3_000,
+ amount_satoshis: 3_000,
claimable_height: htlc_cltv_timeout,
payment_hash: claimed_payment_hash,
}]),
mine_transaction(&nodes[1], &as_revoked_txn[0]);
check_closed_broadcast!(nodes[1], true);
- check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [nodes[0].node.get_our_node_id()], 1000000);
check_added_monitors!(nodes[1], 1);
let mut claim_txn: Vec<_> = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().drain(..).filter(|tx| tx.input.iter().any(|inp| inp.previous_output.txid == as_revoked_txn[0].txid())).collect();
assert_eq!(sorted_vec(vec![Balance::ClaimableAwaitingConfirmations {
// to_remote output in A's revoked commitment
- claimable_amount_satoshis: 100_000 - 4_000 - 3_000,
+ amount_satoshis: 100_000 - 4_000 - 3_000,
confirmation_height: to_remote_maturity,
}, Balance::CounterpartyRevokedOutputClaimable {
// to_self output in A's revoked commitment
- claimable_amount_satoshis: 1_000_000 - 100_000 - chan_feerate *
+ amount_satoshis: 1_000_000 - 100_000 - chan_feerate *
(channel::commitment_tx_base_weight(&channel_type_features) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
}, Balance::CounterpartyRevokedOutputClaimable { // HTLC 1
- claimable_amount_satoshis: 4_000,
+ amount_satoshis: 4_000,
}, Balance::CounterpartyRevokedOutputClaimable { // HTLC 2
- claimable_amount_satoshis: 3_000,
+ amount_satoshis: 3_000,
}]),
sorted_vec(nodes[1].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()));
// Confirm A's HTLC-Success tranasction which presumably raced B's claim, causing B to create a
// new claim.
mine_transaction(&nodes[1], &as_revoked_txn[1]);
- expect_payment_sent!(nodes[1], claimed_payment_preimage);
+ expect_payment_sent(&nodes[1], claimed_payment_preimage, None, true, false);
let mut claim_txn_2: Vec<_> = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().clone();
claim_txn_2.sort_unstable_by_key(|tx| if tx.input.iter().any(|inp| inp.previous_output.txid == as_revoked_txn[0].txid()) { 0 } else { 1 });
// Once B sees the HTLC-Success transaction it splits its claim transaction into two, though in
assert_eq!(sorted_vec(vec![Balance::ClaimableAwaitingConfirmations {
// to_remote output in A's revoked commitment
- claimable_amount_satoshis: 100_000 - 4_000 - 3_000,
+ amount_satoshis: 100_000 - 4_000 - 3_000,
confirmation_height: to_remote_maturity,
}, Balance::CounterpartyRevokedOutputClaimable {
// to_self output in A's revoked commitment
- claimable_amount_satoshis: 1_000_000 - 100_000 - chan_feerate *
+ amount_satoshis: 1_000_000 - 100_000 - chan_feerate *
(channel::commitment_tx_base_weight(&channel_type_features) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
}, Balance::CounterpartyRevokedOutputClaimable { // HTLC 1
- claimable_amount_satoshis: 4_000,
+ amount_satoshis: 4_000,
}, Balance::CounterpartyRevokedOutputClaimable { // HTLC 2
// The amount here is a bit of a misnomer, really its been reduced by the HTLC
// transaction fee, but the claimable amount is always a bit of an overshoot for HTLCs
// anyway, so its not a big change.
- claimable_amount_satoshis: 3_000,
+ amount_satoshis: 3_000,
}]),
sorted_vec(nodes[1].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()));
assert_eq!(sorted_vec(vec![Balance::CounterpartyRevokedOutputClaimable {
// to_self output in A's revoked commitment
- claimable_amount_satoshis: 1_000_000 - 100_000 - chan_feerate *
+ amount_satoshis: 1_000_000 - 100_000 - chan_feerate *
(channel::commitment_tx_base_weight(&channel_type_features) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
}, Balance::CounterpartyRevokedOutputClaimable { // HTLC 1
- claimable_amount_satoshis: 4_000,
+ amount_satoshis: 4_000,
}, Balance::CounterpartyRevokedOutputClaimable { // HTLC 2
// The amount here is a bit of a misnomer, really its been reduced by the HTLC
// transaction fee, but the claimable amount is always a bit of an overshoot for HTLCs
// anyway, so its not a big change.
- claimable_amount_satoshis: 3_000,
+ amount_satoshis: 3_000,
}]),
sorted_vec(nodes[1].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()));
assert_eq!(sorted_vec(vec![Balance::CounterpartyRevokedOutputClaimable {
// to_self output in A's revoked commitment
- claimable_amount_satoshis: 1_000_000 - 100_000 - chan_feerate *
+ amount_satoshis: 1_000_000 - 100_000 - chan_feerate *
(channel::commitment_tx_base_weight(&channel_type_features) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
}, Balance::CounterpartyRevokedOutputClaimable { // HTLC 1
- claimable_amount_satoshis: 4_000,
+ amount_satoshis: 4_000,
}, Balance::ClaimableAwaitingConfirmations { // HTLC 2
- claimable_amount_satoshis: claim_txn_2[1].output[0].value,
+ amount_satoshis: claim_txn_2[1].output[0].value,
confirmation_height: htlc_2_claim_maturity,
}]),
sorted_vec(nodes[1].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()));
assert_eq!(sorted_vec(vec![Balance::CounterpartyRevokedOutputClaimable {
// to_self output in A's revoked commitment
- claimable_amount_satoshis: 1_000_000 - 100_000 - chan_feerate *
+ amount_satoshis: 1_000_000 - 100_000 - chan_feerate *
(channel::commitment_tx_base_weight(&channel_type_features) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
}, Balance::CounterpartyRevokedOutputClaimable { // HTLC 1
- claimable_amount_satoshis: 4_000,
+ amount_satoshis: 4_000,
}]),
sorted_vec(nodes[1].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()));
let rest_claim_maturity = nodes[1].best_block_info().1 + ANTI_REORG_DELAY - 1;
assert_eq!(vec![Balance::ClaimableAwaitingConfirmations {
- claimable_amount_satoshis: claim_txn_2[0].output[0].value,
+ amount_satoshis: claim_txn_2[0].output[0].value,
confirmation_height: rest_claim_maturity,
}],
nodes[1].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances());
fn do_test_restored_packages_retry(check_old_monitor_retries_after_upgrade: bool) {
// Tests that we'll retry packages that were previously timelocked after we've restored them.
+ let chanmon_cfgs = create_chanmon_cfgs(2);
+ let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
let persister;
let new_chain_monitor;
- let node_deserialized;
- let chanmon_cfgs = create_chanmon_cfgs(2);
- let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
+ let node_deserialized;
+
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
// Open a channel, lock in an HTLC, and immediately broadcast the commitment transaction. This
nodes[0].node.force_close_broadcasting_latest_txn(&chan_id, &nodes[1].node.get_our_node_id()).unwrap();
check_added_monitors(&nodes[0], 1);
check_closed_broadcast(&nodes[0], 1, true);
- check_closed_event(&nodes[0], 1, ClosureReason::HolderForceClosed, false);
+ check_closed_event!(&nodes[0], 1, ClosureReason::HolderForceClosed, false,
+ [nodes[1].node.get_our_node_id()], 100000);
let commitment_tx = {
let mut txn = nodes[0].tx_broadcaster.txn_broadcast();
check_spends!(&commitment_txn[0], &funding_tx);
mine_transaction(&nodes[0], &commitment_txn[0]);
check_closed_broadcast!(&nodes[0], true);
- check_closed_event(&nodes[0], 1, ClosureReason::CommitmentTxConfirmed, false);
+ check_closed_event!(&nodes[0], 1, ClosureReason::CommitmentTxConfirmed,
+ false, [nodes[1].node.get_our_node_id()], 1000000);
check_added_monitors(&nodes[0], 1);
+ let coinbase_tx = Transaction {
+ version: 2,
+ lock_time: PackedLockTime::ZERO,
+ input: vec![TxIn { ..Default::default() }],
+ output: vec![TxOut { // UTXO to attach fees to `htlc_tx` on anchors
+ value: Amount::ONE_BTC.to_sat(),
+ script_pubkey: nodes[0].wallet_source.get_change_script().unwrap(),
+ }],
+ };
+ nodes[0].wallet_source.add_utxo(bitcoin::OutPoint { txid: coinbase_tx.txid(), vout: 0 }, coinbase_tx.output[0].value);
+
// Set up a helper closure we'll use throughout our test. We should only expect retries without
// bumps if fees have not increased after a block has been connected (assuming the height timer
// re-evaluates at every block) or after `ChainMonitor::rebroadcast_pending_claims` is called.
let mut check_htlc_retry = |should_retry: bool, should_bump: bool| -> Option<Transaction> {
let (htlc_tx, htlc_tx_feerate) = if anchors {
assert!(nodes[0].tx_broadcaster.txn_broadcast().is_empty());
- let mut events = nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events();
+ let events = nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events();
assert_eq!(events.len(), if should_retry { 1 } else { 0 });
if !should_retry {
return None;
}
- #[allow(unused_assignments)]
- let mut tx = Transaction {
- version: 2,
- lock_time: bitcoin::PackedLockTime::ZERO,
- input: vec![],
- output: vec![],
- };
- #[allow(unused_assignments)]
- let mut feerate = 0;
- feerate = if let Event::BumpTransaction(BumpTransactionEvent::HTLCResolution {
- target_feerate_sat_per_1000_weight, mut htlc_descriptors, tx_lock_time, ..
- }) = events.pop().unwrap() {
- let secp = Secp256k1::new();
- assert_eq!(htlc_descriptors.len(), 1);
- let descriptor = htlc_descriptors.pop().unwrap();
- assert_eq!(descriptor.commitment_txid, commitment_txn[0].txid());
- let htlc_output_idx = descriptor.htlc.transaction_output_index.unwrap() as usize;
- assert!(htlc_output_idx < commitment_txn[0].output.len());
- tx.lock_time = tx_lock_time;
- // Note that we don't care about actually making the HTLC transaction meet the
- // feerate for the test, we just want to make sure the feerates we receive from
- // the events never decrease.
- tx.input.push(descriptor.unsigned_tx_input());
- let signer = nodes[0].keys_manager.derive_channel_keys(
- descriptor.channel_value_satoshis, &descriptor.channel_keys_id,
- );
- let per_commitment_point = signer.get_per_commitment_point(
- descriptor.per_commitment_number, &secp
- );
- tx.output.push(descriptor.tx_output(&per_commitment_point, &secp));
- let our_sig = signer.sign_holder_htlc_transaction(&mut tx, 0, &descriptor, &secp).unwrap();
- let witness_script = descriptor.witness_script(&per_commitment_point, &secp);
- tx.input[0].witness = descriptor.tx_input_witness(&our_sig, &witness_script);
- target_feerate_sat_per_1000_weight as u64
- } else { panic!("unexpected event"); };
- (tx, feerate)
+ match &events[0] {
+ Event::BumpTransaction(event) => {
+ nodes[0].bump_tx_handler.handle_event(&event);
+ let mut txn = nodes[0].tx_broadcaster.unique_txn_broadcast();
+ assert_eq!(txn.len(), 1);
+ let htlc_tx = txn.pop().unwrap();
+ check_spends!(&htlc_tx, &commitment_txn[0], &coinbase_tx);
+ let htlc_tx_fee = HTLC_AMT_SAT + coinbase_tx.output[0].value -
+ htlc_tx.output.iter().map(|output| output.value).sum::<u64>();
+ let htlc_tx_weight = htlc_tx.weight() as u64;
+ (htlc_tx, compute_feerate_sat_per_1000_weight(htlc_tx_fee, htlc_tx_weight))
+ }
+ _ => panic!("Unexpected event"),
+ }
} else {
assert!(nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events().is_empty());
let mut txn = nodes[0].tx_broadcaster.txn_broadcast();
let htlc_tx = txn.pop().unwrap();
check_spends!(htlc_tx, commitment_txn[0]);
let htlc_tx_fee = HTLC_AMT_SAT - htlc_tx.output[0].value;
- let htlc_tx_feerate = htlc_tx_fee * 1000 / htlc_tx.weight() as u64;
- (htlc_tx, htlc_tx_feerate)
+ let htlc_tx_weight = htlc_tx.weight() as u64;
+ (htlc_tx, compute_feerate_sat_per_1000_weight(htlc_tx_fee, htlc_tx_weight))
};
if should_bump {
assert!(htlc_tx_feerate > prev_htlc_tx_feerate.take().unwrap());
// Mine the HTLC transaction to ensure we don't retry claims while they're confirmed.
mine_transaction(&nodes[0], &htlc_tx);
- // If we have a `ConnectStyle` that advertises the new block first without the transasctions,
+ // If we have a `ConnectStyle` that advertises the new block first without the transactions,
// we'll receive an extra bumped claim.
if nodes[0].connect_style.borrow().updates_best_block_first() {
+ nodes[0].wallet_source.add_utxo(bitcoin::OutPoint { txid: coinbase_tx.txid(), vout: 0 }, coinbase_tx.output[0].value);
+ nodes[0].wallet_source.remove_utxo(bitcoin::OutPoint { txid: htlc_tx.txid(), vout: 1 });
check_htlc_retry(true, anchors);
}
nodes[0].chain_monitor.chain_monitor.rebroadcast_pending_claims();
// allowing the consumer to provide additional fees to the commitment transaction to be
// broadcast. Once the commitment transaction confirms, events for the HTLC resolution should be
// emitted by LDK, such that the consumer can attach fees to the zero fee HTLC transactions.
- let secp = Secp256k1::new();
let mut chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
let mut anchors_config = UserConfig::default();
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(anchors_config), Some(anchors_config)]);
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
- let chan_id = create_announced_chan_between_nodes_with_value(
+ let (_, _, chan_id, funding_tx) = create_announced_chan_between_nodes_with_value(
&nodes, 0, 1, 1_000_000, 500_000_000
- ).2;
- route_payment(&nodes[0], &[&nodes[1]], 1_000_000);
- let (payment_preimage, payment_hash, _) = route_payment(&nodes[1], &[&nodes[0]], 1_000_000);
+ );
+ let (payment_preimage_1, payment_hash_1, ..) = route_payment(&nodes[0], &[&nodes[1]], 1_000_000);
+ let (payment_preimage_2, payment_hash_2, ..) = route_payment(&nodes[1], &[&nodes[0]], 2_000_000);
assert!(nodes[0].node.get_and_clear_pending_events().is_empty());
+ assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
+
+ *nodes[0].fee_estimator.sat_per_kw.lock().unwrap() *= 2;
connect_blocks(&nodes[0], TEST_FINAL_CLTV + LATENCY_GRACE_PERIOD_BLOCKS + 1);
- check_closed_broadcast!(&nodes[0], true);
- assert!(nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().is_empty());
+ assert!(nodes[0].tx_broadcaster.txn_broadcast().is_empty());
+
+ connect_blocks(&nodes[1], TEST_FINAL_CLTV + LATENCY_GRACE_PERIOD_BLOCKS + 1);
+ {
+ let txn = nodes[1].tx_broadcaster.txn_broadcast();
+ assert_eq!(txn.len(), 1);
+ check_spends!(txn[0], funding_tx);
+ }
get_monitor!(nodes[0], chan_id).provide_payment_preimage(
- &payment_hash, &payment_preimage, &node_cfgs[0].tx_broadcaster,
+ &payment_hash_2, &payment_preimage_2, &node_cfgs[0].tx_broadcaster,
&LowerBoundedFeeEstimator::new(node_cfgs[0].fee_estimator), &nodes[0].logger
);
+ get_monitor!(nodes[1], chan_id).provide_payment_preimage(
+ &payment_hash_1, &payment_preimage_1, &node_cfgs[0].tx_broadcaster,
+ &LowerBoundedFeeEstimator::new(node_cfgs[1].fee_estimator), &nodes[1].logger
+ );
let mut holder_events = nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events();
assert_eq!(holder_events.len(), 1);
let (commitment_tx, anchor_tx) = match holder_events.pop().unwrap() {
- Event::BumpTransaction(BumpTransactionEvent::ChannelClose { commitment_tx, anchor_descriptor, .. }) => {
- assert_eq!(commitment_tx.input.len(), 1);
- assert_eq!(commitment_tx.output.len(), 6);
- let mut anchor_tx = Transaction {
+ Event::BumpTransaction(event) => {
+ let coinbase_tx = Transaction {
version: 2,
lock_time: PackedLockTime::ZERO,
- input: vec![
- TxIn { previous_output: anchor_descriptor.outpoint, ..Default::default() },
- TxIn { ..Default::default() },
- ],
- output: vec![TxOut {
+ input: vec![TxIn { ..Default::default() }],
+ output: vec![TxOut { // UTXO to attach fees to `anchor_tx`
value: Amount::ONE_BTC.to_sat(),
- script_pubkey: Script::new_op_return(&[]),
+ script_pubkey: nodes[0].wallet_source.get_change_script().unwrap(),
}],
};
- let signer = nodes[0].keys_manager.derive_channel_keys(
- anchor_descriptor.channel_value_satoshis, &anchor_descriptor.channel_keys_id,
- );
- let funding_sig = signer.sign_holder_anchor_input(&mut anchor_tx, 0, &secp).unwrap();
- anchor_tx.input[0].witness = chan_utils::build_anchor_input_witness(
- &signer.pubkeys().funding_pubkey, &funding_sig
- );
+ nodes[0].wallet_source.add_utxo(bitcoin::OutPoint { txid: coinbase_tx.txid(), vout: 0 }, coinbase_tx.output[0].value);
+ nodes[0].bump_tx_handler.handle_event(&event);
+ let mut txn = nodes[0].tx_broadcaster.unique_txn_broadcast();
+ assert_eq!(txn.len(), 2);
+ let anchor_tx = txn.pop().unwrap();
+ let commitment_tx = txn.pop().unwrap();
+ check_spends!(commitment_tx, funding_tx);
+ check_spends!(anchor_tx, coinbase_tx, commitment_tx);
(commitment_tx, anchor_tx)
},
_ => panic!("Unexpected event"),
};
+ assert_eq!(commitment_tx.output[2].value, 1_000); // HTLC A -> B
+ assert_eq!(commitment_tx.output[3].value, 2_000); // HTLC B -> A
+
mine_transactions(&nodes[0], &[&commitment_tx, &anchor_tx]);
check_added_monitors!(nodes[0], 1);
+ mine_transactions(&nodes[1], &[&commitment_tx, &anchor_tx]);
+ check_added_monitors!(nodes[1], 1);
+
+ {
+ let mut txn = nodes[1].tx_broadcaster.unique_txn_broadcast();
+ assert_eq!(txn.len(), if nodes[1].connect_style.borrow().updates_best_block_first() { 3 } else { 2 });
+
+ let htlc_preimage_tx = txn.pop().unwrap();
+ assert_eq!(htlc_preimage_tx.input.len(), 1);
+ assert_eq!(htlc_preimage_tx.input[0].previous_output.vout, 3);
+ check_spends!(htlc_preimage_tx, commitment_tx);
+
+ let htlc_timeout_tx = txn.pop().unwrap();
+ assert_eq!(htlc_timeout_tx.input.len(), 1);
+ assert_eq!(htlc_timeout_tx.input[0].previous_output.vout, 2);
+ check_spends!(htlc_timeout_tx, commitment_tx);
+
+ if let Some(commitment_tx) = txn.pop() {
+ check_spends!(commitment_tx, funding_tx);
+ }
+ }
let mut holder_events = nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events();
// Certain block `ConnectStyle`s cause an extra `ChannelClose` event to be emitted since the
// best block is updated before the confirmed transactions are notified.
- match *nodes[0].connect_style.borrow() {
- ConnectStyle::BestBlockFirst|ConnectStyle::BestBlockFirstReorgsOnlyTip|ConnectStyle::BestBlockFirstSkippingBlocks => {
- assert_eq!(holder_events.len(), 3);
- if let Event::BumpTransaction(BumpTransactionEvent::ChannelClose { .. }) = holder_events.remove(0) {}
- else { panic!("unexpected event"); }
-
- },
- _ => assert_eq!(holder_events.len(), 2),
- };
+ if nodes[0].connect_style.borrow().updates_best_block_first() {
+ assert_eq!(holder_events.len(), 3);
+ if let Event::BumpTransaction(BumpTransactionEvent::ChannelClose { .. }) = holder_events.remove(0) {}
+ else { panic!("unexpected event"); }
+ } else {
+ assert_eq!(holder_events.len(), 2);
+ }
let mut htlc_txs = Vec::with_capacity(2);
for event in holder_events {
match event {
- Event::BumpTransaction(BumpTransactionEvent::HTLCResolution { htlc_descriptors, tx_lock_time, .. }) => {
- assert_eq!(htlc_descriptors.len(), 1);
- let htlc_descriptor = &htlc_descriptors[0];
- let signer = nodes[0].keys_manager.derive_channel_keys(
- htlc_descriptor.channel_value_satoshis, &htlc_descriptor.channel_keys_id
- );
- let per_commitment_point = signer.get_per_commitment_point(htlc_descriptor.per_commitment_number, &secp);
- let mut htlc_tx = Transaction {
- version: 2,
- lock_time: tx_lock_time,
- input: vec![
- htlc_descriptor.unsigned_tx_input(), // HTLC input
- TxIn { ..Default::default() } // Fee input
- ],
- output: vec![
- htlc_descriptor.tx_output(&per_commitment_point, &secp), // HTLC output
- TxOut { // Fee input change
- value: Amount::ONE_BTC.to_sat(),
- script_pubkey: Script::new_op_return(&[]),
- }
- ]
- };
- let our_sig = signer.sign_holder_htlc_transaction(&mut htlc_tx, 0, htlc_descriptor, &secp).unwrap();
- let witness_script = htlc_descriptor.witness_script(&per_commitment_point, &secp);
- htlc_tx.input[0].witness = htlc_descriptor.tx_input_witness(&our_sig, &witness_script);
+ Event::BumpTransaction(event) => {
+ nodes[0].bump_tx_handler.handle_event(&event);
+ let mut txn = nodes[0].tx_broadcaster.unique_txn_broadcast();
+ assert_eq!(txn.len(), 1);
+ let htlc_tx = txn.pop().unwrap();
+ check_spends!(htlc_tx, commitment_tx, anchor_tx);
htlc_txs.push(htlc_tx);
},
_ => panic!("Unexpected event"),
// Clear the remaining events as they're not relevant to what we're testing.
nodes[0].node.get_and_clear_pending_events();
+ nodes[1].node.get_and_clear_pending_events();
+ nodes[0].node.get_and_clear_pending_msg_events();
+ nodes[1].node.get_and_clear_pending_msg_events();
}
#[test]
// Required to sign a revoked commitment transaction
chanmon_cfgs[1].keys_manager.disable_revocation_policy_check = true;
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
+ let bob_persister;
+ let bob_chain_monitor;
+
let mut anchors_config = UserConfig::default();
anchors_config.channel_handshake_config.announced_channel = true;
anchors_config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;
anchors_config.manually_accept_inbound_channels = true;
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(anchors_config), Some(anchors_config)]);
-
- let bob_persister: test_utils::TestPersister;
- let bob_chain_monitor: test_utils::TestChainMonitor;
- let bob_deserialized: ChannelManager<
- &test_utils::TestChainMonitor, &test_utils::TestBroadcaster, &test_utils::TestKeysInterface,
- &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestFeeEstimator,
- &test_utils::TestRouter, &test_utils::TestLogger,
- >;
+ let bob_deserialized;
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
// Bob force closes by restarting with the outdated state, prompting the ChannelMonitors to
// broadcast the latest commitment transaction known to them, which in our case is the one with
// the HTLCs still pending.
+ *nodes[1].fee_estimator.sat_per_kw.lock().unwrap() *= 2;
nodes[1].node.timer_tick_occurred();
check_added_monitors(&nodes[1], 2);
- check_closed_event!(&nodes[1], 2, ClosureReason::OutdatedChannelManager);
+ check_closed_event!(&nodes[1], 2, ClosureReason::OutdatedChannelManager, [nodes[0].node.get_our_node_id(); 2], 1000000);
let (revoked_commitment_a, revoked_commitment_b) = {
- let txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
+ let txn = nodes[1].tx_broadcaster.unique_txn_broadcast();
assert_eq!(txn.len(), 2);
assert_eq!(txn[0].output.len(), 6); // 2 HTLC outputs + 1 to_self output + 1 to_remote output + 2 anchor outputs
assert_eq!(txn[1].output.len(), 6); // 2 HTLC outputs + 1 to_self output + 1 to_remote output + 2 anchor outputs
assert!(nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events().is_empty());
let events = nodes[1].chain_monitor.chain_monitor.get_and_clear_pending_events();
assert_eq!(events.len(), 2);
- let anchor_tx = {
- let secret_key = SecretKey::from_slice(&[1; 32]).unwrap();
- let public_key = PublicKey::new(secret_key.public_key(&secp));
- let fee_utxo_script = Script::new_v0_p2wpkh(&public_key.wpubkey_hash().unwrap());
+ let mut anchor_txs = Vec::with_capacity(events.len());
+ for (idx, event) in events.into_iter().enumerate() {
+ let utxo_value = Amount::ONE_BTC.to_sat() * (idx + 1) as u64;
let coinbase_tx = Transaction {
version: 2,
lock_time: PackedLockTime::ZERO,
input: vec![TxIn { ..Default::default() }],
output: vec![TxOut { // UTXO to attach fees to `anchor_tx`
- value: Amount::ONE_BTC.to_sat(),
- script_pubkey: fee_utxo_script.clone(),
- }],
- };
- let mut anchor_tx = Transaction {
- version: 2,
- lock_time: PackedLockTime::ZERO,
- input: vec![
- TxIn { // Fee input
- previous_output: bitcoin::OutPoint { txid: coinbase_tx.txid(), vout: 0 },
- ..Default::default()
- },
- ],
- output: vec![TxOut { // Fee input change
- value: coinbase_tx.output[0].value / 2 ,
- script_pubkey: Script::new_op_return(&[]),
+ value: utxo_value,
+ script_pubkey: nodes[1].wallet_source.get_change_script().unwrap(),
}],
};
- let mut signers = Vec::with_capacity(2);
- for event in events {
- match event {
- Event::BumpTransaction(BumpTransactionEvent::ChannelClose { anchor_descriptor, .. }) => {
- anchor_tx.input.push(TxIn {
- previous_output: anchor_descriptor.outpoint,
- ..Default::default()
- });
- let signer = nodes[1].keys_manager.derive_channel_keys(
- anchor_descriptor.channel_value_satoshis, &anchor_descriptor.channel_keys_id,
- );
- signers.push(signer);
- },
- _ => panic!("Unexpected event"),
- }
- }
- for (i, signer) in signers.into_iter().enumerate() {
- let anchor_idx = i + 1;
- let funding_sig = signer.sign_holder_anchor_input(&mut anchor_tx, anchor_idx, &secp).unwrap();
- anchor_tx.input[anchor_idx].witness = chan_utils::build_anchor_input_witness(
- &signer.pubkeys().funding_pubkey, &funding_sig
- );
- }
- let fee_utxo_sig = {
- let witness_script = Script::new_p2pkh(&public_key.pubkey_hash());
- let sighash = hash_to_message!(&SighashCache::new(&anchor_tx).segwit_signature_hash(
- 0, &witness_script, coinbase_tx.output[0].value, EcdsaSighashType::All
- ).unwrap()[..]);
- let sig = sign(&secp, &sighash, &secret_key);
- let mut sig = sig.serialize_der().to_vec();
- sig.push(EcdsaSighashType::All as u8);
- sig
+ nodes[1].wallet_source.add_utxo(bitcoin::OutPoint { txid: coinbase_tx.txid(), vout: 0 }, utxo_value);
+ match event {
+ Event::BumpTransaction(event) => nodes[1].bump_tx_handler.handle_event(&event),
+ _ => panic!("Unexpected event"),
};
- anchor_tx.input[0].witness = Witness::from_vec(vec![fee_utxo_sig, public_key.to_bytes()]);
- check_spends!(anchor_tx, coinbase_tx, revoked_commitment_a, revoked_commitment_b);
- anchor_tx
+ let txn = nodes[1].tx_broadcaster.txn_broadcast();
+ assert_eq!(txn.len(), 2);
+ let (commitment_tx, anchor_tx) = (&txn[0], &txn[1]);
+ check_spends!(anchor_tx, coinbase_tx, commitment_tx);
+ anchor_txs.push(anchor_tx.clone());
};
for node in &nodes {
- mine_transactions(node, &[&revoked_commitment_a, &revoked_commitment_b, &anchor_tx]);
+ mine_transactions(node, &[&revoked_commitment_a, &anchor_txs[0], &revoked_commitment_b, &anchor_txs[1]]);
}
check_added_monitors!(&nodes[0], 2);
check_closed_broadcast(&nodes[0], 2, true);
- check_closed_event!(&nodes[0], 2, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(&nodes[0], 2, ClosureReason::CommitmentTxConfirmed, [nodes[1].node.get_our_node_id(); 2], 1000000);
// Alice should detect the confirmed revoked commitments, and attempt to claim all of the
// revoked outputs.
};
let mut descriptors = Vec::with_capacity(4);
for event in events {
+ // We don't use the `BumpTransactionEventHandler` here because it does not support
+ // creating one transaction from multiple `HTLCResolution` events.
if let Event::BumpTransaction(BumpTransactionEvent::HTLCResolution { mut htlc_descriptors, tx_lock_time, .. }) = event {
assert_eq!(htlc_descriptors.len(), 2);
for htlc_descriptor in &htlc_descriptors {
assert!(!htlc_descriptor.htlc.offered);
- let signer = nodes[1].keys_manager.derive_channel_keys(
- htlc_descriptor.channel_value_satoshis, &htlc_descriptor.channel_keys_id
- );
- let per_commitment_point = signer.get_per_commitment_point(htlc_descriptor.per_commitment_number, &secp);
htlc_tx.input.push(htlc_descriptor.unsigned_tx_input());
- htlc_tx.output.push(htlc_descriptor.tx_output(&per_commitment_point, &secp));
+ htlc_tx.output.push(htlc_descriptor.tx_output(&secp));
}
descriptors.append(&mut htlc_descriptors);
htlc_tx.lock_time = tx_lock_time;
}
for (idx, htlc_descriptor) in descriptors.into_iter().enumerate() {
let htlc_input_idx = idx + 1;
- let signer = nodes[1].keys_manager.derive_channel_keys(
- htlc_descriptor.channel_value_satoshis, &htlc_descriptor.channel_keys_id
- );
+ let signer = htlc_descriptor.derive_channel_signer(&nodes[1].keys_manager);
let our_sig = signer.sign_holder_htlc_transaction(&htlc_tx, htlc_input_idx, &htlc_descriptor, &secp).unwrap();
- let per_commitment_point = signer.get_per_commitment_point(htlc_descriptor.per_commitment_number, &secp);
- let witness_script = htlc_descriptor.witness_script(&per_commitment_point, &secp);
+ let witness_script = htlc_descriptor.witness_script(&secp);
htlc_tx.input[htlc_input_idx].witness = htlc_descriptor.tx_input_witness(&our_sig, &witness_script);
}
let fee_utxo_sig = {
// Alice should see that Bob is trying to claim to HTLCs, so she should now try to claim them at
// the second level instead.
- let revoked_claims = {
+ let revoked_claim_transactions = {
let txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
assert_eq!(txn.len(), 2);
check_spends!(revoked_htlc_claim, htlc_tx);
}
- txn
+ let mut revoked_claim_transaction_map = HashMap::new();
+ for current_tx in txn.into_iter() {
+ revoked_claim_transaction_map.insert(current_tx.txid(), current_tx);
+ }
+ revoked_claim_transaction_map
};
for node in &nodes {
- mine_transactions(node, &revoked_claims.iter().collect::<Vec<_>>());
+ mine_transactions(node, &revoked_claim_transactions.values().collect::<Vec<_>>());
}
assert!(nodes[1].chain_monitor.chain_monitor.get_and_clear_pending_events().is_empty());
let spendable_output_events = nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events();
assert_eq!(spendable_output_events.len(), 2);
- for (idx, event) in spendable_output_events.iter().enumerate() {
- if let Event::SpendableOutputs { outputs } = event {
+ for event in spendable_output_events.iter() {
+ if let Event::SpendableOutputs { outputs, channel_id } = event {
assert_eq!(outputs.len(), 1);
+ assert!(vec![chan_b.2, chan_a.2].contains(&channel_id.unwrap()));
let spend_tx = nodes[0].keys_manager.backing.spend_spendable_outputs(
&[&outputs[0]], Vec::new(), Script::new_op_return(&[]), 253, None, &Secp256k1::new(),
).unwrap();
- check_spends!(spend_tx, revoked_claims[idx]);
+
+ check_spends!(spend_tx, revoked_claim_transactions.get(&spend_tx.input[0].previous_output.txid).unwrap());
} else {
panic!("unexpected event");
}
use bitcoin::blockdata::script::Script;
use bitcoin::hash_types::{Txid, BlockHash};
+use crate::blinded_path::payment::ReceiveTlvs;
+use crate::ln::{ChannelId, PaymentPreimage, PaymentHash, PaymentSecret};
use crate::ln::features::{ChannelFeatures, ChannelTypeFeatures, InitFeatures, NodeFeatures};
use crate::ln::onion_utils;
use crate::onion_message;
+use crate::sign::{NodeSigner, Recipient};
use crate::prelude::*;
+use core::convert::TryFrom;
use core::fmt;
use core::fmt::Debug;
-use crate::io::{self, Read};
+use core::ops::Deref;
+use core::str::FromStr;
+use crate::io::{self, Cursor, Read};
use crate::io_extras::read_to_end;
use crate::events::{MessageSendEventsProvider, OnionMessageProvider};
+use crate::util::chacha20poly1305rfc::ChaChaPolyReadAdapter;
use crate::util::logger;
-use crate::util::ser::{LengthReadable, Readable, ReadableArgs, Writeable, Writer, WithoutLength, FixedLengthReader, HighZeroBytesDroppedBigSize, Hostname, TransactionU16LenLimited};
-
-use crate::ln::{PaymentPreimage, PaymentHash, PaymentSecret};
+use crate::util::ser::{LengthReadable, LengthReadableArgs, Readable, ReadableArgs, Writeable, Writer, WithoutLength, FixedLengthReader, HighZeroBytesDroppedBigSize, Hostname, TransactionU16LenLimited, BigSize};
+use crate::util::base32;
use crate::routing::gossip::{NodeAlias, NodeId};
/// message. A node can decide to use that information to discover a potential update to its
/// public IPv4 address (NAT) and use that for a [`NodeAnnouncement`] update message containing
/// the new address.
- pub remote_network_address: Option<NetAddress>,
+ pub remote_network_address: Option<SocketAddress>,
}
/// An [`error`] message to be sent to or received from a peer.
///
/// All-0s indicates a general error unrelated to a specific channel, after which all channels
/// with the sending peer should be closed.
- pub channel_id: [u8; 32],
+ pub channel_id: ChannelId,
/// A possibly human-readable error description.
///
/// The string should be sanitized before it is used (e.g., emitted to logs or printed to
/// The channel ID involved in the warning.
///
/// All-0s indicates a warning unrelated to a specific channel.
- pub channel_id: [u8; 32],
+ pub channel_id: ChannelId,
/// A possibly human-readable warning description.
///
/// The string should be sanitized before it is used (e.g. emitted to logs or printed to
/// The genesis hash of the blockchain where the channel is to be opened
pub chain_hash: BlockHash,
/// A temporary channel ID, until the funding outpoint is announced
- pub temporary_channel_id: [u8; 32],
+ pub temporary_channel_id: ChannelId,
/// The channel value
pub funding_satoshis: u64,
/// The amount to push to the counterparty as part of the open, in milli-satoshi
/// The genesis hash of the blockchain where the channel is to be opened
pub chain_hash: BlockHash,
/// A temporary channel ID derived using a zeroed out value for the channel acceptor's revocation basepoint
- pub temporary_channel_id: [u8; 32],
+ pub temporary_channel_id: ChannelId,
/// The feerate for the funding transaction set by the channel initiator
pub funding_feerate_sat_per_1000_weight: u32,
/// The feerate for the commitment transaction set by the channel initiator
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AcceptChannel {
/// A temporary channel ID, until the funding outpoint is announced
- pub temporary_channel_id: [u8; 32],
+ pub temporary_channel_id: ChannelId,
/// The threshold below which outputs on transactions broadcast by sender will be omitted
pub dust_limit_satoshis: u64,
/// The maximum inbound HTLC value in flight towards sender, in milli-satoshi
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AcceptChannelV2 {
/// The same `temporary_channel_id` received from the initiator's `open_channel2` message.
- pub temporary_channel_id: [u8; 32],
+ pub temporary_channel_id: ChannelId,
/// Part of the channel value contributed by the channel acceptor
pub funding_satoshis: u64,
/// The threshold below which outputs on transactions broadcast by the channel acceptor will be
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FundingCreated {
/// A temporary channel ID, until the funding is established
- pub temporary_channel_id: [u8; 32],
+ pub temporary_channel_id: ChannelId,
/// The funding transaction ID
pub funding_txid: Txid,
/// The specific output index funding this channel
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FundingSigned {
/// The channel ID
- pub channel_id: [u8; 32],
+ pub channel_id: ChannelId,
/// The signature of the channel acceptor (fundee) on the initial commitment transaction
pub signature: Signature,
#[cfg(taproot)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ChannelReady {
/// The channel ID
- pub channel_id: [u8; 32],
+ pub channel_id: ChannelId,
/// The per-commitment point of the second commitment transaction
pub next_per_commitment_point: PublicKey,
/// If set, provides a `short_channel_id` alias for this channel.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TxAddInput {
/// The channel ID
- pub channel_id: [u8; 32],
+ pub channel_id: ChannelId,
/// A randomly chosen unique identifier for this input, which is even for initiators and odd for
/// non-initiators.
pub serial_id: u64,
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TxAddOutput {
/// The channel ID
- pub channel_id: [u8; 32],
+ pub channel_id: ChannelId,
/// A randomly chosen unique identifier for this output, which is even for initiators and odd for
/// non-initiators.
pub serial_id: u64,
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TxRemoveInput {
/// The channel ID
- pub channel_id: [u8; 32],
+ pub channel_id: ChannelId,
/// The serial ID of the input to be removed
pub serial_id: u64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TxRemoveOutput {
/// The channel ID
- pub channel_id: [u8; 32],
+ pub channel_id: ChannelId,
/// The serial ID of the output to be removed
pub serial_id: u64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TxComplete {
/// The channel ID
- pub channel_id: [u8; 32],
+ pub channel_id: ChannelId,
}
/// A tx_signatures message containing the sender's signatures for a transaction constructed with
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TxSignatures {
/// The channel ID
- pub channel_id: [u8; 32],
+ pub channel_id: ChannelId,
/// The TXID
pub tx_hash: Txid,
/// The list of witnesses
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TxInitRbf {
/// The channel ID
- pub channel_id: [u8; 32],
+ pub channel_id: ChannelId,
/// The locktime of the transaction
pub locktime: u32,
/// The feerate of the transaction
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TxAckRbf {
/// The channel ID
- pub channel_id: [u8; 32],
+ pub channel_id: ChannelId,
/// The number of satoshis the sender will contribute to or, if negative, remove from
/// (e.g. splice-out) the funding output of the transaction
pub funding_output_contribution: Option<i64>,
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TxAbort {
/// The channel ID
- pub channel_id: [u8; 32],
+ pub channel_id: ChannelId,
/// Message data
pub data: Vec<u8>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Shutdown {
/// The channel ID
- pub channel_id: [u8; 32],
+ pub channel_id: ChannelId,
/// The destination of this peer's funds on closing.
///
/// Must be in one of these forms: P2PKH, P2SH, P2WPKH, P2WSH, P2TR.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ClosingSigned {
/// The channel ID
- pub channel_id: [u8; 32],
+ pub channel_id: ChannelId,
/// The proposed total fee for the closing transaction
pub fee_satoshis: u64,
/// A signature on the closing transaction
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct UpdateAddHTLC {
/// The channel ID
- pub channel_id: [u8; 32],
+ pub channel_id: ChannelId,
/// The HTLC ID
pub htlc_id: u64,
/// The HTLC value in milli-satoshi
pub struct OnionMessage {
/// Used in decrypting the onion packet's payload.
pub blinding_point: PublicKey,
- pub(crate) onion_routing_packet: onion_message::Packet,
+ /// The full onion packet including hop data, pubkey, and hmac
+ pub onion_routing_packet: onion_message::Packet,
}
/// An [`update_fulfill_htlc`] message to be sent to or received from a peer.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct UpdateFulfillHTLC {
/// The channel ID
- pub channel_id: [u8; 32],
+ pub channel_id: ChannelId,
/// The HTLC ID
pub htlc_id: u64,
/// The pre-image of the payment hash, allowing HTLC redemption
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct UpdateFailHTLC {
/// The channel ID
- pub channel_id: [u8; 32],
+ pub channel_id: ChannelId,
/// The HTLC ID
pub htlc_id: u64,
pub(crate) reason: OnionErrorPacket,
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct UpdateFailMalformedHTLC {
/// The channel ID
- pub channel_id: [u8; 32],
+ pub channel_id: ChannelId,
/// The HTLC ID
pub htlc_id: u64,
pub(crate) sha256_of_onion: [u8; 32],
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CommitmentSigned {
/// The channel ID
- pub channel_id: [u8; 32],
+ pub channel_id: ChannelId,
/// A signature on the commitment transaction
pub signature: Signature,
/// Signatures on the HTLC transactions
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RevokeAndACK {
/// The channel ID
- pub channel_id: [u8; 32],
+ pub channel_id: ChannelId,
/// The secret corresponding to the per-commitment point
pub per_commitment_secret: [u8; 32],
/// The next sender-broadcast commitment transaction's per-commitment point
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct UpdateFee {
/// The channel ID
- pub channel_id: [u8; 32],
+ pub channel_id: ChannelId,
/// Fee rate per 1000-weight of the transaction
pub feerate_per_kw: u32,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ChannelReestablish {
/// The channel ID
- pub channel_id: [u8; 32],
+ pub channel_id: ChannelId,
/// The next commitment number for the sender
pub next_local_commitment_number: u64,
/// The next commitment number for the recipient
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AnnouncementSignatures {
/// The channel ID
- pub channel_id: [u8; 32],
+ pub channel_id: ChannelId,
/// The short channel ID
pub short_channel_id: u64,
/// A signature by the node key
/// An address which can be used to connect to a remote peer.
#[derive(Clone, Debug, PartialEq, Eq)]
-pub enum NetAddress {
- /// An IPv4 address/port on which the peer is listening.
- IPv4 {
+pub enum SocketAddress {
+ /// An IPv4 address and port on which the peer is listening.
+ TcpIpV4 {
/// The 4-byte IPv4 address
addr: [u8; 4],
/// The port on which the node is listening
port: u16,
},
- /// An IPv6 address/port on which the peer is listening.
- IPv6 {
+ /// An IPv6 address and port on which the peer is listening.
+ TcpIpV6 {
/// The 16-byte IPv6 address
addr: [u8; 16],
/// The port on which the node is listening
port: u16,
},
}
-impl NetAddress {
+impl SocketAddress {
/// Gets the ID of this address type. Addresses in [`NodeAnnouncement`] messages should be sorted
/// by this.
pub(crate) fn get_id(&self) -> u8 {
match self {
- &NetAddress::IPv4 {..} => { 1 },
- &NetAddress::IPv6 {..} => { 2 },
- &NetAddress::OnionV2(_) => { 3 },
- &NetAddress::OnionV3 {..} => { 4 },
- &NetAddress::Hostname {..} => { 5 },
+ &SocketAddress::TcpIpV4 {..} => { 1 },
+ &SocketAddress::TcpIpV6 {..} => { 2 },
+ &SocketAddress::OnionV2(_) => { 3 },
+ &SocketAddress::OnionV3 {..} => { 4 },
+ &SocketAddress::Hostname {..} => { 5 },
}
}
/// Strict byte-length of address descriptor, 1-byte type not recorded
fn len(&self) -> u16 {
match self {
- &NetAddress::IPv4 { .. } => { 6 },
- &NetAddress::IPv6 { .. } => { 18 },
- &NetAddress::OnionV2(_) => { 12 },
- &NetAddress::OnionV3 { .. } => { 37 },
+ &SocketAddress::TcpIpV4 { .. } => { 6 },
+ &SocketAddress::TcpIpV6 { .. } => { 18 },
+ &SocketAddress::OnionV2(_) => { 12 },
+ &SocketAddress::OnionV3 { .. } => { 37 },
// Consists of 1-byte hostname length, hostname bytes, and 2-byte port.
- &NetAddress::Hostname { ref hostname, .. } => { u16::from(hostname.len()) + 3 },
+ &SocketAddress::Hostname { ref hostname, .. } => { u16::from(hostname.len()) + 3 },
}
}
pub(crate) const MAX_LEN: u16 = 258;
}
-impl Writeable for NetAddress {
+impl Writeable for SocketAddress {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
match self {
- &NetAddress::IPv4 { ref addr, ref port } => {
+ &SocketAddress::TcpIpV4 { ref addr, ref port } => {
1u8.write(writer)?;
addr.write(writer)?;
port.write(writer)?;
},
- &NetAddress::IPv6 { ref addr, ref port } => {
+ &SocketAddress::TcpIpV6 { ref addr, ref port } => {
2u8.write(writer)?;
addr.write(writer)?;
port.write(writer)?;
},
- &NetAddress::OnionV2(bytes) => {
+ &SocketAddress::OnionV2(bytes) => {
3u8.write(writer)?;
bytes.write(writer)?;
},
- &NetAddress::OnionV3 { ref ed25519_pubkey, ref checksum, ref version, ref port } => {
+ &SocketAddress::OnionV3 { ref ed25519_pubkey, ref checksum, ref version, ref port } => {
4u8.write(writer)?;
ed25519_pubkey.write(writer)?;
checksum.write(writer)?;
version.write(writer)?;
port.write(writer)?;
},
- &NetAddress::Hostname { ref hostname, ref port } => {
+ &SocketAddress::Hostname { ref hostname, ref port } => {
5u8.write(writer)?;
hostname.write(writer)?;
port.write(writer)?;
}
}
-impl Readable for Result<NetAddress, u8> {
- fn read<R: Read>(reader: &mut R) -> Result<Result<NetAddress, u8>, DecodeError> {
+impl Readable for Result<SocketAddress, u8> {
+ fn read<R: Read>(reader: &mut R) -> Result<Result<SocketAddress, u8>, DecodeError> {
let byte = <u8 as Readable>::read(reader)?;
match byte {
1 => {
- Ok(Ok(NetAddress::IPv4 {
+ Ok(Ok(SocketAddress::TcpIpV4 {
addr: Readable::read(reader)?,
port: Readable::read(reader)?,
}))
},
2 => {
- Ok(Ok(NetAddress::IPv6 {
+ Ok(Ok(SocketAddress::TcpIpV6 {
addr: Readable::read(reader)?,
port: Readable::read(reader)?,
}))
},
- 3 => Ok(Ok(NetAddress::OnionV2(Readable::read(reader)?))),
+ 3 => Ok(Ok(SocketAddress::OnionV2(Readable::read(reader)?))),
4 => {
- Ok(Ok(NetAddress::OnionV3 {
+ Ok(Ok(SocketAddress::OnionV3 {
ed25519_pubkey: Readable::read(reader)?,
checksum: Readable::read(reader)?,
version: Readable::read(reader)?,
}))
},
5 => {
- Ok(Ok(NetAddress::Hostname {
+ Ok(Ok(SocketAddress::Hostname {
hostname: Readable::read(reader)?,
port: Readable::read(reader)?,
}))
}
}
-impl Readable for NetAddress {
- fn read<R: Read>(reader: &mut R) -> Result<NetAddress, DecodeError> {
+impl Readable for SocketAddress {
+ fn read<R: Read>(reader: &mut R) -> Result<SocketAddress, DecodeError> {
match Readable::read(reader) {
Ok(Ok(res)) => Ok(res),
Ok(Err(_)) => Err(DecodeError::UnknownVersion),
}
}
+/// [`SocketAddress`] error variants
+#[derive(Debug, Eq, PartialEq, Clone)]
+pub enum SocketAddressParseError {
+ /// Socket address (IPv4/IPv6) parsing error
+ SocketAddrParse,
+ /// Invalid input format
+ InvalidInput,
+ /// Invalid port
+ InvalidPort,
+ /// Invalid onion v3 address
+ InvalidOnionV3,
+}
+
+impl fmt::Display for SocketAddressParseError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ SocketAddressParseError::SocketAddrParse => write!(f, "Socket address (IPv4/IPv6) parsing error"),
+ SocketAddressParseError::InvalidInput => write!(f, "Invalid input format. \
+ Expected: \"<ipv4>:<port>\", \"[<ipv6>]:<port>\", \"<onion address>.onion:<port>\" or \"<hostname>:<port>\""),
+ SocketAddressParseError::InvalidPort => write!(f, "Invalid port"),
+ SocketAddressParseError::InvalidOnionV3 => write!(f, "Invalid onion v3 address"),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl From<std::net::SocketAddrV4> for SocketAddress {
+ fn from(addr: std::net::SocketAddrV4) -> Self {
+ SocketAddress::TcpIpV4 { addr: addr.ip().octets(), port: addr.port() }
+ }
+}
+
+#[cfg(feature = "std")]
+impl From<std::net::SocketAddrV6> for SocketAddress {
+ fn from(addr: std::net::SocketAddrV6) -> Self {
+ SocketAddress::TcpIpV6 { addr: addr.ip().octets(), port: addr.port() }
+ }
+}
+
+#[cfg(feature = "std")]
+impl From<std::net::SocketAddr> for SocketAddress {
+ fn from(addr: std::net::SocketAddr) -> Self {
+ match addr {
+ std::net::SocketAddr::V4(addr) => addr.into(),
+ std::net::SocketAddr::V6(addr) => addr.into(),
+ }
+ }
+}
+
+fn parse_onion_address(host: &str, port: u16) -> Result<SocketAddress, SocketAddressParseError> {
+ if host.ends_with(".onion") {
+ let domain = &host[..host.len() - ".onion".len()];
+ if domain.len() != 56 {
+ return Err(SocketAddressParseError::InvalidOnionV3);
+ }
+ let onion = base32::Alphabet::RFC4648 { padding: false }.decode(&domain).map_err(|_| SocketAddressParseError::InvalidOnionV3)?;
+ if onion.len() != 35 {
+ return Err(SocketAddressParseError::InvalidOnionV3);
+ }
+ let version = onion[0];
+ let first_checksum_flag = onion[1];
+ let second_checksum_flag = onion[2];
+ let mut ed25519_pubkey = [0; 32];
+ ed25519_pubkey.copy_from_slice(&onion[3..35]);
+ let checksum = u16::from_be_bytes([first_checksum_flag, second_checksum_flag]);
+ return Ok(SocketAddress::OnionV3 { ed25519_pubkey, checksum, version, port });
+
+ } else {
+ return Err(SocketAddressParseError::InvalidInput);
+ }
+}
+
+#[cfg(feature = "std")]
+impl FromStr for SocketAddress {
+ type Err = SocketAddressParseError;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ match std::net::SocketAddr::from_str(s) {
+ Ok(addr) => Ok(addr.into()),
+ Err(_) => {
+ let trimmed_input = match s.rfind(":") {
+ Some(pos) => pos,
+ None => return Err(SocketAddressParseError::InvalidInput),
+ };
+ let host = &s[..trimmed_input];
+ let port: u16 = s[trimmed_input + 1..].parse().map_err(|_| SocketAddressParseError::InvalidPort)?;
+ if host.ends_with(".onion") {
+ return parse_onion_address(host, port);
+ };
+ if let Ok(hostname) = Hostname::try_from(s[..trimmed_input].to_string()) {
+ return Ok(SocketAddress::Hostname { hostname, port });
+ };
+ return Err(SocketAddressParseError::SocketAddrParse)
+ },
+ }
+ }
+}
+
/// Represents the set of gossip messages that require a signature from a node's identity key.
pub enum UnsignedGossipMessage<'a> {
/// An unsigned channel announcement.
/// This should be sanitized before use. There is no guarantee of uniqueness.
pub alias: NodeAlias,
/// List of addresses on which this node is reachable
- pub addresses: Vec<NetAddress>,
+ pub addresses: Vec<SocketAddress>,
pub(crate) excess_address_data: Vec<u8>,
pub(crate) excess_data: Vec<u8>,
}
pub bitcoin_key_1: NodeId,
/// The funding key for the second node
pub bitcoin_key_2: NodeId,
- pub(crate) excess_data: Vec<u8>,
+ /// Excess data which was signed as a part of the message which we do not (yet) understand how
+ /// to decode.
+ ///
+ /// This is stored to ensure forward-compatibility as new fields are added to the lightning gossip protocol.
+ pub excess_data: Vec<u8>,
}
/// A [`channel_announcement`] message to be sent to or received from a peer.
///
}
/// Used to put an error message in a [`LightningError`].
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, PartialEq)]
pub enum ErrorAction {
/// The peer took some action which made us think they were useless. Disconnect them.
DisconnectPeer {
/// Handle an incoming `channel_ready` message from the given peer.
fn handle_channel_ready(&self, their_node_id: &PublicKey, msg: &ChannelReady);
- // Channl close:
+ // Channel close:
/// Handle an incoming `shutdown` message from the given peer.
fn handle_shutdown(&self, their_node_id: &PublicKey, msg: &Shutdown);
/// Handle an incoming `closing_signed` message from the given peer.
}
mod fuzzy_internal_msgs {
+ use bitcoin::secp256k1::PublicKey;
+ use crate::blinded_path::payment::PaymentConstraints;
use crate::prelude::*;
use crate::ln::{PaymentPreimage, PaymentSecret};
// These types aren't intended to be pub, but are exposed for direct fuzzing (as we deserialize
// them from untrusted input):
#[derive(Clone)]
- pub(crate) struct FinalOnionHopData {
- pub(crate) payment_secret: PaymentSecret,
+ pub struct FinalOnionHopData {
+ pub payment_secret: PaymentSecret,
/// The total value, in msat, of the payment as received by the ultimate recipient.
/// Message serialization may panic if this value is more than 21 million Bitcoin.
- pub(crate) total_msat: u64,
+ pub total_msat: u64,
}
- pub(crate) enum OnionHopDataFormat {
- NonFinalNode {
+ pub enum InboundOnionPayload {
+ Forward {
short_channel_id: u64,
+ /// The value, in msat, of the payment after this hop's fee is deducted.
+ amt_to_forward: u64,
+ outgoing_cltv_value: u32,
},
- FinalNode {
+ Receive {
payment_data: Option<FinalOnionHopData>,
payment_metadata: Option<Vec<u8>>,
keysend_preimage: Option<PaymentPreimage>,
+ custom_tlvs: Vec<(u64, Vec<u8>)>,
+ amt_msat: u64,
+ outgoing_cltv_value: u32,
},
+ BlindedReceive {
+ amt_msat: u64,
+ total_msat: u64,
+ outgoing_cltv_value: u32,
+ payment_secret: PaymentSecret,
+ payment_constraints: PaymentConstraints,
+ intro_node_blinding_point: PublicKey,
+ }
}
- pub struct OnionHopData {
- pub(crate) format: OnionHopDataFormat,
- /// The value, in msat, of the payment after this hop's fee is deducted.
- /// Message serialization may panic if this value is more than 21 million Bitcoin.
- pub(crate) amt_to_forward: u64,
- pub(crate) outgoing_cltv_value: u32,
+ pub(crate) enum OutboundOnionPayload {
+ Forward {
+ short_channel_id: u64,
+ /// The value, in msat, of the payment after this hop's fee is deducted.
+ amt_to_forward: u64,
+ outgoing_cltv_value: u32,
+ },
+ Receive {
+ payment_data: Option<FinalOnionHopData>,
+ payment_metadata: Option<Vec<u8>>,
+ keysend_preimage: Option<PaymentPreimage>,
+ custom_tlvs: Vec<(u64, Vec<u8>)>,
+ amt_msat: u64,
+ outgoing_cltv_value: u32,
+ },
+ BlindedForward {
+ encrypted_tlvs: Vec<u8>,
+ intro_node_blinding_point: Option<PublicKey>,
+ },
+ BlindedReceive {
+ amt_msat: u64,
+ total_msat: u64,
+ outgoing_cltv_value: u32,
+ encrypted_tlvs: Vec<u8>,
+ intro_node_blinding_point: Option<PublicKey>, // Set if the introduction node of the blinded path is the final node
+ }
}
pub struct DecodedOnionErrorPacket {
fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
let global_features: InitFeatures = Readable::read(r)?;
let features: InitFeatures = Readable::read(r)?;
- let mut remote_network_address: Option<NetAddress> = None;
+ let mut remote_network_address: Option<SocketAddress> = None;
let mut networks: Option<WithoutLength<Vec<ChainHash>>> = None;
decode_tlv_stream!(r, {
(1, networks, option),
}
}
-impl Writeable for OnionHopData {
+impl Writeable for OutboundOnionPayload {
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
- match self.format {
- OnionHopDataFormat::NonFinalNode { short_channel_id } => {
+ match self {
+ Self::Forward { short_channel_id, amt_to_forward, outgoing_cltv_value } => {
_encode_varint_length_prefixed_tlv!(w, {
- (2, HighZeroBytesDroppedBigSize(self.amt_to_forward), required),
- (4, HighZeroBytesDroppedBigSize(self.outgoing_cltv_value), required),
+ (2, HighZeroBytesDroppedBigSize(*amt_to_forward), required),
+ (4, HighZeroBytesDroppedBigSize(*outgoing_cltv_value), required),
(6, short_channel_id, required)
});
},
- OnionHopDataFormat::FinalNode { ref payment_data, ref payment_metadata, ref keysend_preimage } => {
+ Self::Receive {
+ ref payment_data, ref payment_metadata, ref keysend_preimage, amt_msat,
+ outgoing_cltv_value, ref custom_tlvs,
+ } => {
+ // We need to update [`ln::outbound_payment::RecipientOnionFields::with_custom_tlvs`]
+ // to reject any reserved types in the experimental range if new ones are ever
+ // standardized.
+ let keysend_tlv = keysend_preimage.map(|preimage| (5482373484, preimage.encode()));
+ let mut custom_tlvs: Vec<&(u64, Vec<u8>)> = custom_tlvs.iter().chain(keysend_tlv.iter()).collect();
+ custom_tlvs.sort_unstable_by_key(|(typ, _)| *typ);
_encode_varint_length_prefixed_tlv!(w, {
- (2, HighZeroBytesDroppedBigSize(self.amt_to_forward), required),
- (4, HighZeroBytesDroppedBigSize(self.outgoing_cltv_value), required),
+ (2, HighZeroBytesDroppedBigSize(*amt_msat), required),
+ (4, HighZeroBytesDroppedBigSize(*outgoing_cltv_value), required),
(8, payment_data, option),
- (16, payment_metadata.as_ref().map(|m| WithoutLength(m)), option),
- (5482373484, keysend_preimage, option)
+ (16, payment_metadata.as_ref().map(|m| WithoutLength(m)), option)
+ }, custom_tlvs.iter());
+ },
+ Self::BlindedForward { encrypted_tlvs, intro_node_blinding_point } => {
+ _encode_varint_length_prefixed_tlv!(w, {
+ (10, *encrypted_tlvs, required_vec),
+ (12, intro_node_blinding_point, option)
+ });
+ },
+ Self::BlindedReceive {
+ amt_msat, total_msat, outgoing_cltv_value, encrypted_tlvs,
+ intro_node_blinding_point,
+ } => {
+ _encode_varint_length_prefixed_tlv!(w, {
+ (2, HighZeroBytesDroppedBigSize(*amt_msat), required),
+ (4, HighZeroBytesDroppedBigSize(*outgoing_cltv_value), required),
+ (10, *encrypted_tlvs, required_vec),
+ (12, intro_node_blinding_point, option),
+ (18, HighZeroBytesDroppedBigSize(*total_msat), required)
});
},
}
}
}
-impl Readable for OnionHopData {
- fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
- let mut amt = HighZeroBytesDroppedBigSize(0u64);
- let mut cltv_value = HighZeroBytesDroppedBigSize(0u32);
+impl<NS: Deref> ReadableArgs<&NS> for InboundOnionPayload where NS::Target: NodeSigner {
+ fn read<R: Read>(r: &mut R, node_signer: &NS) -> Result<Self, DecodeError> {
+ let mut amt = None;
+ let mut cltv_value = None;
let mut short_id: Option<u64> = None;
let mut payment_data: Option<FinalOnionHopData> = None;
+ let mut encrypted_tlvs_opt: Option<WithoutLength<Vec<u8>>> = None;
+ let mut intro_node_blinding_point = None;
let mut payment_metadata: Option<WithoutLength<Vec<u8>>> = None;
+ let mut total_msat = None;
let mut keysend_preimage: Option<PaymentPreimage> = None;
- read_tlv_fields!(r, {
- (2, amt, required),
- (4, cltv_value, required),
+ let mut custom_tlvs = Vec::new();
+
+ let tlv_len = BigSize::read(r)?;
+ let rd = FixedLengthReader::new(r, tlv_len.0);
+ decode_tlv_stream_with_custom_tlv_decode!(rd, {
+ (2, amt, (option, encoding: (u64, HighZeroBytesDroppedBigSize))),
+ (4, cltv_value, (option, encoding: (u32, HighZeroBytesDroppedBigSize))),
(6, short_id, option),
(8, payment_data, option),
+ (10, encrypted_tlvs_opt, option),
+ (12, intro_node_blinding_point, option),
(16, payment_metadata, option),
+ (18, total_msat, (option, encoding: (u64, HighZeroBytesDroppedBigSize))),
// See https://github.com/lightning/blips/blob/master/blip-0003.md
(5482373484, keysend_preimage, option)
+ }, |msg_type: u64, msg_reader: &mut FixedLengthReader<_>| -> Result<bool, DecodeError> {
+ if msg_type < 1 << 16 { return Ok(false) }
+ let mut value = Vec::new();
+ msg_reader.read_to_end(&mut value)?;
+ custom_tlvs.push((msg_type, value));
+ Ok(true)
});
- let format = if let Some(short_channel_id) = short_id {
- if payment_data.is_some() { return Err(DecodeError::InvalidValue); }
- if payment_metadata.is_some() { return Err(DecodeError::InvalidValue); }
- OnionHopDataFormat::NonFinalNode {
- short_channel_id,
+ if amt.unwrap_or(0) > MAX_VALUE_MSAT { return Err(DecodeError::InvalidValue) }
+
+ if let Some(blinding_point) = intro_node_blinding_point {
+ if short_id.is_some() || payment_data.is_some() || payment_metadata.is_some() {
+ return Err(DecodeError::InvalidValue)
+ }
+ let enc_tlvs = encrypted_tlvs_opt.ok_or(DecodeError::InvalidValue)?.0;
+ let enc_tlvs_ss = node_signer.ecdh(Recipient::Node, &blinding_point, None)
+ .map_err(|_| DecodeError::InvalidValue)?;
+ let rho = onion_utils::gen_rho_from_shared_secret(&enc_tlvs_ss.secret_bytes());
+ let mut s = Cursor::new(&enc_tlvs);
+ let mut reader = FixedLengthReader::new(&mut s, enc_tlvs.len() as u64);
+ match ChaChaPolyReadAdapter::read(&mut reader, rho)? {
+ ChaChaPolyReadAdapter { readable: ReceiveTlvs { payment_secret, payment_constraints }} => {
+ if total_msat.unwrap_or(0) > MAX_VALUE_MSAT { return Err(DecodeError::InvalidValue) }
+ Ok(Self::BlindedReceive {
+ amt_msat: amt.ok_or(DecodeError::InvalidValue)?,
+ total_msat: total_msat.ok_or(DecodeError::InvalidValue)?,
+ outgoing_cltv_value: cltv_value.ok_or(DecodeError::InvalidValue)?,
+ payment_secret,
+ payment_constraints,
+ intro_node_blinding_point: blinding_point,
+ })
+ },
}
+ } else if let Some(short_channel_id) = short_id {
+ if payment_data.is_some() || payment_metadata.is_some() || encrypted_tlvs_opt.is_some() ||
+ total_msat.is_some()
+ { return Err(DecodeError::InvalidValue) }
+ Ok(Self::Forward {
+ short_channel_id,
+ amt_to_forward: amt.ok_or(DecodeError::InvalidValue)?,
+ outgoing_cltv_value: cltv_value.ok_or(DecodeError::InvalidValue)?,
+ })
} else {
+ if encrypted_tlvs_opt.is_some() || total_msat.is_some() {
+ return Err(DecodeError::InvalidValue)
+ }
if let Some(data) = &payment_data {
if data.total_msat > MAX_VALUE_MSAT {
return Err(DecodeError::InvalidValue);
}
}
- OnionHopDataFormat::FinalNode {
+ Ok(Self::Receive {
payment_data,
payment_metadata: payment_metadata.map(|w| w.0),
keysend_preimage,
- }
- };
-
- if amt.0 > MAX_VALUE_MSAT {
- return Err(DecodeError::InvalidValue);
+ amt_msat: amt.ok_or(DecodeError::InvalidValue)?,
+ outgoing_cltv_value: cltv_value.ok_or(DecodeError::InvalidValue)?,
+ custom_tlvs,
+ })
}
- Ok(OnionHopData {
- format,
- amt_to_forward: amt.0,
- outgoing_cltv_value: cltv_value.0,
- })
- }
-}
-
-// ReadableArgs because we need onion_utils::decode_next_hop to accommodate payment packets and
-// onion message packets.
-impl ReadableArgs<()> for OnionHopData {
- fn read<R: Read>(r: &mut R, _arg: ()) -> Result<Self, DecodeError> {
- <Self as Readable>::read(r)
}
}
let alias: NodeAlias = Readable::read(r)?;
let addr_len: u16 = Readable::read(r)?;
- let mut addresses: Vec<NetAddress> = Vec::new();
+ let mut addresses: Vec<SocketAddress> = Vec::new();
let mut addr_readpos = 0;
let mut excess = false;
let mut excess_byte = 0;
#[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 crate::ln::ChannelId;
use crate::ln::features::{ChannelFeatures, ChannelTypeFeatures, InitFeatures, NodeFeatures};
- use crate::ln::msgs::{self, FinalOnionHopData, OnionErrorPacket, OnionHopDataFormat};
+ use crate::ln::msgs::{self, FinalOnionHopData, OnionErrorPacket};
+ use crate::ln::msgs::SocketAddress;
use crate::routing::gossip::{NodeAlias, NodeId};
- use crate::util::ser::{Writeable, Readable, Hostname, TransactionU16LenLimited};
+ use crate::util::ser::{Writeable, Readable, ReadableArgs, Hostname, TransactionU16LenLimited};
+ use crate::util::test_utils;
use bitcoin::hashes::hex::FromHex;
use bitcoin::util::address::Address;
use crate::io::{self, Cursor};
use crate::prelude::*;
- use core::convert::TryFrom;
use core::str::FromStr;
-
use crate::chain::transaction::OutPoint;
+ #[cfg(feature = "std")]
+ use std::net::{Ipv4Addr, Ipv6Addr};
+ use crate::ln::msgs::SocketAddressParseError;
+
#[test]
fn encoding_channel_reestablish() {
let public_key = {
};
let cr = msgs::ChannelReestablish {
- channel_id: [4, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0],
+ channel_id: ChannelId::from_bytes([4, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0]),
next_local_commitment_number: 3,
next_remote_commitment_number: 4,
your_last_per_commitment_secret: [9;32],
};
let cr = msgs::ChannelReestablish {
- channel_id: [4, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0],
+ channel_id: ChannelId::from_bytes([4, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0]),
next_local_commitment_number: 3,
next_remote_commitment_number: 4,
your_last_per_commitment_secret: [9;32],
let sig_1 = get_sig_on!(privkey, secp_ctx, String::from("01010101010101010101010101010101"));
let sig_2 = get_sig_on!(privkey, secp_ctx, String::from("02020202020202020202020202020202"));
let announcement_signatures = msgs::AnnouncementSignatures {
- channel_id: [4, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0],
+ channel_id: ChannelId::from_bytes([4, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0]),
short_channel_id: 2316138423780173,
node_signature: sig_1,
bitcoin_signature: sig_2,
};
let mut addresses = Vec::new();
if ipv4 {
- addresses.push(msgs::NetAddress::IPv4 {
+ addresses.push(SocketAddress::TcpIpV4 {
addr: [255, 254, 253, 252],
port: 9735
});
}
if ipv6 {
- addresses.push(msgs::NetAddress::IPv6 {
+ addresses.push(SocketAddress::TcpIpV6 {
addr: [255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 245, 244, 243, 242, 241, 240],
port: 9735
});
}
if onionv2 {
- addresses.push(msgs::NetAddress::OnionV2(
+ addresses.push(msgs::SocketAddress::OnionV2(
[255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 38, 7]
));
}
if onionv3 {
- addresses.push(msgs::NetAddress::OnionV3 {
+ addresses.push(msgs::SocketAddress::OnionV3 {
ed25519_pubkey: [255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 245, 244, 243, 242, 241, 240, 239, 238, 237, 236, 235, 234, 233, 232, 231, 230, 229, 228, 227, 226, 225, 224],
checksum: 32,
version: 16,
});
}
if hostname {
- addresses.push(msgs::NetAddress::Hostname {
+ addresses.push(SocketAddress::Hostname {
hostname: Hostname::try_from(String::from("host")).unwrap(),
port: 9735,
});
let (_, pubkey_6) = get_keys_from!("0606060606060606060606060606060606060606060606060606060606060606", secp_ctx);
let open_channel = msgs::OpenChannel {
chain_hash: BlockHash::from_hex("6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000").unwrap(),
- temporary_channel_id: [2; 32],
+ temporary_channel_id: ChannelId::from_bytes([2; 32]),
funding_satoshis: 1311768467284833366,
push_msat: 2536655962884945560,
dust_limit_satoshis: 3608586615801332854,
let (_, pubkey_7) = get_keys_from!("0707070707070707070707070707070707070707070707070707070707070707", secp_ctx);
let open_channelv2 = msgs::OpenChannelV2 {
chain_hash: BlockHash::from_hex("6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000").unwrap(),
- temporary_channel_id: [2; 32],
+ temporary_channel_id: ChannelId::from_bytes([2; 32]),
funding_feerate_sat_per_1000_weight: 821716,
commitment_feerate_sat_per_1000_weight: 821716,
funding_satoshis: 1311768467284833366,
let (_, pubkey_5) = get_keys_from!("0505050505050505050505050505050505050505050505050505050505050505", secp_ctx);
let (_, pubkey_6) = get_keys_from!("0606060606060606060606060606060606060606060606060606060606060606", secp_ctx);
let accept_channel = msgs::AcceptChannel {
- temporary_channel_id: [2; 32],
+ temporary_channel_id: ChannelId::from_bytes([2; 32]),
dust_limit_satoshis: 1311768467284833366,
max_htlc_value_in_flight_msat: 2536655962884945560,
channel_reserve_satoshis: 3608586615801332854,
let (_, pubkey_6) = get_keys_from!("0606060606060606060606060606060606060606060606060606060606060606", secp_ctx);
let (_, pubkey_7) = get_keys_from!("0707070707070707070707070707070707070707070707070707070707070707", secp_ctx);
let accept_channelv2 = msgs::AcceptChannelV2 {
- temporary_channel_id: [2; 32],
+ temporary_channel_id: ChannelId::from_bytes([2; 32]),
funding_satoshis: 1311768467284833366,
dust_limit_satoshis: 1311768467284833366,
max_htlc_value_in_flight_msat: 2536655962884945560,
let (privkey_1, _) = get_keys_from!("0101010101010101010101010101010101010101010101010101010101010101", secp_ctx);
let sig_1 = get_sig_on!(privkey_1, secp_ctx, String::from("01010101010101010101010101010101"));
let funding_created = msgs::FundingCreated {
- temporary_channel_id: [2; 32],
+ temporary_channel_id: ChannelId::from_bytes([2; 32]),
funding_txid: Txid::from_hex("c2d4449afa8d26140898dd54d3390b057ba2a5afcf03ba29d7dc0d8b9ffe966e").unwrap(),
funding_output_index: 255,
signature: sig_1,
let (privkey_1, _) = get_keys_from!("0101010101010101010101010101010101010101010101010101010101010101", secp_ctx);
let sig_1 = get_sig_on!(privkey_1, secp_ctx, String::from("01010101010101010101010101010101"));
let funding_signed = msgs::FundingSigned {
- channel_id: [2; 32],
+ channel_id: ChannelId::from_bytes([2; 32]),
signature: sig_1,
#[cfg(taproot)]
partial_signature_with_nonce: None,
let secp_ctx = Secp256k1::new();
let (_, pubkey_1,) = get_keys_from!("0101010101010101010101010101010101010101010101010101010101010101", secp_ctx);
let channel_ready = msgs::ChannelReady {
- channel_id: [2; 32],
+ channel_id: ChannelId::from_bytes([2; 32]),
next_per_commitment_point: pubkey_1,
short_channel_id_alias: None,
};
#[test]
fn encoding_tx_add_input() {
let tx_add_input = msgs::TxAddInput {
- channel_id: [2; 32],
+ channel_id: ChannelId::from_bytes([2; 32]),
serial_id: 4886718345,
prevtx: TransactionU16LenLimited::new(Transaction {
version: 2,
#[test]
fn encoding_tx_add_output() {
let tx_add_output = msgs::TxAddOutput {
- channel_id: [2; 32],
+ channel_id: ChannelId::from_bytes([2; 32]),
serial_id: 4886718345,
sats: 4886718345,
script: Address::from_str("bc1qxmk834g5marzm227dgqvynd23y2nvt2ztwcw2z").unwrap().script_pubkey(),
#[test]
fn encoding_tx_remove_input() {
let tx_remove_input = msgs::TxRemoveInput {
- channel_id: [2; 32],
+ channel_id: ChannelId::from_bytes([2; 32]),
serial_id: 4886718345,
};
let encoded_value = tx_remove_input.encode();
#[test]
fn encoding_tx_remove_output() {
let tx_remove_output = msgs::TxRemoveOutput {
- channel_id: [2; 32],
+ channel_id: ChannelId::from_bytes([2; 32]),
serial_id: 4886718345,
};
let encoded_value = tx_remove_output.encode();
#[test]
fn encoding_tx_complete() {
let tx_complete = msgs::TxComplete {
- channel_id: [2; 32],
+ channel_id: ChannelId::from_bytes([2; 32]),
};
let encoded_value = tx_complete.encode();
let target_value = hex::decode("0202020202020202020202020202020202020202020202020202020202020202").unwrap();
#[test]
fn encoding_tx_signatures() {
let tx_signatures = msgs::TxSignatures {
- channel_id: [2; 32],
+ channel_id: ChannelId::from_bytes([2; 32]),
tx_hash: Txid::from_hex("c2d4449afa8d26140898dd54d3390b057ba2a5afcf03ba29d7dc0d8b9ffe966e").unwrap(),
witnesses: vec![
Witness::from_vec(vec![
fn do_encoding_tx_init_rbf(funding_value_with_hex_target: Option<(i64, &str)>) {
let tx_init_rbf = msgs::TxInitRbf {
- channel_id: [2; 32],
+ channel_id: ChannelId::from_bytes([2; 32]),
locktime: 305419896,
feerate_sat_per_1000_weight: 20190119,
funding_output_contribution: if let Some((value, _)) = funding_value_with_hex_target { Some(value) } else { None },
fn do_encoding_tx_ack_rbf(funding_value_with_hex_target: Option<(i64, &str)>) {
let tx_ack_rbf = msgs::TxAckRbf {
- channel_id: [2; 32],
+ channel_id: ChannelId::from_bytes([2; 32]),
funding_output_contribution: if let Some((value, _)) = funding_value_with_hex_target { Some(value) } else { None },
};
let encoded_value = tx_ack_rbf.encode();
#[test]
fn encoding_tx_abort() {
let tx_abort = msgs::TxAbort {
- channel_id: [2; 32],
+ channel_id: ChannelId::from_bytes([2; 32]),
data: hex::decode("54686520717569636B2062726F776E20666F78206A756D7073206F76657220746865206C617A7920646F672E").unwrap(),
};
let encoded_value = tx_abort.encode();
let (_, pubkey_1) = get_keys_from!("0101010101010101010101010101010101010101010101010101010101010101", secp_ctx);
let script = Builder::new().push_opcode(opcodes::OP_TRUE).into_script();
let shutdown = msgs::Shutdown {
- channel_id: [2; 32],
+ channel_id: ChannelId::from_bytes([2; 32]),
scriptpubkey:
- if script_type == 1 { Address::p2pkh(&::bitcoin::PublicKey{compressed: true, inner: pubkey_1}, Network::Testnet).script_pubkey() }
+ if script_type == 1 { Address::p2pkh(&::bitcoin::PublicKey{compressed: true, inner: pubkey_1}, Network::Testnet).script_pubkey() }
else if script_type == 2 { Address::p2sh(&script, Network::Testnet).unwrap().script_pubkey() }
else if script_type == 3 { Address::p2wpkh(&::bitcoin::PublicKey{compressed: true, inner: pubkey_1}, Network::Testnet).unwrap().script_pubkey() }
- else { Address::p2wsh(&script, Network::Testnet).script_pubkey() },
+ else { Address::p2wsh(&script, Network::Testnet).script_pubkey() },
};
let encoded_value = shutdown.encode();
let mut target_value = hex::decode("0202020202020202020202020202020202020202020202020202020202020202").unwrap();
let (privkey_1, _) = get_keys_from!("0101010101010101010101010101010101010101010101010101010101010101", secp_ctx);
let sig_1 = get_sig_on!(privkey_1, secp_ctx, String::from("01010101010101010101010101010101"));
let closing_signed = msgs::ClosingSigned {
- channel_id: [2; 32],
+ channel_id: ChannelId::from_bytes([2; 32]),
fee_satoshis: 2316138423780173,
signature: sig_1,
fee_range: None,
assert_eq!(msgs::ClosingSigned::read(&mut Cursor::new(&target_value)).unwrap(), closing_signed);
let closing_signed_with_range = msgs::ClosingSigned {
- channel_id: [2; 32],
+ channel_id: ChannelId::from_bytes([2; 32]),
fee_satoshis: 2316138423780173,
signature: sig_1,
fee_range: Some(msgs::ClosingSignedFeeRange {
hmac: [2; 32]
};
let update_add_htlc = msgs::UpdateAddHTLC {
- channel_id: [2; 32],
+ channel_id: ChannelId::from_bytes([2; 32]),
htlc_id: 2316138423780173,
amount_msat: 3608586615801332854,
payment_hash: PaymentHash([1; 32]),
#[test]
fn encoding_update_fulfill_htlc() {
let update_fulfill_htlc = msgs::UpdateFulfillHTLC {
- channel_id: [2; 32],
+ channel_id: ChannelId::from_bytes([2; 32]),
htlc_id: 2316138423780173,
payment_preimage: PaymentPreimage([1; 32]),
};
data: [1; 32].to_vec(),
};
let update_fail_htlc = msgs::UpdateFailHTLC {
- channel_id: [2; 32],
+ channel_id: ChannelId::from_bytes([2; 32]),
htlc_id: 2316138423780173,
reason
};
#[test]
fn encoding_update_fail_malformed_htlc() {
let update_fail_malformed_htlc = msgs::UpdateFailMalformedHTLC {
- channel_id: [2; 32],
+ channel_id: ChannelId::from_bytes([2; 32]),
htlc_id: 2316138423780173,
sha256_of_onion: [1; 32],
failure_code: 255
let sig_3 = get_sig_on!(privkey_3, secp_ctx, String::from("01010101010101010101010101010101"));
let sig_4 = get_sig_on!(privkey_4, secp_ctx, String::from("01010101010101010101010101010101"));
let commitment_signed = msgs::CommitmentSigned {
- channel_id: [2; 32],
+ channel_id: ChannelId::from_bytes([2; 32]),
signature: sig_1,
htlc_signatures: if htlcs { vec![sig_2, sig_3, sig_4] } else { Vec::new() },
#[cfg(taproot)]
let secp_ctx = Secp256k1::new();
let (_, pubkey_1) = get_keys_from!("0101010101010101010101010101010101010101010101010101010101010101", secp_ctx);
let raa = msgs::RevokeAndACK {
- channel_id: [2; 32],
+ channel_id: ChannelId::from_bytes([2; 32]),
per_commitment_secret: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
next_per_commitment_point: pubkey_1,
#[cfg(taproot)]
#[test]
fn encoding_update_fee() {
let update_fee = msgs::UpdateFee {
- channel_id: [2; 32],
+ channel_id: ChannelId::from_bytes([2; 32]),
feerate_per_kw: 20190119,
};
let encoded_value = update_fee.encode();
}.encode(), hex::decode("00000000014001010101010101010101010101010101010101010101010101010101010101010202020202020202020202020202020202020202020202020202020202020202").unwrap());
let init_msg = msgs::Init { features: InitFeatures::from_le_bytes(vec![]),
networks: Some(vec![mainnet_hash]),
- remote_network_address: Some(msgs::NetAddress::IPv4 {
+ remote_network_address: Some(SocketAddress::TcpIpV4 {
addr: [127, 0, 0, 1],
port: 1000,
}),
#[test]
fn encoding_error() {
let error = msgs::ErrorMessage {
- channel_id: [2; 32],
+ channel_id: ChannelId::from_bytes([2; 32]),
data: String::from("rust-lightning"),
};
let encoded_value = error.encode();
#[test]
fn encoding_warning() {
let error = msgs::WarningMessage {
- channel_id: [2; 32],
+ channel_id: ChannelId::from_bytes([2; 32]),
data: String::from("rust-lightning"),
};
let encoded_value = error.encode();
#[test]
fn encoding_nonfinal_onion_hop_data() {
- let mut msg = msgs::OnionHopData {
- format: OnionHopDataFormat::NonFinalNode {
- short_channel_id: 0xdeadbeef1bad1dea,
- },
+ let outbound_msg = msgs::OutboundOnionPayload::Forward {
+ short_channel_id: 0xdeadbeef1bad1dea,
amt_to_forward: 0x0badf00d01020304,
outgoing_cltv_value: 0xffffffff,
};
- let encoded_value = msg.encode();
+ let encoded_value = outbound_msg.encode();
let target_value = hex::decode("1a02080badf00d010203040404ffffffff0608deadbeef1bad1dea").unwrap();
assert_eq!(encoded_value, target_value);
- msg = Readable::read(&mut Cursor::new(&target_value[..])).unwrap();
- if let OnionHopDataFormat::NonFinalNode { short_channel_id } = msg.format {
+
+ let node_signer = test_utils::TestKeysInterface::new(&[42; 32], Network::Testnet);
+ let inbound_msg = ReadableArgs::read(&mut Cursor::new(&target_value[..]), &&node_signer).unwrap();
+ if let msgs::InboundOnionPayload::Forward {
+ short_channel_id, amt_to_forward, outgoing_cltv_value
+ } = inbound_msg {
assert_eq!(short_channel_id, 0xdeadbeef1bad1dea);
+ assert_eq!(amt_to_forward, 0x0badf00d01020304);
+ assert_eq!(outgoing_cltv_value, 0xffffffff);
} else { panic!(); }
- assert_eq!(msg.amt_to_forward, 0x0badf00d01020304);
- assert_eq!(msg.outgoing_cltv_value, 0xffffffff);
}
#[test]
fn encoding_final_onion_hop_data() {
- let mut msg = msgs::OnionHopData {
- format: OnionHopDataFormat::FinalNode {
- payment_data: None,
- payment_metadata: None,
- keysend_preimage: None,
- },
- amt_to_forward: 0x0badf00d01020304,
+ let outbound_msg = msgs::OutboundOnionPayload::Receive {
+ payment_data: None,
+ payment_metadata: None,
+ keysend_preimage: None,
+ amt_msat: 0x0badf00d01020304,
outgoing_cltv_value: 0xffffffff,
+ custom_tlvs: vec![],
};
- let encoded_value = msg.encode();
+ let encoded_value = outbound_msg.encode();
let target_value = hex::decode("1002080badf00d010203040404ffffffff").unwrap();
assert_eq!(encoded_value, target_value);
- msg = Readable::read(&mut Cursor::new(&target_value[..])).unwrap();
- if let OnionHopDataFormat::FinalNode { payment_data: None, .. } = msg.format { } else { panic!(); }
- assert_eq!(msg.amt_to_forward, 0x0badf00d01020304);
- assert_eq!(msg.outgoing_cltv_value, 0xffffffff);
+
+ let node_signer = test_utils::TestKeysInterface::new(&[42; 32], Network::Testnet);
+ let inbound_msg = ReadableArgs::read(&mut Cursor::new(&target_value[..]), &&node_signer).unwrap();
+ if let msgs::InboundOnionPayload::Receive {
+ payment_data: None, amt_msat, outgoing_cltv_value, ..
+ } = inbound_msg {
+ assert_eq!(amt_msat, 0x0badf00d01020304);
+ assert_eq!(outgoing_cltv_value, 0xffffffff);
+ } else { panic!(); }
}
#[test]
fn encoding_final_onion_hop_data_with_secret() {
let expected_payment_secret = PaymentSecret([0x42u8; 32]);
- let mut msg = msgs::OnionHopData {
- format: OnionHopDataFormat::FinalNode {
- payment_data: Some(FinalOnionHopData {
- payment_secret: expected_payment_secret,
- total_msat: 0x1badca1f
- }),
- payment_metadata: None,
- keysend_preimage: None,
- },
- amt_to_forward: 0x0badf00d01020304,
+ let outbound_msg = msgs::OutboundOnionPayload::Receive {
+ payment_data: Some(FinalOnionHopData {
+ payment_secret: expected_payment_secret,
+ total_msat: 0x1badca1f
+ }),
+ payment_metadata: None,
+ keysend_preimage: None,
+ amt_msat: 0x0badf00d01020304,
outgoing_cltv_value: 0xffffffff,
+ custom_tlvs: vec![],
};
- let encoded_value = msg.encode();
+ let encoded_value = outbound_msg.encode();
let target_value = hex::decode("3602080badf00d010203040404ffffffff082442424242424242424242424242424242424242424242424242424242424242421badca1f").unwrap();
assert_eq!(encoded_value, target_value);
- msg = Readable::read(&mut Cursor::new(&target_value[..])).unwrap();
- if let OnionHopDataFormat::FinalNode {
+
+ let node_signer = test_utils::TestKeysInterface::new(&[42; 32], Network::Testnet);
+ let inbound_msg = ReadableArgs::read(&mut Cursor::new(&target_value[..]), &&node_signer).unwrap();
+ if let msgs::InboundOnionPayload::Receive {
payment_data: Some(FinalOnionHopData {
payment_secret,
total_msat: 0x1badca1f
}),
+ amt_msat, outgoing_cltv_value,
payment_metadata: None,
keysend_preimage: None,
- } = msg.format {
+ custom_tlvs,
+ } = inbound_msg {
assert_eq!(payment_secret, expected_payment_secret);
+ assert_eq!(amt_msat, 0x0badf00d01020304);
+ assert_eq!(outgoing_cltv_value, 0xffffffff);
+ assert_eq!(custom_tlvs, vec![]);
+ } else { panic!(); }
+ }
+
+ #[test]
+ fn encoding_final_onion_hop_data_with_bad_custom_tlvs() {
+ // If custom TLVs have type number within the range reserved for protocol, treat them as if
+ // they're unknown
+ let bad_type_range_tlvs = vec![
+ ((1 << 16) - 4, vec![42]),
+ ((1 << 16) - 2, vec![42; 32]),
+ ];
+ let mut msg = msgs::OutboundOnionPayload::Receive {
+ payment_data: None,
+ payment_metadata: None,
+ keysend_preimage: None,
+ custom_tlvs: bad_type_range_tlvs,
+ amt_msat: 0x0badf00d01020304,
+ outgoing_cltv_value: 0xffffffff,
+ };
+ let encoded_value = msg.encode();
+ let node_signer = test_utils::TestKeysInterface::new(&[42; 32], Network::Testnet);
+ assert!(msgs::InboundOnionPayload::read(&mut Cursor::new(&encoded_value[..]), &&node_signer).is_err());
+ let good_type_range_tlvs = vec![
+ ((1 << 16) - 3, vec![42]),
+ ((1 << 16) - 1, vec![42; 32]),
+ ];
+ if let msgs::OutboundOnionPayload::Receive { ref mut custom_tlvs, .. } = msg {
+ *custom_tlvs = good_type_range_tlvs.clone();
+ }
+ let encoded_value = msg.encode();
+ let inbound_msg = ReadableArgs::read(&mut Cursor::new(&encoded_value[..]), &&node_signer).unwrap();
+ match inbound_msg {
+ msgs::InboundOnionPayload::Receive { custom_tlvs, .. } => assert!(custom_tlvs.is_empty()),
+ _ => panic!(),
+ }
+ }
+
+ #[test]
+ fn encoding_final_onion_hop_data_with_custom_tlvs() {
+ let expected_custom_tlvs = vec![
+ (5482373483, vec![0x12, 0x34]),
+ (5482373487, vec![0x42u8; 8]),
+ ];
+ let msg = msgs::OutboundOnionPayload::Receive {
+ payment_data: None,
+ payment_metadata: None,
+ keysend_preimage: None,
+ custom_tlvs: expected_custom_tlvs.clone(),
+ amt_msat: 0x0badf00d01020304,
+ outgoing_cltv_value: 0xffffffff,
+ };
+ let encoded_value = msg.encode();
+ let target_value = hex::decode("2e02080badf00d010203040404ffffffffff0000000146c6616b021234ff0000000146c6616f084242424242424242").unwrap();
+ assert_eq!(encoded_value, target_value);
+ let node_signer = test_utils::TestKeysInterface::new(&[42; 32], Network::Testnet);
+ let inbound_msg: msgs::InboundOnionPayload = ReadableArgs::read(&mut Cursor::new(&target_value[..]), &&node_signer).unwrap();
+ if let msgs::InboundOnionPayload::Receive {
+ payment_data: None,
+ payment_metadata: None,
+ keysend_preimage: None,
+ custom_tlvs,
+ amt_msat,
+ outgoing_cltv_value,
+ ..
+ } = inbound_msg {
+ assert_eq!(custom_tlvs, expected_custom_tlvs);
+ assert_eq!(amt_msat, 0x0badf00d01020304);
+ assert_eq!(outgoing_cltv_value, 0xffffffff);
} else { panic!(); }
- assert_eq!(msg.amt_to_forward, 0x0badf00d01020304);
- assert_eq!(msg.outgoing_cltv_value, 0xffffffff);
}
#[test]
// payload length to be encoded over multiple bytes rather than a single u8.
let big_payload = encode_big_payload().unwrap();
let mut rd = Cursor::new(&big_payload[..]);
- <msgs::OnionHopData as Readable>::read(&mut rd).unwrap();
+
+ let node_signer = test_utils::TestKeysInterface::new(&[42; 32], Network::Testnet);
+ <msgs::InboundOnionPayload as ReadableArgs<&&test_utils::TestKeysInterface>>
+ ::read(&mut rd, &&node_signer).unwrap();
}
// see above test, needs to be a separate method for use of the serialization macros.
fn encode_big_payload() -> Result<Vec<u8>, io::Error> {
use crate::util::ser::HighZeroBytesDroppedBigSize;
- let payload = msgs::OnionHopData {
- format: OnionHopDataFormat::NonFinalNode {
- short_channel_id: 0xdeadbeef1bad1dea,
- },
+ let payload = msgs::OutboundOnionPayload::Forward {
+ short_channel_id: 0xdeadbeef1bad1dea,
amt_to_forward: 1000,
outgoing_cltv_value: 0xffffffff,
};
let mut encoded_payload = Vec::new();
let test_bytes = vec![42u8; 1000];
- if let OnionHopDataFormat::NonFinalNode { short_channel_id } = payload.format {
+ if let msgs::OutboundOnionPayload::Forward { short_channel_id, amt_to_forward, outgoing_cltv_value } = payload {
_encode_varint_length_prefixed_tlv!(&mut encoded_payload, {
- (1, test_bytes, vec_type),
- (2, HighZeroBytesDroppedBigSize(payload.amt_to_forward), required),
- (4, HighZeroBytesDroppedBigSize(payload.outgoing_cltv_value), required),
+ (1, test_bytes, required_vec),
+ (2, HighZeroBytesDroppedBigSize(amt_to_forward), required),
+ (4, HighZeroBytesDroppedBigSize(outgoing_cltv_value), required),
(6, short_channel_id, required)
});
}
Ok(encoded_payload)
}
+
+ #[test]
+ #[cfg(feature = "std")]
+ fn test_socket_address_from_str() {
+ assert_eq!(SocketAddress::TcpIpV4 {
+ addr: Ipv4Addr::new(127, 0, 0, 1).octets(),
+ port: 1234,
+ }, SocketAddress::from_str("127.0.0.1:1234").unwrap());
+
+ assert_eq!(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 {
+ 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 {
+ 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!(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!(SocketAddress::from_str("pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion.onion:9735:94").is_err());
+ assert!(SocketAddress::from_str("wrong$%#.com:1234").is_err());
+ assert_eq!(Err(SocketAddressParseError::InvalidPort), SocketAddress::from_str("example.com:wrong"));
+ assert!("localhost".parse::<SocketAddress>().is_err());
+ assert!("localhost:invalid-port".parse::<SocketAddress>().is_err());
+ assert!( "invalid-onion-v3-hostname.onion:8080".parse::<SocketAddress>().is_err());
+ assert!("b32.example.onion:invalid-port".parse::<SocketAddress>().is_err());
+ assert!("invalid-address".parse::<SocketAddress>().is_err());
+ assert!(SocketAddress::from_str("pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion.onion:1234").is_err());
+ }
}
use crate::ln::channelmanager::{HTLCForwardInfo, FailureCode, CLTV_FAR_FAR_AWAY, DISABLE_GOSSIP_TICKS, MIN_CLTV_EXPIRY_DELTA, PendingAddHTLCInfo, PendingHTLCInfo, PendingHTLCRouting, PaymentId, RecipientOnionFields};
use crate::ln::onion_utils;
use crate::routing::gossip::{NetworkUpdate, RoutingFees};
-use crate::routing::router::{get_route, PaymentParameters, Route, RouteHint, RouteHintHop};
-use crate::ln::features::{InitFeatures, InvoiceFeatures};
+use crate::routing::router::{get_route, PaymentParameters, Route, RouteParameters, RouteHint, RouteHintHop};
+use crate::ln::features::{InitFeatures, Bolt11InvoiceFeatures};
use crate::ln::msgs;
use crate::ln::msgs::{ChannelMessageHandler, ChannelUpdate};
use crate::ln::wire::Encode;
-use crate::util::ser::{Writeable, Writer};
+use crate::util::ser::{Writeable, Writer, BigSize};
use crate::util::test_utils;
-use crate::util::config::{UserConfig, ChannelConfig};
+use crate::util::config::{UserConfig, ChannelConfig, MaxDustHTLCExposure};
use crate::util::errors::APIError;
use bitcoin::hash_types::BlockHash;
-use bitcoin::hashes::Hash;
+use bitcoin::hashes::{Hash, HashEngine};
+use bitcoin::hashes::hmac::{Hmac, HmacEngine};
use bitcoin::hashes::sha256::Hash as Sha256;
use bitcoin::secp256k1;
// 3: final node fails backward (but tamper onion payloads from node0)
// 100: trigger error in the intermediate node and tamper returning fail_htlc
// 200: trigger error in the final node and tamper returning fail_htlc
-fn run_onion_failure_test_with_fail_intercept<F1,F2,F3>(_name: &str, test_case: u8, nodes: &Vec<Node>, route: &Route, payment_hash: &PaymentHash, payment_secret: &PaymentSecret, mut callback_msg: F1, mut callback_fail: F2, mut callback_node: F3, expected_retryable: bool, expected_error_code: Option<u16>, expected_channel_update: Option<NetworkUpdate>, expected_short_channel_id: Option<u64>)
+fn run_onion_failure_test_with_fail_intercept<F1,F2,F3>(
+ _name: &str, test_case: u8, nodes: &Vec<Node>, route: &Route, payment_hash: &PaymentHash,
+ payment_secret: &PaymentSecret, mut callback_msg: F1, mut callback_fail: F2,
+ mut callback_node: F3, expected_retryable: bool, expected_error_code: Option<u16>,
+ expected_channel_update: Option<NetworkUpdate>, expected_short_channel_id: Option<u64>
+)
where F1: for <'a> FnMut(&'a mut msgs::UpdateAddHTLC),
F2: for <'a> FnMut(&'a mut msgs::UpdateFailHTLC),
F3: FnMut(),
data: Vec<u8>
}
impl BogusOnionHopData {
- fn new(orig: msgs::OnionHopData) -> Self {
+ fn new(orig: msgs::OutboundOnionPayload) -> Self {
Self { data: orig.encode() }
}
}
let short_channel_id = channels[1].0.contents.short_channel_id;
let amt_to_forward = nodes[1].node.per_peer_state.read().unwrap().get(&nodes[2].node.get_our_node_id())
.unwrap().lock().unwrap().channel_by_id.get(&channels[1].2).unwrap()
- .context.get_counterparty_htlc_minimum_msat() - 1;
+ .context().get_counterparty_htlc_minimum_msat() - 1;
let mut bogus_route = route.clone();
let route_len = bogus_route.paths[0].hops.len();
bogus_route.paths[0].hops[route_len-1].fee_msat = amt_to_forward;
nodes[1].node.get_and_clear_pending_msg_events();
nodes[2].node.get_and_clear_pending_msg_events();
}, true, Some(UPDATE|20), Some(NetworkUpdate::ChannelUpdateMessage{msg: ChannelUpdate::dummy(short_channel_id)}), Some(short_channel_id));
- reconnect_nodes(&nodes[1], &nodes[2], (false, false), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (false, false));
+ reconnect_nodes(ReconnectArgs::new(&nodes[1], &nodes[2]));
run_onion_failure_test("expiry_too_far", 0, &nodes, &route, &payment_hash, &payment_secret, |msg| {
let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
}, ||{
nodes[2].node.fail_htlc_backwards(&payment_hash);
}, true, Some(23), None, None);
+
+ run_onion_failure_test_with_fail_intercept("bogus err packet with valid hmac", 200, &nodes,
+ &route, &payment_hash, &payment_secret, |_msg| {}, |msg| {
+ let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
+ let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
+ let mut decoded_err_packet = msgs::DecodedOnionErrorPacket {
+ failuremsg: vec![0],
+ pad: vec![0; 255],
+ hmac: [0; 32],
+ };
+ let um = onion_utils::gen_um_from_shared_secret(&onion_keys[1].shared_secret.as_ref());
+ let mut hmac = HmacEngine::<Sha256>::new(&um);
+ hmac.input(&decoded_err_packet.encode()[32..]);
+ decoded_err_packet.hmac = Hmac::from_engine(hmac).into_inner();
+ msg.reason = onion_utils::encrypt_failure_packet(
+ &onion_keys[1].shared_secret.as_ref(), &decoded_err_packet.encode()[..])
+ }, || nodes[2].node.fail_htlc_backwards(&payment_hash), false, None,
+ Some(NetworkUpdate::NodeFailure { node_id: route.paths[0].hops[1].pubkey, is_permanent: true }),
+ Some(channels[1].0.contents.short_channel_id));
+ run_onion_failure_test_with_fail_intercept("0-length channel update in intermediate node UPDATE onion failure",
+ 100, &nodes, &route, &payment_hash, &payment_secret, |msg| {
+ msg.amount_msat -= 1;
+ }, |msg| {
+ let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
+ let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
+ let mut decoded_err_packet = msgs::DecodedOnionErrorPacket {
+ failuremsg: vec![
+ 0x10, 0x7, // UPDATE|7
+ 0x0, 0x0 // 0-len channel update
+ ],
+ pad: vec![0; 255 - 4 /* 4-byte error message */],
+ hmac: [0; 32],
+ };
+ let um = onion_utils::gen_um_from_shared_secret(&onion_keys[0].shared_secret.as_ref());
+ let mut hmac = HmacEngine::<Sha256>::new(&um);
+ hmac.input(&decoded_err_packet.encode()[32..]);
+ decoded_err_packet.hmac = Hmac::from_engine(hmac).into_inner();
+ msg.reason = onion_utils::encrypt_failure_packet(
+ &onion_keys[0].shared_secret.as_ref(), &decoded_err_packet.encode()[..])
+ }, || {}, true, Some(0x1000|7),
+ Some(NetworkUpdate::ChannelFailure {
+ short_channel_id: channels[1].0.contents.short_channel_id,
+ is_permanent: false,
+ }),
+ Some(channels[1].0.contents.short_channel_id));
+ run_onion_failure_test_with_fail_intercept("0-length channel update in final node UPDATE onion failure",
+ 200, &nodes, &route, &payment_hash, &payment_secret, |_msg| {}, |msg| {
+ let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
+ let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
+ let mut decoded_err_packet = msgs::DecodedOnionErrorPacket {
+ failuremsg: vec![
+ 0x10, 0x7, // UPDATE|7
+ 0x0, 0x0 // 0-len channel update
+ ],
+ pad: vec![0; 255 - 4 /* 4-byte error message */],
+ hmac: [0; 32],
+ };
+ let um = onion_utils::gen_um_from_shared_secret(&onion_keys[1].shared_secret.as_ref());
+ let mut hmac = HmacEngine::<Sha256>::new(&um);
+ hmac.input(&decoded_err_packet.encode()[32..]);
+ decoded_err_packet.hmac = Hmac::from_engine(hmac).into_inner();
+ msg.reason = onion_utils::encrypt_failure_packet(
+ &onion_keys[1].shared_secret.as_ref(), &decoded_err_packet.encode()[..])
+ }, || nodes[2].node.fail_htlc_backwards(&payment_hash), true, Some(0x1000|7),
+ Some(NetworkUpdate::ChannelFailure {
+ short_channel_id: channels[1].0.contents.short_channel_id,
+ is_permanent: false,
+ }),
+ Some(channels[1].0.contents.short_channel_id));
}
#[test]
config.channel_handshake_config.announced_channel = announced_channel;
config.channel_handshake_limits.force_announced_channel_preference = false;
config.accept_forwards_to_priv_channels = !announced_channel;
+ config.channel_config.max_dust_htlc_exposure = MaxDustHTLCExposure::FeeRateMultiplier(5_000_000 / 253);
let chanmon_cfgs = create_chanmon_cfgs(3);
+ let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
let persister;
let chain_monitor;
- let channel_manager_1_deserialized;
- let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, Some(config), None]);
+ let channel_manager_1_deserialized;
let mut nodes = create_network(3, &node_cfgs, &node_chanmgrs);
let other_channel = create_chan_between_nodes(
create_announced_chan_between_nodes(&nodes, 1, 2);
let payment_params = PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), TEST_FINAL_CLTV)
- .with_bolt11_features(InvoiceFeatures::empty()).unwrap();
+ .with_bolt11_features(Bolt11InvoiceFeatures::empty()).unwrap();
let (route, _payment_hash, _payment_preimage, _payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[2], payment_params, 40000);
let hops = &route.paths[0].hops;
let (onion_payloads, _htlc_msat, _htlc_cltv) = onion_utils::build_onion_payloads(
&route.paths[0], 40000, RecipientOnionFields::spontaneous_empty(), cur_height, &None).unwrap();
- match onion_payloads[0].format {
- msgs::OnionHopDataFormat::NonFinalNode {..} => {},
+ match onion_payloads[0] {
+ msgs::OutboundOnionPayload::Forward {..} => {},
_ => { panic!(
"Should have generated a `msgs::OnionHopDataFormat::NonFinalNode` payload for `hops[0]`,
despite that the features signals no support for variable length onions"
)}
}
- match onion_payloads[1].format {
- msgs::OnionHopDataFormat::FinalNode {..} => {},
+ match onion_payloads[1] {
+ msgs::OutboundOnionPayload::Receive {..} => {},
_ => {panic!(
"Should have generated a `msgs::OnionHopDataFormat::FinalNode` payload for `hops[1]`,
despite that the features signals no support for variable length onions"
let mut htlc_msat_height_data = (payment_amount as u64).to_be_bytes().to_vec();
htlc_msat_height_data.extend_from_slice(&CHAN_CONFIRM_DEPTH.to_be_bytes());
htlc_msat_height_data
+ },
+ FailureCode::InvalidOnionPayload(data) => {
+ match data {
+ Some((typ, offset)) => [BigSize(typ).encode(), offset.encode()].concat(),
+ None => Vec::new(),
+ }
}
};
- let failure_code = failure_code as u16;
+ let failure_code = failure_code.into();
let permanent_flag = 0x4000;
let permanent_fail = (failure_code & permanent_flag) != 0;
expect_payment_failed!(nodes[0], payment_hash, permanent_fail, failure_code, failure_data);
do_test_fail_htlc_backwards_with_reason(FailureCode::TemporaryNodeFailure);
do_test_fail_htlc_backwards_with_reason(FailureCode::RequiredNodeFeatureMissing);
do_test_fail_htlc_backwards_with_reason(FailureCode::IncorrectOrUnknownPaymentDetails);
+ do_test_fail_htlc_backwards_with_reason(FailureCode::InvalidOnionPayload(Some((1 << 16, 42))));
+ do_test_fail_htlc_backwards_with_reason(FailureCode::InvalidOnionPayload(None));
}
macro_rules! get_phantom_route {
])]).unwrap();
let scorer = test_utils::TestScorer::new();
let network_graph = $nodes[0].network_graph.read_only();
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, $amt);
(get_route(
- &$nodes[0].node.get_our_node_id(), &payment_params, &network_graph,
+ &$nodes[0].node.get_our_node_id(), &route_params, &network_graph,
Some(&$nodes[0].node.list_usable_channels().iter().collect::<Vec<_>>()),
- $amt, $nodes[0].logger, &scorer, &(), &[0u8; 32]
+ $nodes[0].logger, &scorer, &Default::default(), &[0u8; 32]
).unwrap(), phantom_route_hint.phantom_scid)
}
}}
#[test]
fn test_phantom_dust_exposure_failure() {
+ do_test_phantom_dust_exposure_failure(false);
+ do_test_phantom_dust_exposure_failure(true);
+}
+
+fn do_test_phantom_dust_exposure_failure(multiplier_dust_limit: bool) {
// Set the max dust exposure to the dust limit.
let max_dust_exposure = 546;
let mut receiver_config = UserConfig::default();
- receiver_config.channel_config.max_dust_htlc_exposure_msat = max_dust_exposure;
+ // Default test fee estimator rate is 253, so to set the max dust exposure to the dust limit,
+ // we need to set the multiplier to 2.
+ receiver_config.channel_config.max_dust_htlc_exposure =
+ if multiplier_dust_limit { MaxDustHTLCExposure::FeeRateMultiplier(2) }
+ else { MaxDustHTLCExposure::FixedLimitMsat(max_dust_exposure) };
receiver_config.channel_handshake_config.announced_channel = true;
let chanmon_cfgs = create_chanmon_cfgs(2);
use crate::ln::msgs;
use crate::ln::wire::Encode;
use crate::routing::gossip::NetworkUpdate;
-use crate::routing::router::{Path, RouteHop};
+use crate::routing::router::{BlindedTail, Path, RouteHop};
+use crate::sign::NodeSigner;
use crate::util::chacha20::{ChaCha20, ChaChaReader};
use crate::util::errors::{self, APIError};
use crate::util::ser::{Readable, ReadableArgs, Writeable, Writer, LengthCalculatingWriter};
Hmac::from_engine(hmac).into_inner()
}
-pub(crate) fn next_hop_packet_pubkey<T: secp256k1::Signing + secp256k1::Verification>(secp_ctx: &Secp256k1<T>, packet_pubkey: PublicKey, packet_shared_secret: &[u8; 32]) -> Result<PublicKey, secp256k1::Error> {
+/// 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>(
+ secp_ctx: &Secp256k1<T>, curr_pubkey: PublicKey, shared_secret: &[u8]
+) -> Result<PublicKey, secp256k1::Error> {
let blinding_factor = {
let mut sha = Sha256::engine();
- sha.input(&packet_pubkey.serialize()[..]);
- sha.input(packet_shared_secret);
+ sha.input(&curr_pubkey.serialize()[..]);
+ sha.input(shared_secret);
Sha256::from_engine(sha).into_inner()
};
- packet_pubkey.mul_tweak(secp_ctx, &Scalar::from_be_bytes(blinding_factor).unwrap())
+ curr_pubkey.mul_tweak(secp_ctx, &Scalar::from_be_bytes(blinding_factor).unwrap())
}
// can only fail if an intermediary hop has an invalid public key or session_priv is invalid
#[inline]
-pub(super) fn construct_onion_keys_callback<T: secp256k1::Signing, FType: FnMut(SharedSecret, [u8; 32], PublicKey, &RouteHop, usize)> (secp_ctx: &Secp256k1<T>, path: &Vec<RouteHop>, session_priv: &SecretKey, mut callback: FType) -> Result<(), secp256k1::Error> {
+pub(super) fn construct_onion_keys_callback<T, FType>(
+ secp_ctx: &Secp256k1<T>, path: &Path, session_priv: &SecretKey, mut callback: FType
+) -> Result<(), secp256k1::Error>
+where
+ T: secp256k1::Signing,
+ FType: FnMut(SharedSecret, [u8; 32], PublicKey, Option<&RouteHop>, usize)
+{
let mut blinded_priv = session_priv.clone();
let mut blinded_pub = PublicKey::from_secret_key(secp_ctx, &blinded_priv);
- for (idx, hop) in path.iter().enumerate() {
- let shared_secret = SharedSecret::new(&hop.pubkey, &blinded_priv);
+ let unblinded_hops_iter = path.hops.iter().map(|h| (&h.pubkey, Some(h)));
+ let blinded_pks_iter = path.blinded_tail.as_ref()
+ .map(|t| t.hops.iter()).unwrap_or([].iter())
+ .skip(1) // Skip the intro node because it's included in the unblinded hops
+ .map(|h| (&h.blinded_node_id, None));
+ for (idx, (pubkey, route_hop_opt)) in unblinded_hops_iter.chain(blinded_pks_iter).enumerate() {
+ let shared_secret = SharedSecret::new(pubkey, &blinded_priv);
let mut sha = Sha256::engine();
sha.input(&blinded_pub.serialize()[..]);
blinded_priv = blinded_priv.mul_tweak(&Scalar::from_be_bytes(blinding_factor).unwrap())?;
blinded_pub = PublicKey::from_secret_key(secp_ctx, &blinded_priv);
- callback(shared_secret, blinding_factor, ephemeral_pubkey, hop, idx);
+ callback(shared_secret, blinding_factor, ephemeral_pubkey, route_hop_opt, idx);
}
Ok(())
pub(super) fn construct_onion_keys<T: secp256k1::Signing>(secp_ctx: &Secp256k1<T>, path: &Path, session_priv: &SecretKey) -> Result<Vec<OnionKeys>, secp256k1::Error> {
let mut res = Vec::with_capacity(path.hops.len());
- construct_onion_keys_callback(secp_ctx, &path.hops, session_priv, |shared_secret, _blinding_factor, ephemeral_pubkey, _, _| {
+ construct_onion_keys_callback(secp_ctx, &path, session_priv,
+ |shared_secret, _blinding_factor, ephemeral_pubkey, _, _|
+ {
let (rho, mu) = gen_rho_mu_from_shared_secret(shared_secret.as_ref());
res.push(OnionKeys {
}
/// returns the hop data, as well as the first-hop value_msat and CLTV value we should send.
-pub(super) fn build_onion_payloads(path: &Path, total_msat: u64, mut recipient_onion: RecipientOnionFields, starting_htlc_offset: u32, keysend_preimage: &Option<PaymentPreimage>) -> Result<(Vec<msgs::OnionHopData>, u64, u32), APIError> {
+pub(super) fn build_onion_payloads(path: &Path, total_msat: u64, mut recipient_onion: RecipientOnionFields, starting_htlc_offset: u32, keysend_preimage: &Option<PaymentPreimage>) -> Result<(Vec<msgs::OutboundOnionPayload>, u64, u32), APIError> {
let mut cur_value_msat = 0u64;
let mut cur_cltv = starting_htlc_offset;
let mut last_short_channel_id = 0;
- let mut res: Vec<msgs::OnionHopData> = Vec::with_capacity(path.hops.len());
+ let mut res: Vec<msgs::OutboundOnionPayload> = Vec::with_capacity(
+ path.hops.len() + path.blinded_tail.as_ref().map_or(0, |t| t.hops.len())
+ );
for (idx, hop) in path.hops.iter().rev().enumerate() {
// First hop gets special values so that it can check, on receipt, that everything is
// the intended recipient).
let value_msat = if cur_value_msat == 0 { hop.fee_msat } else { cur_value_msat };
let cltv = if cur_cltv == starting_htlc_offset { hop.cltv_expiry_delta + starting_htlc_offset } else { cur_cltv };
- res.insert(0, msgs::OnionHopData {
- format: if idx == 0 {
- msgs::OnionHopDataFormat::FinalNode {
+ if idx == 0 {
+ if let Some(BlindedTail {
+ blinding_point, hops, final_value_msat, excess_final_cltv_expiry_delta, ..
+ }) = &path.blinded_tail {
+ let mut blinding_point = Some(*blinding_point);
+ for (i, blinded_hop) in hops.iter().enumerate() {
+ if i == hops.len() - 1 {
+ cur_value_msat += final_value_msat;
+ cur_cltv += excess_final_cltv_expiry_delta;
+ res.push(msgs::OutboundOnionPayload::BlindedReceive {
+ amt_msat: *final_value_msat,
+ total_msat,
+ outgoing_cltv_value: cltv,
+ encrypted_tlvs: blinded_hop.encrypted_payload.clone(),
+ intro_node_blinding_point: blinding_point.take(),
+ });
+ } else {
+ res.push(msgs::OutboundOnionPayload::BlindedForward {
+ encrypted_tlvs: blinded_hop.encrypted_payload.clone(),
+ intro_node_blinding_point: blinding_point.take(),
+ });
+ }
+ }
+ } else {
+ res.push(msgs::OutboundOnionPayload::Receive {
payment_data: if let Some(secret) = recipient_onion.payment_secret.take() {
Some(msgs::FinalOnionHopData {
payment_secret: secret,
} else { None },
payment_metadata: recipient_onion.payment_metadata.take(),
keysend_preimage: *keysend_preimage,
- }
- } else {
- msgs::OnionHopDataFormat::NonFinalNode {
- short_channel_id: last_short_channel_id,
- }
- },
- amt_to_forward: value_msat,
- outgoing_cltv_value: cltv,
- });
+ custom_tlvs: recipient_onion.custom_tlvs.clone(),
+ amt_msat: value_msat,
+ outgoing_cltv_value: cltv,
+ });
+ }
+ } else {
+ res.insert(0, msgs::OutboundOnionPayload::Forward {
+ short_channel_id: last_short_channel_id,
+ amt_to_forward: value_msat,
+ outgoing_cltv_value: cltv,
+ });
+ }
cur_value_msat += hop.fee_msat;
if cur_value_msat >= 21000000 * 100000000 * 1000 {
return Err(APIError::InvalidRoute{err: "Channel fees overflowed?".to_owned()});
}
}
-pub(super) fn construct_onion_packet(payloads: Vec<msgs::OnionHopData>, onion_keys: Vec<OnionKeys>, prng_seed: [u8; 32], associated_data: &PaymentHash) -> Result<msgs::OnionPacket, ()> {
+pub(super) fn construct_onion_packet(
+ payloads: Vec<msgs::OutboundOnionPayload>, onion_keys: Vec<OnionKeys>, prng_seed: [u8; 32],
+ associated_data: &PaymentHash
+) -> Result<msgs::OnionPacket, ()> {
let mut packet_data = [0; ONION_DATA_LEN];
let mut chacha = ChaCha20::new(&prng_seed, &[0; 8]);
encrypt_failure_packet(shared_secret, &failure_packet.encode()[..])
}
+pub(crate) struct DecodedOnionFailure {
+ pub(crate) network_update: Option<NetworkUpdate>,
+ pub(crate) short_channel_id: Option<u64>,
+ pub(crate) payment_failed_permanently: bool,
+ #[cfg(test)]
+ pub(crate) onion_error_code: Option<u16>,
+ #[cfg(test)]
+ pub(crate) onion_error_data: Option<Vec<u8>>,
+}
+
/// Process failure we got back from upstream on a payment we sent (implying htlc_source is an
/// OutboundRoute).
-/// Returns update, a boolean indicating that the payment itself failed, the short channel id of
-/// the responsible channel, and the error code.
#[inline]
-pub(super) fn process_onion_failure<T: secp256k1::Signing, L: Deref>(secp_ctx: &Secp256k1<T>, logger: &L, htlc_source: &HTLCSource, mut packet_decrypted: Vec<u8>) -> (Option<NetworkUpdate>, Option<u64>, bool, Option<u16>, Option<Vec<u8>>) where L::Target: Logger {
- if let &HTLCSource::OutboundRoute { ref path, ref session_priv, ref first_hop_htlc_msat, .. } = htlc_source {
- let mut res = None;
- let mut htlc_msat = *first_hop_htlc_msat;
- let mut error_code_ret = None;
- let mut error_packet_ret = None;
- let mut is_from_final_node = false;
-
- // Handle packed channel/node updates for passing back for the route handler
- construct_onion_keys_callback(secp_ctx, &path.hops, session_priv, |shared_secret, _, _, route_hop, route_hop_idx| {
- if res.is_some() { return; }
-
- let amt_to_forward = htlc_msat - route_hop.fee_msat;
- htlc_msat = amt_to_forward;
-
- let ammag = gen_ammag_from_shared_secret(shared_secret.as_ref());
-
- let mut decryption_tmp = Vec::with_capacity(packet_decrypted.len());
- decryption_tmp.resize(packet_decrypted.len(), 0);
- let mut chacha = ChaCha20::new(&ammag, &[0u8; 8]);
- chacha.process(&packet_decrypted, &mut decryption_tmp[..]);
- packet_decrypted = decryption_tmp;
-
- // The failing hop includes either the inbound channel to the recipient or the outbound
- // channel from the current hop (i.e., the next hop's inbound channel).
- is_from_final_node = route_hop_idx + 1 == path.hops.len();
- let failing_route_hop = if is_from_final_node { route_hop } else { &path.hops[route_hop_idx + 1] };
-
- if let Ok(err_packet) = msgs::DecodedOnionErrorPacket::read(&mut Cursor::new(&packet_decrypted)) {
- let um = gen_um_from_shared_secret(shared_secret.as_ref());
- let mut hmac = HmacEngine::<Sha256>::new(&um);
- hmac.input(&err_packet.encode()[32..]);
-
- if fixed_time_eq(&Hmac::from_engine(hmac).into_inner(), &err_packet.hmac) {
- if let Some(error_code_slice) = err_packet.failuremsg.get(0..2) {
- const BADONION: u16 = 0x8000;
- const PERM: u16 = 0x4000;
- const NODE: u16 = 0x2000;
- const UPDATE: u16 = 0x1000;
-
- let error_code = u16::from_be_bytes(error_code_slice.try_into().expect("len is 2"));
- error_code_ret = Some(error_code);
- error_packet_ret = Some(err_packet.failuremsg[2..].to_vec());
-
- let (debug_field, debug_field_size) = errors::get_onion_debug_field(error_code);
-
- // indicate that payment parameter has failed and no need to
- // update Route object
- let payment_failed = match error_code & 0xff {
- 15|16|17|18|19|23 => true,
- _ => false,
- } && is_from_final_node; // PERM bit observed below even if this error is from the intermediate nodes
-
- let mut network_update = None;
- let mut short_channel_id = None;
-
- if error_code & BADONION == BADONION {
- // If the error code has the BADONION bit set, always blame the channel
- // from the node "originating" the error to its next hop. The
- // "originator" is ultimately actually claiming that its counterparty
- // is the one who is failing the HTLC.
- // If the "originator" here isn't lying we should really mark the
- // next-hop node as failed entirely, but we can't be confident in that,
- // as it would allow any node to get us to completely ban one of its
- // counterparties. Instead, we simply remove the channel in question.
+pub(super) fn process_onion_failure<T: secp256k1::Signing, L: Deref>(
+ secp_ctx: &Secp256k1<T>, logger: &L, htlc_source: &HTLCSource, mut packet_decrypted: Vec<u8>
+) -> DecodedOnionFailure where L::Target: Logger {
+ let (path, session_priv, first_hop_htlc_msat) = if let &HTLCSource::OutboundRoute {
+ ref path, ref session_priv, ref first_hop_htlc_msat, ..
+ } = htlc_source {
+ (path, session_priv, first_hop_htlc_msat)
+ } else { unreachable!() };
+
+ // Learnings from the HTLC failure to inform future payment retries and scoring.
+ struct FailureLearnings {
+ network_update: Option<NetworkUpdate>,
+ short_channel_id: Option<u64>,
+ payment_failed_permanently: bool,
+ }
+ let mut res: Option<FailureLearnings> = None;
+ let mut htlc_msat = *first_hop_htlc_msat;
+ let mut error_code_ret = None;
+ let mut error_packet_ret = None;
+ let mut is_from_final_node = false;
+
+ const BADONION: u16 = 0x8000;
+ const PERM: u16 = 0x4000;
+ const NODE: u16 = 0x2000;
+ const UPDATE: u16 = 0x1000;
+
+ // Handle packed channel/node updates for passing back for the route handler
+ construct_onion_keys_callback(secp_ctx, &path, session_priv,
+ |shared_secret, _, _, route_hop_opt, route_hop_idx|
+ {
+ if res.is_some() { return; }
+
+ let route_hop = match route_hop_opt {
+ Some(hop) => hop,
+ None => {
+ // Got an error from within a blinded route.
+ error_code_ret = Some(BADONION | PERM | 24); // invalid_onion_blinding
+ error_packet_ret = Some(vec![0; 32]);
+ res = Some(FailureLearnings {
+ network_update: None, short_channel_id: None, payment_failed_permanently: false
+ });
+ return
+ },
+ };
+
+ // The failing hop includes either the inbound channel to the recipient or the outbound channel
+ // from the current hop (i.e., the next hop's inbound channel).
+ let num_blinded_hops = path.blinded_tail.as_ref().map_or(0, |bt| bt.hops.len());
+ // For 1-hop blinded paths, the final `path.hops` entry is the recipient.
+ is_from_final_node = route_hop_idx + 1 == path.hops.len() && num_blinded_hops <= 1;
+ let failing_route_hop = if is_from_final_node { route_hop } else {
+ match path.hops.get(route_hop_idx + 1) {
+ Some(hop) => hop,
+ None => {
+ // The failing hop is within a multi-hop blinded path.
+ error_code_ret = Some(BADONION | PERM | 24); // invalid_onion_blinding
+ error_packet_ret = Some(vec![0; 32]);
+ res = Some(FailureLearnings {
+ network_update: None, short_channel_id: None, payment_failed_permanently: false
+ });
+ return
+ }
+ }
+ };
+
+ let amt_to_forward = htlc_msat - route_hop.fee_msat;
+ htlc_msat = amt_to_forward;
+
+ let ammag = gen_ammag_from_shared_secret(shared_secret.as_ref());
+
+ let mut decryption_tmp = Vec::with_capacity(packet_decrypted.len());
+ decryption_tmp.resize(packet_decrypted.len(), 0);
+ let mut chacha = ChaCha20::new(&ammag, &[0u8; 8]);
+ chacha.process(&packet_decrypted, &mut decryption_tmp[..]);
+ packet_decrypted = decryption_tmp;
+
+ let err_packet = match msgs::DecodedOnionErrorPacket::read(&mut Cursor::new(&packet_decrypted)) {
+ Ok(p) => p,
+ Err(_) => return
+ };
+ let um = gen_um_from_shared_secret(shared_secret.as_ref());
+ let mut hmac = HmacEngine::<Sha256>::new(&um);
+ hmac.input(&err_packet.encode()[32..]);
+
+ if !fixed_time_eq(&Hmac::from_engine(hmac).into_inner(), &err_packet.hmac) { return }
+ let error_code_slice = match err_packet.failuremsg.get(0..2) {
+ Some(s) => s,
+ None => {
+ // Useless packet that we can't use but it passed HMAC, so it definitely came from the peer
+ // in question
+ let network_update = Some(NetworkUpdate::NodeFailure {
+ node_id: route_hop.pubkey,
+ is_permanent: true,
+ });
+ let short_channel_id = Some(route_hop.short_channel_id);
+ res = Some(FailureLearnings {
+ network_update, short_channel_id, payment_failed_permanently: is_from_final_node
+ });
+ return
+ }
+ };
+
+ let error_code = u16::from_be_bytes(error_code_slice.try_into().expect("len is 2"));
+ error_code_ret = Some(error_code);
+ error_packet_ret = Some(err_packet.failuremsg[2..].to_vec());
+
+ let (debug_field, debug_field_size) = errors::get_onion_debug_field(error_code);
+
+ // indicate that payment parameter has failed and no need to update Route object
+ let payment_failed = match error_code & 0xff {
+ 15|16|17|18|19|23 => true,
+ _ => false,
+ } && is_from_final_node; // PERM bit observed below even if this error is from the intermediate nodes
+
+ let mut network_update = None;
+ let mut short_channel_id = None;
+
+ if error_code & BADONION == BADONION {
+ // If the error code has the BADONION bit set, always blame the channel from the node
+ // "originating" the error to its next hop. The "originator" is ultimately actually claiming
+ // that its counterparty is the one who is failing the HTLC.
+ // If the "originator" here isn't lying we should really mark the next-hop node as failed
+ // entirely, but we can't be confident in that, as it would allow any node to get us to
+ // completely ban one of its counterparties. Instead, we simply remove the channel in
+ // question.
+ network_update = Some(NetworkUpdate::ChannelFailure {
+ short_channel_id: failing_route_hop.short_channel_id,
+ is_permanent: true,
+ });
+ } else if error_code & NODE == NODE {
+ let is_permanent = error_code & PERM == PERM;
+ network_update = Some(NetworkUpdate::NodeFailure { node_id: route_hop.pubkey, is_permanent });
+ short_channel_id = Some(route_hop.short_channel_id);
+ } else if error_code & PERM == PERM {
+ if !payment_failed {
+ network_update = Some(NetworkUpdate::ChannelFailure {
+ short_channel_id: failing_route_hop.short_channel_id,
+ is_permanent: true,
+ });
+ short_channel_id = Some(failing_route_hop.short_channel_id);
+ }
+ } else if error_code & UPDATE == UPDATE {
+ if let Some(update_len_slice) = err_packet.failuremsg.get(debug_field_size+2..debug_field_size+4) {
+ let update_len = u16::from_be_bytes(update_len_slice.try_into().expect("len is 2")) as usize;
+ if let Some(mut update_slice) = err_packet.failuremsg.get(debug_field_size + 4..debug_field_size + 4 + update_len) {
+ // Historically, the BOLTs were unclear if the message type
+ // bytes should be included here or not. The BOLTs have now
+ // been updated to indicate that they *are* included, but many
+ // nodes still send messages without the type bytes, so we
+ // support both here.
+ // TODO: Switch to hard require the type prefix, as the current
+ // permissiveness introduces the (although small) possibility
+ // that we fail to decode legitimate channel updates that
+ // happen to start with ChannelUpdate::TYPE, i.e., [0x01, 0x02].
+ if update_slice.len() > 2 && update_slice[0..2] == msgs::ChannelUpdate::TYPE.to_be_bytes() {
+ update_slice = &update_slice[2..];
+ } else {
+ log_trace!(logger, "Failure provided features a channel update without type prefix. Deprecated, but allowing for now.");
+ }
+ let update_opt = msgs::ChannelUpdate::read(&mut Cursor::new(&update_slice));
+ if update_opt.is_ok() || update_slice.is_empty() {
+ // if channel_update should NOT have caused the failure:
+ // MAY treat the channel_update as invalid.
+ let is_chan_update_invalid = match error_code & 0xff {
+ 7 => false,
+ 11 => update_opt.is_ok() &&
+ amt_to_forward >
+ update_opt.as_ref().unwrap().contents.htlc_minimum_msat,
+ 12 => update_opt.is_ok() && amt_to_forward
+ .checked_mul(update_opt.as_ref().unwrap()
+ .contents.fee_proportional_millionths as u64)
+ .map(|prop_fee| prop_fee / 1_000_000)
+ .and_then(|prop_fee| prop_fee.checked_add(
+ update_opt.as_ref().unwrap().contents.fee_base_msat as u64))
+ .map(|fee_msats| route_hop.fee_msat >= fee_msats)
+ .unwrap_or(false),
+ 13 => update_opt.is_ok() &&
+ route_hop.cltv_expiry_delta as u16 >=
+ update_opt.as_ref().unwrap().contents.cltv_expiry_delta,
+ 14 => false, // expiry_too_soon; always valid?
+ 20 => update_opt.as_ref().unwrap().contents.flags & 2 == 0,
+ _ => false, // unknown error code; take channel_update as valid
+ };
+ if is_chan_update_invalid {
+ // This probably indicates the node which forwarded
+ // to the node in question corrupted something.
network_update = Some(NetworkUpdate::ChannelFailure {
- short_channel_id: failing_route_hop.short_channel_id,
+ short_channel_id: route_hop.short_channel_id,
is_permanent: true,
});
- } else if error_code & NODE == NODE {
- let is_permanent = error_code & PERM == PERM;
- network_update = Some(NetworkUpdate::NodeFailure { node_id: route_hop.pubkey, is_permanent });
- short_channel_id = Some(route_hop.short_channel_id);
- } else if error_code & PERM == PERM {
- if !payment_failed {
+ } else {
+ if let Ok(chan_update) = update_opt {
+ // Make sure the ChannelUpdate contains the expected
+ // short channel id.
+ if failing_route_hop.short_channel_id == chan_update.contents.short_channel_id {
+ short_channel_id = Some(failing_route_hop.short_channel_id);
+ } else {
+ log_info!(logger, "Node provided a channel_update for which it was not authoritative, ignoring.");
+ }
+ network_update = Some(NetworkUpdate::ChannelUpdateMessage {
+ msg: chan_update,
+ })
+ } else {
+ // The node in question intentionally encoded a 0-length channel update. This is
+ // likely due to https://github.com/ElementsProject/lightning/issues/6200.
+ short_channel_id = Some(failing_route_hop.short_channel_id);
network_update = Some(NetworkUpdate::ChannelFailure {
short_channel_id: failing_route_hop.short_channel_id,
- is_permanent: true,
- });
- short_channel_id = Some(failing_route_hop.short_channel_id);
- }
- } else if error_code & UPDATE == UPDATE {
- if let Some(update_len_slice) = err_packet.failuremsg.get(debug_field_size+2..debug_field_size+4) {
- let update_len = u16::from_be_bytes(update_len_slice.try_into().expect("len is 2")) as usize;
- if let Some(mut update_slice) = err_packet.failuremsg.get(debug_field_size + 4..debug_field_size + 4 + update_len) {
- // Historically, the BOLTs were unclear if the message type
- // bytes should be included here or not. The BOLTs have now
- // been updated to indicate that they *are* included, but many
- // nodes still send messages without the type bytes, so we
- // support both here.
- // TODO: Switch to hard require the type prefix, as the current
- // permissiveness introduces the (although small) possibility
- // that we fail to decode legitimate channel updates that
- // happen to start with ChannelUpdate::TYPE, i.e., [0x01, 0x02].
- if update_slice.len() > 2 && update_slice[0..2] == msgs::ChannelUpdate::TYPE.to_be_bytes() {
- update_slice = &update_slice[2..];
- } else {
- log_trace!(logger, "Failure provided features a channel update without type prefix. Deprecated, but allowing for now.");
- }
- let update_opt = msgs::ChannelUpdate::read(&mut Cursor::new(&update_slice));
- if update_opt.is_ok() || update_slice.is_empty() {
- // if channel_update should NOT have caused the failure:
- // MAY treat the channel_update as invalid.
- let is_chan_update_invalid = match error_code & 0xff {
- 7 => false,
- 11 => update_opt.is_ok() &&
- amt_to_forward >
- update_opt.as_ref().unwrap().contents.htlc_minimum_msat,
- 12 => update_opt.is_ok() && amt_to_forward
- .checked_mul(update_opt.as_ref().unwrap()
- .contents.fee_proportional_millionths as u64)
- .map(|prop_fee| prop_fee / 1_000_000)
- .and_then(|prop_fee| prop_fee.checked_add(
- update_opt.as_ref().unwrap().contents.fee_base_msat as u64))
- .map(|fee_msats| route_hop.fee_msat >= fee_msats)
- .unwrap_or(false),
- 13 => update_opt.is_ok() &&
- route_hop.cltv_expiry_delta as u16 >=
- update_opt.as_ref().unwrap().contents.cltv_expiry_delta,
- 14 => false, // expiry_too_soon; always valid?
- 20 => update_opt.as_ref().unwrap().contents.flags & 2 == 0,
- _ => false, // unknown error code; take channel_update as valid
- };
- if is_chan_update_invalid {
- // This probably indicates the node which forwarded
- // to the node in question corrupted something.
- network_update = Some(NetworkUpdate::ChannelFailure {
- short_channel_id: route_hop.short_channel_id,
- is_permanent: true,
- });
- } else {
- if let Ok(chan_update) = update_opt {
- // Make sure the ChannelUpdate contains the expected
- // short channel id.
- if failing_route_hop.short_channel_id == chan_update.contents.short_channel_id {
- short_channel_id = Some(failing_route_hop.short_channel_id);
- } else {
- log_info!(logger, "Node provided a channel_update for which it was not authoritative, ignoring.");
- }
- network_update = Some(NetworkUpdate::ChannelUpdateMessage {
- msg: chan_update,
- })
- } else {
- network_update = Some(NetworkUpdate::ChannelFailure {
- short_channel_id: route_hop.short_channel_id,
- is_permanent: false,
- });
- }
- };
- } else {
- // If the channel_update had a non-zero length (i.e. was
- // present) but we couldn't read it, treat it as a total
- // node failure.
- log_info!(logger,
- "Failed to read a channel_update of len {} in an onion",
- update_slice.len());
- }
- }
- }
- if network_update.is_none() {
- // They provided an UPDATE which was obviously bogus, not worth
- // trying to relay through them anymore.
- network_update = Some(NetworkUpdate::NodeFailure {
- node_id: route_hop.pubkey,
- is_permanent: true,
+ is_permanent: false,
});
}
- if short_channel_id.is_none() {
- short_channel_id = Some(route_hop.short_channel_id);
- }
- } else if payment_failed {
- // Only blame the hop when a value in the HTLC doesn't match the
- // corresponding value in the onion.
- short_channel_id = match error_code & 0xff {
- 18|19 => Some(route_hop.short_channel_id),
- _ => None,
- };
- } else {
- // We can't understand their error messages and they failed to
- // forward...they probably can't understand our forwards so its
- // really not worth trying any further.
- network_update = Some(NetworkUpdate::NodeFailure {
- node_id: route_hop.pubkey,
- is_permanent: true,
- });
- short_channel_id = Some(route_hop.short_channel_id);
- }
-
- res = Some((network_update, short_channel_id, !(error_code & PERM == PERM && is_from_final_node)));
-
- let (description, title) = errors::get_onion_error_description(error_code);
- if debug_field_size > 0 && err_packet.failuremsg.len() >= 4 + debug_field_size {
- log_info!(logger, "Onion Error[from {}: {}({:#x}) {}({})] {}", route_hop.pubkey, title, error_code, debug_field, log_bytes!(&err_packet.failuremsg[4..4+debug_field_size]), description);
- }
- else {
- log_info!(logger, "Onion Error[from {}: {}({:#x})] {}", route_hop.pubkey, title, error_code, description);
- }
+ };
} else {
- // Useless packet that we can't use but it passed HMAC, so it
- // definitely came from the peer in question
- let network_update = Some(NetworkUpdate::NodeFailure {
- node_id: route_hop.pubkey,
- is_permanent: true,
- });
- let short_channel_id = Some(route_hop.short_channel_id);
- res = Some((network_update, short_channel_id, !is_from_final_node));
+ // If the channel_update had a non-zero length (i.e. was
+ // present) but we couldn't read it, treat it as a total
+ // node failure.
+ log_info!(logger,
+ "Failed to read a channel_update of len {} in an onion",
+ update_slice.len());
}
}
}
- }).expect("Route that we sent via spontaneously grew invalid keys in the middle of it?");
- if let Some((channel_update, short_channel_id, payment_retryable)) = res {
- (channel_update, short_channel_id, payment_retryable, error_code_ret, error_packet_ret)
+ if network_update.is_none() {
+ // They provided an UPDATE which was obviously bogus, not worth
+ // trying to relay through them anymore.
+ network_update = Some(NetworkUpdate::NodeFailure {
+ node_id: route_hop.pubkey,
+ is_permanent: true,
+ });
+ }
+ if short_channel_id.is_none() {
+ short_channel_id = Some(route_hop.short_channel_id);
+ }
+ } else if payment_failed {
+ // Only blame the hop when a value in the HTLC doesn't match the corresponding value in the
+ // onion.
+ short_channel_id = match error_code & 0xff {
+ 18|19 => Some(route_hop.short_channel_id),
+ _ => None,
+ };
} else {
- // only not set either packet unparseable or hmac does not match with any
- // payment not retryable only when garbage is from the final node
- (None, None, !is_from_final_node, None, None)
+ // We can't understand their error messages and they failed to forward...they probably can't
+ // understand our forwards so it's really not worth trying any further.
+ network_update = Some(NetworkUpdate::NodeFailure {
+ node_id: route_hop.pubkey,
+ is_permanent: true,
+ });
+ short_channel_id = Some(route_hop.short_channel_id);
}
- } else { unreachable!(); }
+
+ res = Some(FailureLearnings {
+ network_update, short_channel_id,
+ payment_failed_permanently: error_code & PERM == PERM && is_from_final_node
+ });
+
+ let (description, title) = errors::get_onion_error_description(error_code);
+ if debug_field_size > 0 && err_packet.failuremsg.len() >= 4 + debug_field_size {
+ log_info!(logger, "Onion Error[from {}: {}({:#x}) {}({})] {}", route_hop.pubkey, title, error_code, debug_field, log_bytes!(&err_packet.failuremsg[4..4+debug_field_size]), description);
+ } else {
+ log_info!(logger, "Onion Error[from {}: {}({:#x})] {}", route_hop.pubkey, title, error_code, description);
+ }
+ }).expect("Route that we sent via spontaneously grew invalid keys in the middle of it?");
+ if let Some(FailureLearnings {
+ network_update, short_channel_id, payment_failed_permanently
+ }) = res {
+ DecodedOnionFailure {
+ network_update, short_channel_id, payment_failed_permanently,
+ #[cfg(test)]
+ onion_error_code: error_code_ret,
+ #[cfg(test)]
+ onion_error_data: error_packet_ret
+ }
+ } else {
+ // only not set either packet unparseable or hmac does not match with any
+ // payment not retryable only when garbage is from the final node
+ DecodedOnionFailure {
+ network_update: None, short_channel_id: None, payment_failed_permanently: is_from_final_node,
+ #[cfg(test)]
+ onion_error_code: None,
+ #[cfg(test)]
+ onion_error_data: None
+ }
+ }
}
#[derive(Clone)] // See Channel::revoke_and_ack for why, tl;dr: Rust bug
},
(1, Reason) => {
(0, failure_code, required),
- (2, data, vec_type),
+ (2, data, required_vec),
},
;);
pub(super) fn decode_onion_failure<T: secp256k1::Signing, L: Deref>(
&self, secp_ctx: &Secp256k1<T>, logger: &L, htlc_source: &HTLCSource
- ) -> (Option<NetworkUpdate>, Option<u64>, bool, Option<u16>, Option<Vec<u8>>)
- where L::Target: Logger {
+ ) -> DecodedOnionFailure where L::Target: Logger {
match self.0 {
HTLCFailReasonRepr::LightningError { ref err } => {
process_onion_failure(secp_ctx, logger, &htlc_source, err.data.clone())
},
+ #[allow(unused)]
HTLCFailReasonRepr::Reason { ref failure_code, ref data, .. } => {
// we get a fail_malformed_htlc from the first hop
// TODO: We'd like to generate a NetworkUpdate for temporary
// generally ignores its view of our own channels as we provide them via
// ChannelDetails.
if let &HTLCSource::OutboundRoute { ref path, .. } = htlc_source {
- (None, Some(path.hops[0].short_channel_id), true, Some(*failure_code), Some(data.clone()))
+ DecodedOnionFailure {
+ network_update: None,
+ payment_failed_permanently: false,
+ short_channel_id: Some(path.hops[0].short_channel_id),
+ #[cfg(test)]
+ onion_error_code: Some(*failure_code),
+ #[cfg(test)]
+ onion_error_data: Some(data.clone()),
+ }
} else { unreachable!(); }
}
}
pub(crate) enum Hop {
/// This onion payload was for us, not for forwarding to a next-hop. Contains information for
/// verifying the incoming payment.
- Receive(msgs::OnionHopData),
+ Receive(msgs::InboundOnionPayload),
/// This onion payload needs to be forwarded to a next-hop.
Forward {
/// Onion payload data used in forwarding the payment.
- next_hop_data: msgs::OnionHopData,
+ next_hop_data: msgs::InboundOnionPayload,
/// HMAC of the next hop's onion packet.
next_hop_hmac: [u8; 32],
/// Bytes of the onion packet we're forwarding.
},
}
-pub(crate) fn decode_next_payment_hop(shared_secret: [u8; 32], hop_data: &[u8], hmac_bytes: [u8; 32], payment_hash: PaymentHash) -> Result<Hop, OnionDecodeErr> {
- match decode_next_hop(shared_secret, hop_data, hmac_bytes, Some(payment_hash), ()) {
+pub(crate) fn decode_next_payment_hop<NS: Deref>(
+ shared_secret: [u8; 32], hop_data: &[u8], hmac_bytes: [u8; 32], payment_hash: PaymentHash,
+ node_signer: &NS,
+) -> Result<Hop, OnionDecodeErr> where NS::Target: NodeSigner {
+ match decode_next_hop(shared_secret, hop_data, hmac_bytes, Some(payment_hash), node_signer) {
Ok((next_hop_data, None)) => Ok(Hop::Receive(next_hop_data)),
Ok((next_hop_data, Some((next_hop_hmac, FixedSizeOnionPacket(new_packet_bytes))))) => {
Ok(Hop::Forward {
RouteHop {
pubkey: PublicKey::from_slice(&hex::decode("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619").unwrap()[..]).unwrap(),
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
- short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0 // We fill in the payloads manually instead of generating them from RouteHops.
+ short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0, maybe_announced_channel: true, // We fill in the payloads manually instead of generating them from RouteHops.
},
RouteHop {
pubkey: PublicKey::from_slice(&hex::decode("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c").unwrap()[..]).unwrap(),
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
- short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0 // We fill in the payloads manually instead of generating them from RouteHops.
+ short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0, maybe_announced_channel: true, // We fill in the payloads manually instead of generating them from RouteHops.
},
RouteHop {
pubkey: PublicKey::from_slice(&hex::decode("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007").unwrap()[..]).unwrap(),
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
- short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0 // We fill in the payloads manually instead of generating them from RouteHops.
+ short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0, maybe_announced_channel: true, // We fill in the payloads manually instead of generating them from RouteHops.
},
RouteHop {
pubkey: PublicKey::from_slice(&hex::decode("032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991").unwrap()[..]).unwrap(),
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
- short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0 // We fill in the payloads manually instead of generating them from RouteHops.
+ short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0, maybe_announced_channel: true, // We fill in the payloads manually instead of generating them from RouteHops.
},
RouteHop {
pubkey: PublicKey::from_slice(&hex::decode("02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145").unwrap()[..]).unwrap(),
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
- short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0 // We fill in the payloads manually instead of generating them from RouteHops.
+ short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0, maybe_announced_channel: true, // We fill in the payloads manually instead of generating them from RouteHops.
},
], blinded_tail: None }],
- payment_params: None,
+ route_params: None,
};
let onion_keys = super::construct_onion_keys(&secp_ctx, &route.paths[0], &get_test_session_key()).unwrap();
// with raw hex instead of our in-memory enums, as the payloads contains custom types, and
// we have no way of representing that with our enums.
let payloads = vec!(
- RawOnionHopData::new(msgs::OnionHopData {
- format: msgs::OnionHopDataFormat::NonFinalNode {
- short_channel_id: 1,
- },
+ RawOnionHopData::new(msgs::OutboundOnionPayload::Forward {
+ short_channel_id: 1,
amt_to_forward: 15000,
outgoing_cltv_value: 1500,
}),
RawOnionHopData {
data: hex::decode("52020236b00402057806080000000000000002fd02013c0102030405060708090a0b0c0d0e0f0102030405060708090a0b0c0d0e0f0102030405060708090a0b0c0d0e0f0102030405060708090a0b0c0d0e0f").unwrap(),
},
- RawOnionHopData::new(msgs::OnionHopData {
- format: msgs::OnionHopDataFormat::NonFinalNode {
- short_channel_id: 3,
- },
+ RawOnionHopData::new(msgs::OutboundOnionPayload::Forward {
+ short_channel_id: 3,
amt_to_forward: 12500,
outgoing_cltv_value: 1250,
}),
- RawOnionHopData::new(msgs::OnionHopData {
- format: msgs::OnionHopDataFormat::NonFinalNode {
- short_channel_id: 4,
- },
+ RawOnionHopData::new(msgs::OutboundOnionPayload::Forward {
+ short_channel_id: 4,
amt_to_forward: 10000,
outgoing_cltv_value: 1000,
}),
data: Vec<u8>
}
impl RawOnionHopData {
- fn new(orig: msgs::OnionHopData) -> Self {
+ fn new(orig: msgs::OutboundOnionPayload) -> Self {
Self { data: orig.encode() }
}
}
use crate::sign::{EntropySource, NodeSigner, Recipient};
use crate::events::{self, PaymentFailureReason};
use crate::ln::{PaymentHash, PaymentPreimage, PaymentSecret};
-use crate::ln::channelmanager::{ChannelDetails, EventCompletionAction, HTLCSource, IDEMPOTENCY_TIMEOUT_TICKS, PaymentId};
-use crate::ln::onion_utils::HTLCFailReason;
+use crate::ln::channelmanager::{ChannelDetails, EventCompletionAction, HTLCSource, PaymentId};
+use crate::ln::onion_utils::{DecodedOnionFailure, HTLCFailReason};
+use crate::offers::invoice::Bolt12Invoice;
use crate::routing::router::{InFlightHtlcs, Path, PaymentParameters, Route, RouteParameters, Router};
use crate::util::errors::APIError;
use crate::util::logger::Logger;
use crate::prelude::*;
use crate::sync::Mutex;
+/// The number of ticks of [`ChannelManager::timer_tick_occurred`] until we time-out the idempotency
+/// of payments by [`PaymentId`]. See [`OutboundPayments::remove_stale_payments`].
+///
+/// [`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 {
Legacy {
session_privs: HashSet<[u8; 32]>,
},
+ AwaitingInvoice {
+ timer_ticks_without_response: u8,
+ retry_strategy: Retry,
+ max_total_routing_fee_msat: Option<u64>,
+ },
+ InvoiceReceived {
+ payment_hash: PaymentHash,
+ retry_strategy: Retry,
+ // Note this field is currently just replicated from AwaitingInvoice but not actually
+ // used anywhere.
+ max_total_routing_fee_msat: Option<u64>,
+ },
Retryable {
retry_strategy: Option<Retry>,
attempts: PaymentAttempts,
payment_secret: Option<PaymentSecret>,
payment_metadata: Option<Vec<u8>>,
keysend_preimage: Option<PaymentPreimage>,
+ custom_tlvs: Vec<(u64, Vec<u8>)>,
pending_amt_msat: u64,
/// Used to track the fee paid. Only present if the payment was serialized on 0.0.103+.
pending_fee_msat: Option<u64>,
total_msat: u64,
/// Our best known block height at the time this payment was initiated.
starting_block_height: u32,
+ remaining_max_total_routing_fee_msat: Option<u64>,
},
/// When a pending payment is fulfilled, we continue tracking it until all pending HTLCs have
/// been resolved. This ensures we don't look up pending payments in ChannelMonitors on restart
params.previously_failed_channels.push(scid);
}
}
+ fn is_awaiting_invoice(&self) -> bool {
+ match self {
+ PendingOutboundPayment::AwaitingInvoice { .. } => true,
+ _ => false,
+ }
+ }
pub(super) fn is_fulfilled(&self) -> bool {
match self {
PendingOutboundPayment::Fulfilled { .. } => true,
fn payment_hash(&self) -> Option<PaymentHash> {
match self {
PendingOutboundPayment::Legacy { .. } => None,
+ PendingOutboundPayment::AwaitingInvoice { .. } => None,
+ PendingOutboundPayment::InvoiceReceived { payment_hash, .. } => Some(*payment_hash),
PendingOutboundPayment::Retryable { payment_hash, .. } => Some(*payment_hash),
PendingOutboundPayment::Fulfilled { payment_hash, .. } => *payment_hash,
PendingOutboundPayment::Abandoned { payment_hash, .. } => Some(*payment_hash),
PendingOutboundPayment::Legacy { session_privs } |
PendingOutboundPayment::Retryable { session_privs, .. } |
PendingOutboundPayment::Fulfilled { session_privs, .. } |
- PendingOutboundPayment::Abandoned { session_privs, .. }
- => session_privs,
+ PendingOutboundPayment::Abandoned { session_privs, .. } => session_privs,
+ PendingOutboundPayment::AwaitingInvoice { .. } |
+ PendingOutboundPayment::InvoiceReceived { .. } => { debug_assert!(false); return; },
});
let payment_hash = self.payment_hash();
*self = PendingOutboundPayment::Fulfilled { session_privs, payment_hash, timer_ticks_without_htlcs: 0 };
payment_hash: *payment_hash,
reason: Some(reason)
};
+ } else if let PendingOutboundPayment::InvoiceReceived { payment_hash, .. } = self {
+ *self = PendingOutboundPayment::Abandoned {
+ session_privs: HashSet::new(),
+ payment_hash: *payment_hash,
+ reason: Some(reason)
+ };
}
}
PendingOutboundPayment::Fulfilled { session_privs, .. } |
PendingOutboundPayment::Abandoned { session_privs, .. } => {
session_privs.remove(session_priv)
- }
+ },
+ PendingOutboundPayment::AwaitingInvoice { .. } |
+ PendingOutboundPayment::InvoiceReceived { .. } => { debug_assert!(false); false },
};
if remove_res {
- if let PendingOutboundPayment::Retryable { ref mut pending_amt_msat, ref mut pending_fee_msat, .. } = self {
- let path = path.expect("Fulfilling a payment should always come with a path");
+ if let PendingOutboundPayment::Retryable {
+ ref mut pending_amt_msat, ref mut pending_fee_msat,
+ ref mut remaining_max_total_routing_fee_msat, ..
+ } = self {
+ let path = path.expect("Removing a failed payment should always come with a path");
*pending_amt_msat -= path.final_value_msat();
+ let path_fee_msat = path.fee_msat();
if let Some(fee_msat) = pending_fee_msat.as_mut() {
- *fee_msat -= path.fee_msat();
+ *fee_msat -= path_fee_msat;
+ }
+
+ if let Some(max_total_routing_fee_msat) = remaining_max_total_routing_fee_msat.as_mut() {
+ *max_total_routing_fee_msat = max_total_routing_fee_msat.saturating_add(path_fee_msat);
}
}
}
PendingOutboundPayment::Legacy { session_privs } |
PendingOutboundPayment::Retryable { session_privs, .. } => {
session_privs.insert(session_priv)
- }
+ },
+ PendingOutboundPayment::AwaitingInvoice { .. } |
+ PendingOutboundPayment::InvoiceReceived { .. } => { debug_assert!(false); false },
PendingOutboundPayment::Fulfilled { .. } => false,
PendingOutboundPayment::Abandoned { .. } => false,
};
if insert_res {
- if let PendingOutboundPayment::Retryable { ref mut pending_amt_msat, ref mut pending_fee_msat, .. } = self {
- *pending_amt_msat += path.final_value_msat();
- if let Some(fee_msat) = pending_fee_msat.as_mut() {
- *fee_msat += path.fee_msat();
- }
+ if let PendingOutboundPayment::Retryable {
+ ref mut pending_amt_msat, ref mut pending_fee_msat,
+ ref mut remaining_max_total_routing_fee_msat, ..
+ } = self {
+ *pending_amt_msat += path.final_value_msat();
+ let path_fee_msat = path.fee_msat();
+ if let Some(fee_msat) = pending_fee_msat.as_mut() {
+ *fee_msat += path_fee_msat;
+ }
+
+ if let Some(max_total_routing_fee_msat) = remaining_max_total_routing_fee_msat.as_mut() {
+ *max_total_routing_fee_msat = max_total_routing_fee_msat.saturating_sub(path_fee_msat);
+ }
}
}
insert_res
PendingOutboundPayment::Fulfilled { session_privs, .. } |
PendingOutboundPayment::Abandoned { session_privs, .. } => {
session_privs.len()
- }
+ },
+ PendingOutboundPayment::AwaitingInvoice { .. } => 0,
+ PendingOutboundPayment::InvoiceReceived { .. } => 0,
}
}
}
/// Each attempt may be multiple HTLCs along multiple paths if the router decides to split up a
/// retry, and may retry multiple failed HTLCs at once if they failed around the same time and
/// were retried along a route from a single call to [`Router::find_route_with_id`].
- Attempts(usize),
+ Attempts(u32),
#[cfg(not(feature = "no-std"))]
/// Time elapsed before abandoning retries for a payment. At least one attempt at payment is made;
/// see [`PaymentParameters::expiry_time`] to avoid any attempt at payment after a specific time.
Timeout(core::time::Duration),
}
+#[cfg(feature = "no-std")]
+impl_writeable_tlv_based_enum!(Retry,
+ ;
+ (0, Attempts)
+);
+
+#[cfg(not(feature = "no-std"))]
+impl_writeable_tlv_based_enum!(Retry,
+ ;
+ (0, Attempts),
+ (2, Timeout)
+);
+
impl Retry {
pub(crate) fn is_retryable_now(&self, attempts: &PaymentAttempts) -> bool {
match (self, attempts) {
pub(crate) struct PaymentAttemptsUsingTime<T: Time> {
/// This count will be incremented only after the result of the attempt is known. When it's 0,
/// it means the result of the first attempt is not known yet.
- pub(crate) count: usize,
+ pub(crate) count: u32,
/// This field is only used when retry is `Retry::Timeout` which is only build with feature std
#[cfg(not(feature = "no-std"))]
first_attempted_at: T,
/// is in, see the description of individual enum states for more.
///
/// [`ChannelManager::send_payment_with_route`]: crate::ln::channelmanager::ChannelManager::send_payment_with_route
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, PartialEq, Eq)]
pub enum PaymentSendFailure {
/// A parameter which was passed to send_payment was invalid, preventing us from attempting to
/// send the payment at all.
},
}
+/// An error when attempting to pay a BOLT 12 invoice.
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub(super) enum Bolt12PaymentError {
+ /// The invoice was not requested.
+ UnexpectedInvoice,
+ /// Payment for an invoice with the corresponding [`PaymentId`] was already initiated.
+ DuplicateInvoice,
+}
+
+/// Indicates that we failed to send a payment probe. Further errors may be surfaced later via
+/// [`Event::ProbeFailed`].
+///
+/// [`Event::ProbeFailed`]: crate::events::Event::ProbeFailed
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum ProbeSendFailure {
+ /// We were unable to find a route to the destination.
+ RouteNotFound,
+ /// We failed to send the payment probes.
+ SendingFailed(PaymentSendFailure),
+}
+
/// Information which is provided, encrypted, to the payment recipient when sending HTLCs.
///
/// This should generally be constructed with data communicated to us from the recipient (via a
/// [`Self::payment_secret`] and while nearly all lightning senders support secrets, metadata
/// may not be supported as universally.
pub payment_metadata: Option<Vec<u8>>,
+ /// See [`Self::custom_tlvs`] for more info.
+ pub(super) custom_tlvs: Vec<(u64, Vec<u8>)>,
}
impl_writeable_tlv_based!(RecipientOnionFields, {
(0, payment_secret, option),
+ (1, custom_tlvs, optional_vec),
(2, payment_metadata, option),
});
/// set of onion fields for today's BOLT11 invoices - most nodes require a [`PaymentSecret`]
/// but do not require or provide any further data.
pub fn secret_only(payment_secret: PaymentSecret) -> Self {
- Self { payment_secret: Some(payment_secret), payment_metadata: None }
+ Self { payment_secret: Some(payment_secret), payment_metadata: None, custom_tlvs: Vec::new() }
}
/// Creates a new [`RecipientOnionFields`] with no fields. This generally does not create
/// [`ChannelManager::send_spontaneous_payment`]: super::channelmanager::ChannelManager::send_spontaneous_payment
/// [`RecipientOnionFields::secret_only`]: RecipientOnionFields::secret_only
pub fn spontaneous_empty() -> Self {
- Self { payment_secret: None, payment_metadata: None }
+ Self { payment_secret: None, payment_metadata: None, custom_tlvs: Vec::new() }
+ }
+
+ /// Creates a new [`RecipientOnionFields`] from an existing one, adding custom TLVs. Each
+ /// TLV is provided as a `(u64, Vec<u8>)` for the type number and serialized value
+ /// respectively. TLV type numbers must be unique and within the range
+ /// reserved for custom types, i.e. >= 2^16, otherwise this method will return `Err(())`.
+ ///
+ /// This method will also error for types in the experimental range which have been
+ /// standardized within the protocol, which only includes 5482373484 (keysend) for now.
+ ///
+ /// See [`Self::custom_tlvs`] for more info.
+ pub fn with_custom_tlvs(mut self, mut custom_tlvs: Vec<(u64, Vec<u8>)>) -> Result<Self, ()> {
+ custom_tlvs.sort_unstable_by_key(|(typ, _)| *typ);
+ let mut prev_type = None;
+ for (typ, _) in custom_tlvs.iter() {
+ if *typ < 1 << 16 { return Err(()); }
+ if *typ == 5482373484 { return Err(()); } // keysend
+ match prev_type {
+ Some(prev) if prev >= *typ => return Err(()),
+ _ => {},
+ }
+ prev_type = Some(*typ);
+ }
+ self.custom_tlvs = custom_tlvs;
+ Ok(self)
+ }
+
+ /// Gets the custom TLVs that will be sent or have been received.
+ ///
+ /// Custom TLVs allow sending extra application-specific data with a payment. They provide
+ /// additional flexibility on top of payment metadata, as while other implementations may
+ /// require `payment_metadata` to reflect metadata provided in an invoice, custom TLVs
+ /// do not have this restriction.
+ ///
+ /// Note that if this field is non-empty, it will contain strictly increasing TLVs, each
+ /// represented by a `(u64, Vec<u8>)` for its type number and serialized value respectively.
+ /// This is validated when setting this field using [`Self::with_custom_tlvs`].
+ pub fn custom_tlvs(&self) -> &Vec<(u64, Vec<u8>)> {
+ &self.custom_tlvs
}
/// When we have received some HTLC(s) towards an MPP payment, as we receive further HTLC(s) we
pub(super) fn check_merge(&mut self, further_htlc_fields: &mut Self) -> Result<(), ()> {
if self.payment_secret != further_htlc_fields.payment_secret { return Err(()); }
if self.payment_metadata != further_htlc_fields.payment_metadata { return Err(()); }
- // For custom TLVs we should just drop non-matching ones, but not reject the payment.
+
+ let tlvs = &mut self.custom_tlvs;
+ let further_tlvs = &mut further_htlc_fields.custom_tlvs;
+
+ let even_tlvs = tlvs.iter().filter(|(typ, _)| *typ % 2 == 0);
+ let further_even_tlvs = further_tlvs.iter().filter(|(typ, _)| *typ % 2 == 0);
+ if even_tlvs.ne(further_even_tlvs) { return Err(()) }
+
+ tlvs.retain(|tlv| further_tlvs.iter().any(|further_tlv| tlv == further_tlv));
+ further_tlvs.retain(|further_tlv| tlvs.iter().any(|tlv| tlv == further_tlv));
+
Ok(())
}
}
+/// Arguments for [`super::channelmanager::ChannelManager::send_payment_along_path`].
+pub(super) struct SendAlongPathArgs<'a> {
+ pub path: &'a Path,
+ pub payment_hash: &'a PaymentHash,
+ pub recipient_onion: RecipientOnionFields,
+ pub total_value: u64,
+ pub cur_height: u32,
+ pub payment_id: PaymentId,
+ pub keysend_preimage: &'a Option<PaymentPreimage>,
+ pub session_priv_bytes: [u8; 32],
+}
+
pub(super) struct OutboundPayments {
pub(super) pending_outbound_payments: Mutex<HashMap<PaymentId, PendingOutboundPayment>>,
pub(super) retry_lock: Mutex<()>,
NS::Target: NodeSigner,
L::Target: Logger,
IH: Fn() -> InFlightHtlcs,
- SP: Fn(&Path, &PaymentHash, RecipientOnionFields, u64, u32, PaymentId,
- &Option<PaymentPreimage>, [u8; 32]) -> Result<(), APIError>,
+ SP: Fn(SendAlongPathArgs) -> Result<(), APIError>,
{
self.send_payment_internal(payment_id, payment_hash, recipient_onion, None, retry_strategy,
route_params, router, first_hops, &compute_inflight_htlcs, entropy_source, node_signer,
where
ES::Target: EntropySource,
NS::Target: NodeSigner,
- F: Fn(&Path, &PaymentHash, RecipientOnionFields, u64, u32, PaymentId,
- &Option<PaymentPreimage>, [u8; 32]) -> Result<(), APIError>
+ F: Fn(SendAlongPathArgs) -> Result<(), APIError>
{
let onion_session_privs = self.add_new_pending_payment(payment_hash, recipient_onion.clone(), payment_id, None, route, None, None, entropy_source, best_block_height)?;
self.pay_route_internal(route, payment_hash, recipient_onion, None, payment_id, None,
NS::Target: NodeSigner,
L::Target: Logger,
IH: Fn() -> InFlightHtlcs,
- SP: Fn(&Path, &PaymentHash, RecipientOnionFields, u64, u32, PaymentId,
- &Option<PaymentPreimage>, [u8; 32]) -> Result<(), APIError>,
+ SP: Fn(SendAlongPathArgs) -> Result<(), APIError>,
{
let preimage = payment_preimage
.unwrap_or_else(|| PaymentPreimage(entropy_source.get_secure_random_bytes()));
where
ES::Target: EntropySource,
NS::Target: NodeSigner,
- F: Fn(&Path, &PaymentHash, RecipientOnionFields, u64, u32, PaymentId,
- &Option<PaymentPreimage>, [u8; 32]) -> Result<(), APIError>
+ F: Fn(SendAlongPathArgs) -> Result<(), APIError>,
{
let preimage = payment_preimage
.unwrap_or_else(|| PaymentPreimage(entropy_source.get_secure_random_bytes()));
}
}
+ #[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,
+ best_block_height: u32, logger: &L,
+ pending_events: &Mutex<VecDeque<(events::Event, Option<EventCompletionAction>)>>,
+ send_payment_along_path: SP,
+ ) -> Result<(), Bolt12PaymentError>
+ where
+ R::Target: Router,
+ ES::Target: EntropySource,
+ NS::Target: NodeSigner,
+ L::Target: Logger,
+ IH: Fn() -> InFlightHtlcs,
+ SP: Fn(SendAlongPathArgs) -> Result<(), APIError>,
+ {
+ let payment_hash = invoice.payment_hash();
+ let mut max_total_routing_fee_msat = None;
+ 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, .. } => {
+ max_total_routing_fee_msat = *max_total_fee;
+ *entry.into_mut() = PendingOutboundPayment::InvoiceReceived {
+ payment_hash,
+ retry_strategy: *retry_strategy,
+ max_total_routing_fee_msat,
+ };
+ },
+ _ => return Err(Bolt12PaymentError::DuplicateInvoice),
+ },
+ 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,
+ };
+
+ self.find_route_and_send_payment(
+ payment_hash, payment_id, route_params, router, first_hops, &inflight_htlcs,
+ entropy_source, node_signer, best_block_height, logger, pending_events,
+ &send_payment_along_path
+ );
+
+ Ok(())
+ }
+
pub(super) fn check_retry_payments<R: Deref, ES: Deref, NS: Deref, SP, IH, FH, L: Deref>(
&self, router: &R, first_hops: FH, inflight_htlcs: IH, entropy_source: &ES, node_signer: &NS,
best_block_height: u32,
R::Target: Router,
ES::Target: EntropySource,
NS::Target: NodeSigner,
- SP: Fn(&Path, &PaymentHash, RecipientOnionFields, u64, u32, PaymentId,
- &Option<PaymentPreimage>, [u8; 32]) -> Result<(), APIError>,
+ SP: Fn(SendAlongPathArgs) -> Result<(), APIError>,
IH: Fn() -> InFlightHtlcs,
FH: Fn() -> Vec<ChannelDetails>,
L::Target: Logger,
let mut retry_id_route_params = None;
for (pmt_id, pmt) in outbounds.iter_mut() {
if pmt.is_auto_retryable_now() {
- if let PendingOutboundPayment::Retryable { pending_amt_msat, total_msat, payment_params: Some(params), payment_hash, .. } = pmt {
+ if let PendingOutboundPayment::Retryable { pending_amt_msat, total_msat, payment_params: Some(params), payment_hash, remaining_max_total_routing_fee_msat, .. } = pmt {
if pending_amt_msat < total_msat {
retry_id_route_params = Some((*payment_hash, *pmt_id, RouteParameters {
final_value_msat: *total_msat - *pending_amt_msat,
payment_params: params.clone(),
+ max_total_routing_fee_msat: *remaining_max_total_routing_fee_msat,
}));
break
}
}
core::mem::drop(outbounds);
if let Some((payment_hash, payment_id, route_params)) = retry_id_route_params {
- self.retry_payment_internal(payment_hash, payment_id, route_params, router, first_hops(), &inflight_htlcs, entropy_source, node_signer, best_block_height, logger, pending_events, &send_payment_along_path)
+ self.find_route_and_send_payment(payment_hash, payment_id, route_params, router, first_hops(), &inflight_htlcs, entropy_source, node_signer, best_block_height, logger, pending_events, &send_payment_along_path)
} else { break }
}
let mut outbounds = self.pending_outbound_payments.lock().unwrap();
outbounds.retain(|pmt_id, pmt| {
let mut retain = true;
- if !pmt.is_auto_retryable_now() && pmt.remaining_parts() == 0 {
+ if !pmt.is_auto_retryable_now() && pmt.remaining_parts() == 0 && !pmt.is_awaiting_invoice() {
pmt.mark_abandoned(PaymentFailureReason::RetriesExhausted);
if let PendingOutboundPayment::Abandoned { payment_hash, reason, .. } = pmt {
pending_events.lock().unwrap().push_back((events::Event::PaymentFailed {
pub(super) fn needs_abandon(&self) -> bool {
let outbounds = self.pending_outbound_payments.lock().unwrap();
outbounds.iter().any(|(_, pmt)|
- !pmt.is_auto_retryable_now() && pmt.remaining_parts() == 0 && !pmt.is_fulfilled())
+ !pmt.is_auto_retryable_now() && pmt.remaining_parts() == 0 && !pmt.is_fulfilled() &&
+ !pmt.is_awaiting_invoice())
}
/// Errors immediately on [`RetryableSendFailure`] error conditions. Otherwise, further errors may
NS::Target: NodeSigner,
L::Target: Logger,
IH: Fn() -> InFlightHtlcs,
- SP: Fn(&Path, &PaymentHash, RecipientOnionFields, u64, u32, PaymentId,
- &Option<PaymentPreimage>, [u8; 32]) -> Result<(), APIError>
+ SP: Fn(SendAlongPathArgs) -> Result<(), APIError>,
{
#[cfg(feature = "std")] {
if has_expired(&route_params) {
+ log_error!(logger, "Payment with id {} and hash {} had expired before we started paying",
+ payment_id, payment_hash);
return Err(RetryableSendFailure::PaymentExpired)
}
}
let route = router.find_route_with_id(
&node_signer.get_node_id(Recipient::Node).unwrap(), &route_params,
- Some(&first_hops.iter().collect::<Vec<_>>()), &inflight_htlcs(),
+ Some(&first_hops.iter().collect::<Vec<_>>()), inflight_htlcs(),
payment_hash, payment_id,
- ).map_err(|_| RetryableSendFailure::RouteNotFound)?;
+ ).map_err(|_| {
+ log_error!(logger, "Failed to find route for payment with id {} and hash {}",
+ payment_id, payment_hash);
+ RetryableSendFailure::RouteNotFound
+ })?;
let onion_session_privs = self.add_new_pending_payment(payment_hash,
recipient_onion.clone(), payment_id, keysend_preimage, &route, Some(retry_strategy),
Some(route_params.payment_params.clone()), entropy_source, best_block_height)
- .map_err(|_| RetryableSendFailure::DuplicatePayment)?;
+ .map_err(|_| {
+ log_error!(logger, "Payment with id {} is already pending. New payment had payment hash {}",
+ payment_id, payment_hash);
+ RetryableSendFailure::DuplicatePayment
+ })?;
- let res = self.pay_route_internal(&route, payment_hash, recipient_onion, None, payment_id, None,
+ let res = self.pay_route_internal(&route, payment_hash, recipient_onion, keysend_preimage, payment_id, None,
onion_session_privs, node_signer, best_block_height, &send_payment_along_path);
- log_info!(logger, "Result sending payment with id {}: {:?}", log_bytes!(payment_id.0), res);
+ log_info!(logger, "Sending payment with id {} and hash {} returned {:?}",
+ payment_id, payment_hash, res);
if let Err(e) = res {
self.handle_pay_route_err(e, payment_id, payment_hash, route, route_params, router, first_hops, &inflight_htlcs, entropy_source, node_signer, best_block_height, logger, pending_events, &send_payment_along_path);
}
Ok(())
}
- fn retry_payment_internal<R: Deref, NS: Deref, ES: Deref, IH, SP, L: Deref>(
+ fn find_route_and_send_payment<R: Deref, NS: Deref, ES: Deref, IH, SP, L: Deref>(
&self, payment_hash: PaymentHash, payment_id: PaymentId, route_params: RouteParameters,
router: &R, first_hops: Vec<ChannelDetails>, inflight_htlcs: &IH, entropy_source: &ES,
node_signer: &NS, best_block_height: u32, logger: &L,
NS::Target: NodeSigner,
L::Target: Logger,
IH: Fn() -> InFlightHtlcs,
- SP: Fn(&Path, &PaymentHash, RecipientOnionFields, u64, u32, PaymentId,
- &Option<PaymentPreimage>, [u8; 32]) -> Result<(), APIError>
+ SP: Fn(SendAlongPathArgs) -> Result<(), APIError>,
{
#[cfg(feature = "std")] {
if has_expired(&route_params) {
- log_error!(logger, "Payment params expired on retry, abandoning payment {}", log_bytes!(payment_id.0));
+ log_error!(logger, "Payment params expired on retry, abandoning payment {}", &payment_id);
self.abandon_payment(payment_id, PaymentFailureReason::PaymentExpired, pending_events);
return
}
let route = match router.find_route_with_id(
&node_signer.get_node_id(Recipient::Node).unwrap(), &route_params,
- Some(&first_hops.iter().collect::<Vec<_>>()), &inflight_htlcs(),
+ Some(&first_hops.iter().collect::<Vec<_>>()), inflight_htlcs(),
payment_hash, payment_id,
) {
Ok(route) => route,
Err(e) => {
- log_error!(logger, "Failed to find a route on retry, abandoning payment {}: {:#?}", log_bytes!(payment_id.0), e);
+ log_error!(logger, "Failed to find a route on retry, abandoning payment {}: {:#?}", &payment_id, e);
self.abandon_payment(payment_id, PaymentFailureReason::RouteNotFound, pending_events);
return
}
}
}
- const RETRY_OVERFLOW_PERCENTAGE: u64 = 10;
- let mut onion_session_privs = Vec::with_capacity(route.paths.len());
- for _ in 0..route.paths.len() {
- onion_session_privs.push(entropy_source.get_secure_random_bytes());
- }
-
macro_rules! abandon_with_entry {
($payment: expr, $reason: expr) => {
$payment.get_mut().mark_abandoned($reason);
}
}
}
- let (total_msat, recipient_onion, keysend_preimage) = {
+ let (total_msat, recipient_onion, keysend_preimage, onion_session_privs) = {
let mut outbounds = self.pending_outbound_payments.lock().unwrap();
match outbounds.entry(payment_id) {
hash_map::Entry::Occupied(mut payment) => {
- let res = match payment.get() {
+ match payment.get() {
PendingOutboundPayment::Retryable {
- total_msat, keysend_preimage, payment_secret, payment_metadata, pending_amt_msat, ..
+ total_msat, keysend_preimage, payment_secret, payment_metadata,
+ custom_tlvs, pending_amt_msat, ..
} => {
+ const RETRY_OVERFLOW_PERCENTAGE: u64 = 10;
let retry_amt_msat = route.get_total_amount();
if retry_amt_msat + *pending_amt_msat > *total_msat * (100 + RETRY_OVERFLOW_PERCENTAGE) / 100 {
log_error!(logger, "retry_amt_msat of {} will put pending_amt_msat (currently: {}) more than 10% over total_payment_amt_msat of {}", retry_amt_msat, pending_amt_msat, total_msat);
abandon_with_entry!(payment, PaymentFailureReason::UnexpectedError);
return
}
- (*total_msat, RecipientOnionFields {
- payment_secret: *payment_secret,
- payment_metadata: payment_metadata.clone(),
- }, *keysend_preimage)
+
+ if !payment.get().is_retryable_now() {
+ log_error!(logger, "Retries exhausted for payment id {}", &payment_id);
+ abandon_with_entry!(payment, PaymentFailureReason::RetriesExhausted);
+ return
+ }
+
+ let total_msat = *total_msat;
+ let recipient_onion = RecipientOnionFields {
+ payment_secret: *payment_secret,
+ payment_metadata: payment_metadata.clone(),
+ custom_tlvs: custom_tlvs.clone(),
+ };
+ let keysend_preimage = *keysend_preimage;
+
+ let mut onion_session_privs = Vec::with_capacity(route.paths.len());
+ for _ in 0..route.paths.len() {
+ onion_session_privs.push(entropy_source.get_secure_random_bytes());
+ }
+
+ for (path, session_priv_bytes) in route.paths.iter().zip(onion_session_privs.iter()) {
+ assert!(payment.get_mut().insert(*session_priv_bytes, path));
+ }
+
+ payment.get_mut().increment_attempts();
+
+ (total_msat, recipient_onion, keysend_preimage, onion_session_privs)
},
PendingOutboundPayment::Legacy { .. } => {
log_error!(logger, "Unable to retry payments that were initially sent on LDK versions prior to 0.0.102");
return
},
+ PendingOutboundPayment::AwaitingInvoice { .. } => {
+ log_error!(logger, "Payment not yet sent");
+ return
+ },
+ PendingOutboundPayment::InvoiceReceived { payment_hash, retry_strategy, .. } => {
+ let total_amount = route_params.final_value_msat;
+ let recipient_onion = RecipientOnionFields {
+ payment_secret: None,
+ payment_metadata: None,
+ custom_tlvs: vec![],
+ };
+ let retry_strategy = Some(*retry_strategy);
+ let payment_params = Some(route_params.payment_params.clone());
+ let (retryable_payment, onion_session_privs) = self.create_pending_payment(
+ *payment_hash, recipient_onion.clone(), None, &route,
+ retry_strategy, payment_params, entropy_source, best_block_height
+ );
+ *payment.into_mut() = retryable_payment;
+ (total_amount, recipient_onion, None, onion_session_privs)
+ },
PendingOutboundPayment::Fulfilled { .. } => {
log_error!(logger, "Payment already completed");
return
log_error!(logger, "Payment already abandoned (with some HTLCs still pending)");
return
},
- };
- if !payment.get().is_retryable_now() {
- log_error!(logger, "Retries exhausted for payment id {}", log_bytes!(payment_id.0));
- abandon_with_entry!(payment, PaymentFailureReason::RetriesExhausted);
- return
}
- payment.get_mut().increment_attempts();
- for (path, session_priv_bytes) in route.paths.iter().zip(onion_session_privs.iter()) {
- assert!(payment.get_mut().insert(*session_priv_bytes, path));
- }
- res
},
hash_map::Entry::Vacant(_) => {
- log_error!(logger, "Payment with ID {} not found", log_bytes!(payment_id.0));
+ log_error!(logger, "Payment with ID {} not found", &payment_id);
return
}
}
let res = self.pay_route_internal(&route, payment_hash, recipient_onion, keysend_preimage,
payment_id, Some(total_msat), onion_session_privs, node_signer, best_block_height,
&send_payment_along_path);
- log_info!(logger, "Result retrying payment id {}: {:?}", log_bytes!(payment_id.0), res);
+ log_info!(logger, "Result retrying payment id {}: {:?}", &payment_id, res);
if let Err(e) = res {
self.handle_pay_route_err(e, payment_id, payment_hash, route, route_params, router, first_hops, inflight_htlcs, entropy_source, node_signer, best_block_height, logger, pending_events, send_payment_along_path);
}
NS::Target: NodeSigner,
L::Target: Logger,
IH: Fn() -> InFlightHtlcs,
- SP: Fn(&Path, &PaymentHash, RecipientOnionFields, u64, u32, PaymentId,
- &Option<PaymentPreimage>, [u8; 32]) -> Result<(), APIError>
+ SP: Fn(SendAlongPathArgs) -> Result<(), APIError>,
{
match err {
PaymentSendFailure::AllFailedResendSafe(errs) => {
Self::push_path_failed_evs_and_scids(payment_id, payment_hash, &mut route_params, route.paths, errs.into_iter().map(|e| Err(e)), logger, pending_events);
- self.retry_payment_internal(payment_hash, payment_id, route_params, router, first_hops, inflight_htlcs, entropy_source, node_signer, best_block_height, logger, pending_events, send_payment_along_path);
+ self.find_route_and_send_payment(payment_hash, payment_id, route_params, router, first_hops, inflight_htlcs, entropy_source, node_signer, best_block_height, logger, pending_events, send_payment_along_path);
},
PaymentSendFailure::PartialFailure { failed_paths_retry: Some(mut retry), results, .. } => {
Self::push_path_failed_evs_and_scids(payment_id, payment_hash, &mut retry, route.paths, results.into_iter(), logger, pending_events);
// Some paths were sent, even if we failed to send the full MPP value our recipient may
// misbehave and claim the funds, at which point we have to consider the payment sent, so
// return `Ok()` here, ignoring any retry errors.
- self.retry_payment_internal(payment_hash, payment_id, retry, router, first_hops, inflight_htlcs, entropy_source, node_signer, best_block_height, logger, pending_events, send_payment_along_path);
+ self.find_route_and_send_payment(payment_hash, payment_id, retry, router, first_hops, inflight_htlcs, entropy_source, node_signer, best_block_height, logger, pending_events, send_payment_along_path);
},
PaymentSendFailure::PartialFailure { failed_paths_retry: None, .. } => {
// This may happen if we send a payment and some paths fail, but only due to a temporary
where
ES::Target: EntropySource,
NS::Target: NodeSigner,
- F: Fn(&Path, &PaymentHash, RecipientOnionFields, u64, u32, PaymentId,
- &Option<PaymentPreimage>, [u8; 32]) -> Result<(), APIError>
+ F: Fn(SendAlongPathArgs) -> Result<(), APIError>,
{
let payment_id = PaymentId(entropy_source.get_secure_random_bytes());
+ let payment_secret = PaymentSecret(entropy_source.get_secure_random_bytes());
let payment_hash = probing_cookie_from_id(&payment_id, probing_cookie_secret);
}))
}
- let route = Route { paths: vec![path], payment_params: None };
+ let route = Route { paths: vec![path], route_params: None };
let onion_session_privs = self.add_new_pending_payment(payment_hash,
- RecipientOnionFields::spontaneous_empty(), payment_id, None, &route, None, None,
+ RecipientOnionFields::secret_only(payment_secret), payment_id, None, &route, None, None,
entropy_source, best_block_height)?;
match self.pay_route_internal(&route, payment_hash, RecipientOnionFields::spontaneous_empty(),
keysend_preimage: Option<PaymentPreimage>, route: &Route, retry_strategy: Option<Retry>,
payment_params: Option<PaymentParameters>, entropy_source: &ES, best_block_height: u32
) -> Result<Vec<[u8; 32]>, PaymentSendFailure> where ES::Target: EntropySource {
+ let mut pending_outbounds = self.pending_outbound_payments.lock().unwrap();
+ match pending_outbounds.entry(payment_id) {
+ hash_map::Entry::Occupied(_) => Err(PaymentSendFailure::DuplicatePayment),
+ hash_map::Entry::Vacant(entry) => {
+ let (payment, onion_session_privs) = self.create_pending_payment(
+ payment_hash, recipient_onion, keysend_preimage, route, retry_strategy,
+ payment_params, entropy_source, best_block_height
+ );
+ entry.insert(payment);
+ Ok(onion_session_privs)
+ },
+ }
+ }
+
+ fn create_pending_payment<ES: Deref>(
+ &self, payment_hash: PaymentHash, recipient_onion: RecipientOnionFields,
+ keysend_preimage: Option<PaymentPreimage>, route: &Route, retry_strategy: Option<Retry>,
+ payment_params: Option<PaymentParameters>, entropy_source: &ES, best_block_height: u32
+ ) -> (PendingOutboundPayment, Vec<[u8; 32]>)
+ where
+ ES::Target: EntropySource,
+ {
let mut onion_session_privs = Vec::with_capacity(route.paths.len());
for _ in 0..route.paths.len() {
onion_session_privs.push(entropy_source.get_secure_random_bytes());
}
+ let mut payment = PendingOutboundPayment::Retryable {
+ retry_strategy,
+ attempts: PaymentAttempts::new(),
+ payment_params,
+ session_privs: HashSet::new(),
+ pending_amt_msat: 0,
+ pending_fee_msat: Some(0),
+ payment_hash,
+ payment_secret: recipient_onion.payment_secret,
+ payment_metadata: recipient_onion.payment_metadata,
+ keysend_preimage,
+ custom_tlvs: recipient_onion.custom_tlvs,
+ starting_block_height: best_block_height,
+ total_msat: route.get_total_amount(),
+ remaining_max_total_routing_fee_msat:
+ route.route_params.as_ref().and_then(|p| p.max_total_routing_fee_msat),
+ };
+
+ for (path, session_priv_bytes) in route.paths.iter().zip(onion_session_privs.iter()) {
+ assert!(payment.insert(*session_priv_bytes, path));
+ }
+
+ (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>
+ ) -> Result<(), ()> {
let mut pending_outbounds = self.pending_outbound_payments.lock().unwrap();
match pending_outbounds.entry(payment_id) {
- hash_map::Entry::Occupied(_) => Err(PaymentSendFailure::DuplicatePayment),
+ hash_map::Entry::Occupied(_) => Err(()),
hash_map::Entry::Vacant(entry) => {
- let payment = entry.insert(PendingOutboundPayment::Retryable {
+ entry.insert(PendingOutboundPayment::AwaitingInvoice {
+ timer_ticks_without_response: 0,
retry_strategy,
- attempts: PaymentAttempts::new(),
- payment_params,
- session_privs: HashSet::new(),
- pending_amt_msat: 0,
- pending_fee_msat: Some(0),
- payment_hash,
- payment_secret: recipient_onion.payment_secret,
- payment_metadata: recipient_onion.payment_metadata,
- keysend_preimage,
- starting_block_height: best_block_height,
- total_msat: route.get_total_amount(),
+ max_total_routing_fee_msat,
});
- for (path, session_priv_bytes) in route.paths.iter().zip(onion_session_privs.iter()) {
- assert!(payment.insert(*session_priv_bytes, path));
- }
-
- Ok(onion_session_privs)
+ Ok(())
},
}
}
) -> Result<(), PaymentSendFailure>
where
NS::Target: NodeSigner,
- F: Fn(&Path, &PaymentHash, RecipientOnionFields, u64, u32, PaymentId,
- &Option<PaymentPreimage>, [u8; 32]) -> Result<(), APIError>
+ F: Fn(SendAlongPathArgs) -> Result<(), APIError>,
{
if route.paths.len() < 1 {
return Err(PaymentSendFailure::ParameterError(APIError::InvalidRoute{err: "There must be at least one path to send over".to_owned()}));
}
- if recipient_onion.payment_secret.is_none() && route.paths.len() > 1 {
+ if recipient_onion.payment_secret.is_none() && route.paths.len() > 1
+ && !route.paths.iter().any(|p| p.blinded_tail.is_some())
+ {
return Err(PaymentSendFailure::ParameterError(APIError::APIMisuseError{err: "Payment secret is required for multi-path payments".to_owned()}));
}
let mut total_value = 0;
path_errs.push(Err(APIError::InvalidRoute{err: "Path didn't go anywhere/had bogus size".to_owned()}));
continue 'path_check;
}
- if path.blinded_tail.is_some() {
- path_errs.push(Err(APIError::InvalidRoute{err: "Sending to blinded paths isn't supported yet".to_owned()}));
- continue 'path_check;
- }
let dest_hop_idx = if path.blinded_tail.is_some() && path.blinded_tail.as_ref().unwrap().hops.len() > 1 {
usize::max_value() } else { path.hops.len() - 1 };
for (idx, hop) in path.hops.iter().enumerate() {
let cur_height = best_block_height + 1;
let mut results = Vec::new();
debug_assert_eq!(route.paths.len(), onion_session_privs.len());
- for (path, session_priv) in route.paths.iter().zip(onion_session_privs.into_iter()) {
- let mut path_res = send_payment_along_path(&path, &payment_hash, recipient_onion.clone(),
- total_value, cur_height, payment_id, &keysend_preimage, session_priv);
+ for (path, session_priv_bytes) in route.paths.iter().zip(onion_session_privs.into_iter()) {
+ let mut path_res = send_payment_along_path(SendAlongPathArgs {
+ path: &path, payment_hash: &payment_hash, recipient_onion: recipient_onion.clone(),
+ total_value, cur_height, payment_id, keysend_preimage: &keysend_preimage, session_priv_bytes
+ });
match path_res {
Ok(_) => {},
Err(APIError::MonitorUpdateInProgress) => {
Err(_) => {
let mut pending_outbounds = self.pending_outbound_payments.lock().unwrap();
if let Some(payment) = pending_outbounds.get_mut(&payment_id) {
- let removed = payment.remove(&session_priv, Some(path));
+ let removed = payment.remove(&session_priv_bytes, Some(path));
debug_assert!(removed, "This can't happen as the payment has an entry for this path added by callers");
} else {
debug_assert!(false, "This can't happen as the payment was added by callers");
let mut has_ok = false;
let mut has_err = false;
let mut pending_amt_unsent = 0;
+ let mut total_ok_fees_msat = 0;
for (res, path) in results.iter().zip(route.paths.iter()) {
- if res.is_ok() { has_ok = true; }
+ if res.is_ok() {
+ has_ok = true;
+ total_ok_fees_msat += path.fee_msat();
+ }
if res.is_err() { has_err = true; }
if let &Err(APIError::MonitorUpdateInProgress) = res {
// MonitorUpdateInProgress is inherently unsafe to retry, so we call it a
// PartialFailure.
has_err = true;
has_ok = true;
+ total_ok_fees_msat += path.fee_msat();
} else if res.is_err() {
pending_amt_unsent += path.final_value_msat();
}
results,
payment_id,
failed_paths_retry: if pending_amt_unsent != 0 {
- if let Some(payment_params) = &route.payment_params {
- Some(RouteParameters {
- payment_params: payment_params.clone(),
- final_value_msat: pending_amt_unsent,
- })
+ if let Some(route_params) = &route.route_params {
+ let mut route_params = route_params.clone();
+ // We calculate the leftover fee budget we're allowed to spend by
+ // subtracting the used fee from the total fee budget.
+ route_params.max_total_routing_fee_msat = route_params
+ .max_total_routing_fee_msat.map(|m| m.saturating_sub(total_ok_fees_msat));
+ route_params.final_value_msat = pending_amt_unsent;
+
+ Some(route_params)
} else { None }
} else { None },
})
) -> Result<(), PaymentSendFailure>
where
NS::Target: NodeSigner,
- F: Fn(&Path, &PaymentHash, RecipientOnionFields, u64, u32, PaymentId,
- &Option<PaymentPreimage>, [u8; 32]) -> Result<(), APIError>
+ F: Fn(SendAlongPathArgs) -> Result<(), APIError>,
{
self.pay_route_internal(route, payment_hash, recipient_onion, keysend_preimage, payment_id,
recv_value_msat, onion_session_privs, node_signer, best_block_height,
pub(super) fn claim_htlc<L: Deref>(
&self, payment_id: PaymentId, payment_preimage: PaymentPreimage, session_priv: SecretKey,
- path: Path, from_onchain: bool,
+ path: Path, from_onchain: bool, ev_completion_action: EventCompletionAction,
pending_events: &Mutex<VecDeque<(events::Event, Option<EventCompletionAction>)>>,
logger: &L,
) where L::Target: Logger {
if let hash_map::Entry::Occupied(mut payment) = outbounds.entry(payment_id) {
if !payment.get().is_fulfilled() {
let payment_hash = PaymentHash(Sha256::hash(&payment_preimage.0).into_inner());
+ log_info!(logger, "Payment with id {} and hash {} sent!", payment_id, payment_hash);
let fee_paid_msat = payment.get().get_pending_fee_msat();
pending_events.push_back((events::Event::PaymentSent {
payment_id: Some(payment_id),
payment_preimage,
payment_hash,
fee_paid_msat,
- }, None));
+ }, Some(ev_completion_action.clone())));
payment.get_mut().mark_fulfilled();
}
payment_id,
payment_hash,
path,
- }, None));
+ }, Some(ev_completion_action)));
}
}
} else {
- log_trace!(logger, "Received duplicative fulfill for HTLC with payment_preimage {}", log_bytes!(payment_preimage.0));
+ log_trace!(logger, "Received duplicative fulfill for HTLC with payment_preimage {}", &payment_preimage);
}
}
}
}
- pub(super) fn remove_stale_resolved_payments(&self,
- pending_events: &Mutex<VecDeque<(events::Event, Option<EventCompletionAction>)>>)
+ pub(super) fn remove_stale_payments(
+ &self, pending_events: &Mutex<VecDeque<(events::Event, Option<EventCompletionAction>)>>)
{
- // 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
- // guarantees would be violated. Instead, we wait a few timer ticks to do the actual
- // 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.
let mut pending_outbound_payments = self.pending_outbound_payments.lock().unwrap();
- let pending_events = pending_events.lock().unwrap();
+ let mut pending_events = pending_events.lock().unwrap();
pending_outbound_payments.retain(|payment_id, 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
+ // guarantees would be violated. Instead, we wait a few timer ticks to do the actual
+ // 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 {
let mut no_remaining_entries = session_privs.is_empty();
if no_remaining_entries {
*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 {
+ pending_events.push_back(
+ (events::Event::InvoiceRequestFailed { payment_id: *payment_id }, None)
+ );
+ false
+ }
} else { true }
});
}
pending_events: &Mutex<VecDeque<(events::Event, Option<EventCompletionAction>)>>, logger: &L,
) -> bool where L::Target: Logger {
#[cfg(test)]
- let (network_update, short_channel_id, payment_retryable, onion_error_code, onion_error_data) = onion_error.decode_onion_failure(secp_ctx, logger, &source);
+ let DecodedOnionFailure {
+ network_update, short_channel_id, payment_failed_permanently, onion_error_code,
+ onion_error_data
+ } = onion_error.decode_onion_failure(secp_ctx, logger, &source);
#[cfg(not(test))]
- let (network_update, short_channel_id, payment_retryable, _, _) = onion_error.decode_onion_failure(secp_ctx, logger, &source);
+ let DecodedOnionFailure { network_update, short_channel_id, payment_failed_permanently } =
+ onion_error.decode_onion_failure(secp_ctx, logger, &source);
let payment_is_probe = payment_is_probe(payment_hash, &payment_id, probing_cookie_secret);
let mut session_priv_bytes = [0; 32];
let mut pending_retry_ev = false;
let attempts_remaining = if let hash_map::Entry::Occupied(mut payment) = outbounds.entry(*payment_id) {
if !payment.get_mut().remove(&session_priv_bytes, Some(&path)) {
- log_trace!(logger, "Received duplicative fail for HTLC with payment_hash {}", log_bytes!(payment_hash.0));
+ log_trace!(logger, "Received duplicative fail for HTLC with payment_hash {}", &payment_hash);
return false
}
if payment.get().is_fulfilled() {
- log_trace!(logger, "Received failure of HTLC with payment_hash {} after payment completion", log_bytes!(payment_hash.0));
+ log_trace!(logger, "Received failure of HTLC with payment_hash {} after payment completion", &payment_hash);
return false
}
let mut is_retryable_now = payment.get().is_auto_retryable_now();
payment.get_mut().insert_previously_failed_scid(scid);
}
- if payment_is_probe || !is_retryable_now || !payment_retryable {
- let reason = if !payment_retryable {
+ if payment_is_probe || !is_retryable_now || payment_failed_permanently {
+ let reason = if payment_failed_permanently {
PaymentFailureReason::RecipientRejected
} else {
PaymentFailureReason::RetriesExhausted
is_retryable_now = false;
}
if payment.get().remaining_parts() == 0 {
- if let PendingOutboundPayment::Abandoned { payment_hash, reason, .. }= payment.get() {
+ if let PendingOutboundPayment::Abandoned { payment_hash, reason, .. } = payment.get() {
if !payment_is_probe {
full_failure_ev = Some(events::Event::PaymentFailed {
payment_id: *payment_id,
}
is_retryable_now
} else {
- log_trace!(logger, "Received duplicative fail for HTLC with payment_hash {}", log_bytes!(payment_hash.0));
+ log_trace!(logger, "Received duplicative fail for HTLC with payment_hash {}", &payment_hash);
return false
};
core::mem::drop(outbounds);
- log_trace!(logger, "Failing outbound payment HTLC with payment_hash {}", log_bytes!(payment_hash.0));
+ log_trace!(logger, "Failing outbound payment HTLC with payment_hash {}", &payment_hash);
let path_failure = {
if payment_is_probe {
- if !payment_retryable {
+ if payment_failed_permanently {
events::Event::ProbeSuccessful {
payment_id: *payment_id,
payment_hash: payment_hash.clone(),
events::Event::PaymentPathFailed {
payment_id: Some(*payment_id),
payment_hash: payment_hash.clone(),
- payment_failed_permanently: !payment_retryable,
+ payment_failed_permanently,
failure: events::PathFailure::OnPath { network_update },
path: path.clone(),
short_channel_id,
}, None));
payment.remove();
}
+ } else if let PendingOutboundPayment::AwaitingInvoice { .. } = payment.get() {
+ pending_events.lock().unwrap().push_back((events::Event::InvoiceRequestFailed {
+ payment_id,
+ }, None));
+ payment.remove();
}
}
}
(6, total_msat, required),
(7, payment_metadata, option),
(8, pending_amt_msat, required),
+ (9, custom_tlvs, optional_vec),
(10, starting_block_height, required),
+ (11, remaining_max_total_routing_fee_msat, option),
(not_written, retry_strategy, (static_value, None)),
(not_written, attempts, (static_value, PaymentAttempts::new())),
},
(1, reason, option),
(2, payment_hash, required),
},
+ (5, AwaitingInvoice) => {
+ (0, timer_ticks_without_response, required),
+ (2, retry_strategy, required),
+ (4, max_total_routing_fee_msat, option),
+ },
+ (7, InvoiceReceived) => {
+ (0, payment_hash, required),
+ (2, retry_strategy, required),
+ (4, max_total_routing_fee_msat, option),
+ },
);
#[cfg(test)]
use crate::ln::channelmanager::{PaymentId, RecipientOnionFields};
use crate::ln::features::{ChannelFeatures, NodeFeatures};
use crate::ln::msgs::{ErrorAction, LightningError};
- use crate::ln::outbound_payment::{OutboundPayments, Retry, RetryableSendFailure};
+ use crate::ln::outbound_payment::{Bolt12PaymentError, INVOICE_REQUEST_TIMEOUT_TICKS, OutboundPayments, Retry, RetryableSendFailure};
+ use crate::offers::invoice::DEFAULT_RELATIVE_EXPIRY;
+ use crate::offers::offer::OfferBuilder;
+ use crate::offers::test_utils::*;
use crate::routing::gossip::NetworkGraph;
use crate::routing::router::{InFlightHtlcs, Path, PaymentParameters, Route, RouteHop, RouteParameters};
- use crate::sync::{Arc, Mutex};
+ use crate::sync::{Arc, Mutex, RwLock};
use crate::util::errors::APIError;
use crate::util::test_utils;
use alloc::collections::VecDeque;
+ #[test]
+ fn test_recipient_onion_fields_with_custom_tlvs() {
+ let onion_fields = RecipientOnionFields::spontaneous_empty();
+
+ let bad_type_range_tlvs = vec![
+ (0, vec![42]),
+ (1, vec![42; 32]),
+ ];
+ assert!(onion_fields.clone().with_custom_tlvs(bad_type_range_tlvs).is_err());
+
+ let keysend_tlv = vec![
+ (5482373484, vec![42; 32]),
+ ];
+ assert!(onion_fields.clone().with_custom_tlvs(keysend_tlv).is_err());
+
+ let good_tlvs = vec![
+ ((1 << 16) + 1, vec![42]),
+ ((1 << 16) + 3, vec![42; 32]),
+ ];
+ assert!(onion_fields.with_custom_tlvs(good_tlvs).is_ok());
+ }
+
#[test]
#[cfg(feature = "std")]
fn fails_paying_after_expiration() {
let outbound_payments = OutboundPayments::new();
let logger = test_utils::TestLogger::new();
let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger));
- let scorer = Mutex::new(test_utils::TestScorer::new());
+ let scorer = RwLock::new(test_utils::TestScorer::new());
let router = test_utils::TestRouter::new(network_graph, &scorer);
let secp_ctx = Secp256k1::new();
let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet);
PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap()),
0
).with_expiry_time(past_expiry_time);
- let expired_route_params = RouteParameters {
- payment_params,
- final_value_msat: 0,
- };
+ let expired_route_params = RouteParameters::from_payment_params_and_value(payment_params, 0);
let pending_events = Mutex::new(VecDeque::new());
if on_retry {
outbound_payments.add_new_pending_payment(PaymentHash([0; 32]), RecipientOnionFields::spontaneous_empty(),
- PaymentId([0; 32]), None, &Route { paths: vec![], payment_params: None },
+ PaymentId([0; 32]), None, &Route { paths: vec![], route_params: None },
Some(Retry::Attempts(1)), Some(expired_route_params.payment_params.clone()),
&&keys_manager, 0).unwrap();
- outbound_payments.retry_payment_internal(
+ outbound_payments.find_route_and_send_payment(
PaymentHash([0; 32]), PaymentId([0; 32]), expired_route_params, &&router, vec![],
- &|| InFlightHtlcs::new(), &&keys_manager, &&keys_manager, 0, &&logger,
- &pending_events, &|_, _, _, _, _, _, _, _| Ok(()));
+ &|| InFlightHtlcs::new(), &&keys_manager, &&keys_manager, 0, &&logger, &pending_events,
+ &|_| Ok(()));
let events = pending_events.lock().unwrap();
assert_eq!(events.len(), 1);
if let Event::PaymentFailed { ref reason, .. } = events[0].0 {
let err = outbound_payments.send_payment(
PaymentHash([0; 32]), RecipientOnionFields::spontaneous_empty(), PaymentId([0; 32]),
Retry::Attempts(0), expired_route_params, &&router, vec![], || InFlightHtlcs::new(),
- &&keys_manager, &&keys_manager, 0, &&logger,
- &pending_events, |_, _, _, _, _, _, _, _| Ok(())).unwrap_err();
+ &&keys_manager, &&keys_manager, 0, &&logger, &pending_events, |_| Ok(())).unwrap_err();
if let RetryableSendFailure::PaymentExpired = err { } else { panic!("Unexpected error"); }
}
}
let outbound_payments = OutboundPayments::new();
let logger = test_utils::TestLogger::new();
let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger));
- let scorer = Mutex::new(test_utils::TestScorer::new());
+ let scorer = RwLock::new(test_utils::TestScorer::new());
let router = test_utils::TestRouter::new(network_graph, &scorer);
let secp_ctx = Secp256k1::new();
let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet);
let payment_params = PaymentParameters::from_node_id(
PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap()), 0);
- let route_params = RouteParameters {
- payment_params,
- final_value_msat: 0,
- };
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, 0);
router.expect_find_route(route_params.clone(),
Err(LightningError { err: String::new(), action: ErrorAction::IgnoreError }));
let pending_events = Mutex::new(VecDeque::new());
if on_retry {
outbound_payments.add_new_pending_payment(PaymentHash([0; 32]), RecipientOnionFields::spontaneous_empty(),
- PaymentId([0; 32]), None, &Route { paths: vec![], payment_params: None },
+ PaymentId([0; 32]), None, &Route { paths: vec![], route_params: None },
Some(Retry::Attempts(1)), Some(route_params.payment_params.clone()),
&&keys_manager, 0).unwrap();
- outbound_payments.retry_payment_internal(
+ outbound_payments.find_route_and_send_payment(
PaymentHash([0; 32]), PaymentId([0; 32]), route_params, &&router, vec![],
- &|| InFlightHtlcs::new(), &&keys_manager, &&keys_manager, 0, &&logger,
- &pending_events, &|_, _, _, _, _, _, _, _| Ok(()));
+ &|| InFlightHtlcs::new(), &&keys_manager, &&keys_manager, 0, &&logger, &pending_events,
+ &|_| Ok(()));
let events = pending_events.lock().unwrap();
assert_eq!(events.len(), 1);
if let Event::PaymentFailed { .. } = events[0].0 { } else { panic!("Unexpected event"); }
let err = outbound_payments.send_payment(
PaymentHash([0; 32]), RecipientOnionFields::spontaneous_empty(), PaymentId([0; 32]),
Retry::Attempts(0), route_params, &&router, vec![], || InFlightHtlcs::new(),
- &&keys_manager, &&keys_manager, 0, &&logger,
- &pending_events, |_, _, _, _, _, _, _, _| Ok(())).unwrap_err();
+ &&keys_manager, &&keys_manager, 0, &&logger, &pending_events, |_| Ok(())).unwrap_err();
if let RetryableSendFailure::RouteNotFound = err {
} else { panic!("Unexpected error"); }
}
let outbound_payments = OutboundPayments::new();
let logger = test_utils::TestLogger::new();
let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger));
- let scorer = Mutex::new(test_utils::TestScorer::new());
+ let scorer = RwLock::new(test_utils::TestScorer::new());
let router = test_utils::TestRouter::new(network_graph, &scorer);
let secp_ctx = Secp256k1::new();
let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet);
let sender_pk = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
let receiver_pk = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[43; 32]).unwrap());
let payment_params = PaymentParameters::from_node_id(sender_pk, 0);
- let route_params = RouteParameters {
- payment_params: payment_params.clone(),
- final_value_msat: 0,
- };
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params.clone(), 0);
let failed_scid = 42;
let route = Route {
paths: vec![Path { hops: vec![RouteHop {
channel_features: ChannelFeatures::empty(),
fee_msat: 0,
cltv_expiry_delta: 0,
+ maybe_announced_channel: true,
}], blinded_tail: None }],
- payment_params: Some(payment_params),
+ route_params: Some(route_params.clone()),
};
router.expect_find_route(route_params.clone(), Ok(route.clone()));
let mut route_params_w_failed_scid = route_params.clone();
PaymentHash([0; 32]), RecipientOnionFields::spontaneous_empty(), PaymentId([0; 32]),
Retry::Attempts(0), route_params.clone(), &&router, vec![], || InFlightHtlcs::new(),
&&keys_manager, &&keys_manager, 0, &&logger, &pending_events,
- |_, _, _, _, _, _, _, _| Err(APIError::ChannelUnavailable { err: "test".to_owned() }))
- .unwrap();
+ |_| Err(APIError::ChannelUnavailable { err: "test".to_owned() })).unwrap();
let mut events = pending_events.lock().unwrap();
assert_eq!(events.len(), 2);
if let Event::PaymentPathFailed {
PaymentHash([0; 32]), RecipientOnionFields::spontaneous_empty(), PaymentId([0; 32]),
Retry::Attempts(0), route_params.clone(), &&router, vec![], || InFlightHtlcs::new(),
&&keys_manager, &&keys_manager, 0, &&logger, &pending_events,
- |_, _, _, _, _, _, _, _| Err(APIError::MonitorUpdateInProgress)).unwrap();
+ |_| Err(APIError::MonitorUpdateInProgress)).unwrap();
assert_eq!(pending_events.lock().unwrap().len(), 0);
// Ensure that any other error will result in a PaymentPathFailed event but no blamed scid.
PaymentHash([0; 32]), RecipientOnionFields::spontaneous_empty(), PaymentId([1; 32]),
Retry::Attempts(0), route_params.clone(), &&router, vec![], || InFlightHtlcs::new(),
&&keys_manager, &&keys_manager, 0, &&logger, &pending_events,
- |_, _, _, _, _, _, _, _| Err(APIError::APIMisuseError { err: "test".to_owned() }))
- .unwrap();
+ |_| Err(APIError::APIMisuseError { err: "test".to_owned() })).unwrap();
let events = pending_events.lock().unwrap();
assert_eq!(events.len(), 2);
if let Event::PaymentPathFailed {
} else { panic!("Unexpected event"); }
if let Event::PaymentFailed { .. } = events[1].0 { } else { panic!("Unexpected event"); }
}
+
+ #[test]
+ fn removes_stale_awaiting_invoice() {
+ let pending_events = Mutex::new(VecDeque::new());
+ let outbound_payments = OutboundPayments::new();
+ let payment_id = PaymentId([0; 32]);
+
+ assert!(!outbound_payments.has_pending_payments());
+ assert!(
+ outbound_payments.add_new_awaiting_invoice(payment_id, 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);
+ assert!(outbound_payments.has_pending_payments());
+ assert!(pending_events.lock().unwrap().is_empty());
+ }
+
+ outbound_payments.remove_stale_payments(&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, 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()
+ );
+ }
+
+ #[test]
+ fn removes_abandoned_awaiting_invoice() {
+ let pending_events = Mutex::new(VecDeque::new());
+ let outbound_payments = OutboundPayments::new();
+ let payment_id = PaymentId([0; 32]);
+
+ assert!(!outbound_payments.has_pending_payments());
+ assert!(
+ outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0), None).is_ok()
+ );
+ assert!(outbound_payments.has_pending_payments());
+
+ outbound_payments.abandon_payment(
+ payment_id, PaymentFailureReason::UserAbandoned, &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());
+ }
+
+ #[cfg(feature = "std")]
+ #[test]
+ fn fails_sending_payment_for_expired_bolt12_invoice() {
+ let logger = test_utils::TestLogger::new();
+ let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger));
+ let scorer = RwLock::new(test_utils::TestScorer::new());
+ let router = test_utils::TestRouter::new(network_graph, &scorer);
+ let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet);
+
+ let pending_events = Mutex::new(VecDeque::new());
+ let outbound_payments = OutboundPayments::new();
+ let payment_id = PaymentId([0; 32]);
+
+ assert!(
+ outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0), None).is_ok()
+ );
+ assert!(outbound_payments.has_pending_payments());
+
+ let created_at = now() - DEFAULT_RELATIVE_EXPIRY;
+ let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
+ .amount_msats(1000)
+ .build().unwrap()
+ .request_invoice(vec![1; 32], payer_pubkey()).unwrap()
+ .build().unwrap()
+ .sign(payer_sign).unwrap()
+ .respond_with_no_std(payment_paths(), payment_hash(), created_at).unwrap()
+ .build().unwrap()
+ .sign(recipient_sign).unwrap();
+
+ assert_eq!(
+ outbound_payments.send_payment_for_bolt12_invoice(
+ &invoice, payment_id, &&router, vec![], || InFlightHtlcs::new(), &&keys_manager,
+ &&keys_manager, 0, &&logger, &pending_events, |_| panic!()
+ ),
+ Ok(()),
+ );
+ assert!(!outbound_payments.has_pending_payments());
+
+ let payment_hash = invoice.payment_hash();
+ let reason = Some(PaymentFailureReason::PaymentExpired);
+
+ assert!(!pending_events.lock().unwrap().is_empty());
+ assert_eq!(
+ pending_events.lock().unwrap().pop_front(),
+ Some((Event::PaymentFailed { payment_id, payment_hash, reason }, None)),
+ );
+ assert!(pending_events.lock().unwrap().is_empty());
+ }
+
+ #[test]
+ fn fails_finding_route_for_bolt12_invoice() {
+ let logger = test_utils::TestLogger::new();
+ let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger));
+ let scorer = RwLock::new(test_utils::TestScorer::new());
+ let router = test_utils::TestRouter::new(network_graph, &scorer);
+ let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet);
+
+ let pending_events = Mutex::new(VecDeque::new());
+ let outbound_payments = OutboundPayments::new();
+ let payment_id = PaymentId([0; 32]);
+
+ let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
+ .amount_msats(1000)
+ .build().unwrap()
+ .request_invoice(vec![1; 32], payer_pubkey()).unwrap()
+ .build().unwrap()
+ .sign(payer_sign).unwrap()
+ .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
+ .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.has_pending_payments());
+
+ router.expect_find_route(
+ RouteParameters::from_payment_params_and_value(
+ PaymentParameters::from_bolt12_invoice(&invoice),
+ invoice.amount_msats(),
+ ),
+ Err(LightningError { err: String::new(), action: ErrorAction::IgnoreError }),
+ );
+
+ assert_eq!(
+ outbound_payments.send_payment_for_bolt12_invoice(
+ &invoice, payment_id, &&router, vec![], || InFlightHtlcs::new(), &&keys_manager,
+ &&keys_manager, 0, &&logger, &pending_events, |_| panic!()
+ ),
+ Ok(()),
+ );
+ assert!(!outbound_payments.has_pending_payments());
+
+ let payment_hash = invoice.payment_hash();
+ let reason = Some(PaymentFailureReason::RouteNotFound);
+
+ assert!(!pending_events.lock().unwrap().is_empty());
+ assert_eq!(
+ pending_events.lock().unwrap().pop_front(),
+ Some((Event::PaymentFailed { payment_id, payment_hash, reason }, None)),
+ );
+ assert!(pending_events.lock().unwrap().is_empty());
+ }
+
+ #[test]
+ fn fails_paying_for_bolt12_invoice() {
+ let logger = test_utils::TestLogger::new();
+ let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger));
+ let scorer = RwLock::new(test_utils::TestScorer::new());
+ let router = test_utils::TestRouter::new(network_graph, &scorer);
+ let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet);
+
+ let pending_events = Mutex::new(VecDeque::new());
+ let outbound_payments = OutboundPayments::new();
+ let payment_id = PaymentId([0; 32]);
+
+ let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
+ .amount_msats(1000)
+ .build().unwrap()
+ .request_invoice(vec![1; 32], payer_pubkey()).unwrap()
+ .build().unwrap()
+ .sign(payer_sign).unwrap()
+ .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
+ .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.has_pending_payments());
+
+ let route_params = RouteParameters::from_payment_params_and_value(
+ PaymentParameters::from_bolt12_invoice(&invoice),
+ invoice.amount_msats(),
+ );
+ router.expect_find_route(
+ route_params.clone(), Ok(Route { paths: vec![], route_params: Some(route_params) })
+ );
+
+ assert_eq!(
+ outbound_payments.send_payment_for_bolt12_invoice(
+ &invoice, payment_id, &&router, vec![], || InFlightHtlcs::new(), &&keys_manager,
+ &&keys_manager, 0, &&logger, &pending_events, |_| panic!()
+ ),
+ Ok(()),
+ );
+ assert!(!outbound_payments.has_pending_payments());
+
+ let payment_hash = invoice.payment_hash();
+ let reason = Some(PaymentFailureReason::UnexpectedError);
+
+ assert!(!pending_events.lock().unwrap().is_empty());
+ assert_eq!(
+ pending_events.lock().unwrap().pop_front(),
+ Some((Event::PaymentFailed { payment_id, payment_hash, reason }, None)),
+ );
+ assert!(pending_events.lock().unwrap().is_empty());
+ }
+
+ #[test]
+ fn sends_payment_for_bolt12_invoice() {
+ let logger = test_utils::TestLogger::new();
+ let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger));
+ let scorer = RwLock::new(test_utils::TestScorer::new());
+ let router = test_utils::TestRouter::new(network_graph, &scorer);
+ let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet);
+
+ let pending_events = Mutex::new(VecDeque::new());
+ let outbound_payments = OutboundPayments::new();
+ let payment_id = PaymentId([0; 32]);
+
+ let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
+ .amount_msats(1000)
+ .build().unwrap()
+ .request_invoice(vec![1; 32], payer_pubkey()).unwrap()
+ .build().unwrap()
+ .sign(payer_sign).unwrap()
+ .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
+ .build().unwrap()
+ .sign(recipient_sign).unwrap();
+
+ let route_params = RouteParameters {
+ payment_params: PaymentParameters::from_bolt12_invoice(&invoice),
+ final_value_msat: invoice.amount_msats(),
+ max_total_routing_fee_msat: Some(1234),
+ };
+ router.expect_find_route(
+ route_params.clone(),
+ Ok(Route {
+ paths: vec![
+ Path {
+ hops: vec![
+ RouteHop {
+ pubkey: recipient_pubkey(),
+ node_features: NodeFeatures::empty(),
+ short_channel_id: 42,
+ channel_features: ChannelFeatures::empty(),
+ fee_msat: invoice.amount_msats(),
+ cltv_expiry_delta: 0,
+ maybe_announced_channel: true,
+ }
+ ],
+ blinded_tail: None,
+ }
+ ],
+ route_params: Some(route_params),
+ })
+ );
+
+ assert!(!outbound_payments.has_pending_payments());
+ assert_eq!(
+ outbound_payments.send_payment_for_bolt12_invoice(
+ &invoice, payment_id, &&router, vec![], || InFlightHtlcs::new(), &&keys_manager,
+ &&keys_manager, 0, &&logger, &pending_events, |_| panic!()
+ ),
+ Err(Bolt12PaymentError::UnexpectedInvoice),
+ );
+ assert!(!outbound_payments.has_pending_payments());
+ assert!(pending_events.lock().unwrap().is_empty());
+
+ assert!(
+ outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0), Some(1234)).is_ok()
+ );
+ assert!(outbound_payments.has_pending_payments());
+
+ assert_eq!(
+ outbound_payments.send_payment_for_bolt12_invoice(
+ &invoice, payment_id, &&router, vec![], || InFlightHtlcs::new(), &&keys_manager,
+ &&keys_manager, 0, &&logger, &pending_events, |_| Ok(())
+ ),
+ Ok(()),
+ );
+ assert!(outbound_payments.has_pending_payments());
+ assert!(pending_events.lock().unwrap().is_empty());
+
+ assert_eq!(
+ outbound_payments.send_payment_for_bolt12_invoice(
+ &invoice, payment_id, &&router, vec![], || InFlightHtlcs::new(), &&keys_manager,
+ &&keys_manager, 0, &&logger, &pending_events, |_| panic!()
+ ),
+ Err(Bolt12PaymentError::DuplicateInvoice),
+ );
+ assert!(outbound_payments.has_pending_payments());
+ assert!(pending_events.lock().unwrap().is_empty());
+ }
}
use crate::chain::channelmonitor::{ANTI_REORG_DELAY, HTLC_FAIL_BACK_BUFFER, LATENCY_GRACE_PERIOD_BLOCKS};
use crate::sign::EntropySource;
use crate::chain::transaction::OutPoint;
-use crate::events::{ClosureReason, Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider, PathFailure, PaymentFailureReason};
+use crate::events::{ClosureReason, Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider, PathFailure, PaymentFailureReason, PaymentPurpose};
use crate::ln::channel::EXPIRE_PREV_CONFIG_TICKS;
-use crate::ln::channelmanager::{BREAKDOWN_TIMEOUT, ChannelManager, MPP_TIMEOUT_TICKS, MIN_CLTV_EXPIRY_DELTA, PaymentId, PaymentSendFailure, IDEMPOTENCY_TIMEOUT_TICKS, RecentPaymentDetails, RecipientOnionFields, HTLCForwardInfo, PendingHTLCRouting, PendingAddHTLCInfo};
-use crate::ln::features::InvoiceFeatures;
-use crate::ln::{msgs, PaymentSecret, PaymentPreimage};
+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::{msgs, ChannelId, PaymentSecret, PaymentPreimage};
use crate::ln::msgs::ChannelMessageHandler;
-use crate::ln::outbound_payment::Retry;
+use crate::ln::outbound_payment::{IDEMPOTENCY_TIMEOUT_TICKS, Retry};
use crate::routing::gossip::{EffectiveCapacity, RoutingFees};
use crate::routing::router::{get_route, Path, PaymentParameters, Route, Router, RouteHint, RouteHintHop, RouteHop, RouteParameters, find_route};
use crate::routing::scoring::ChannelUsage;
+use crate::util::config::UserConfig;
use crate::util::test_utils;
use crate::util::errors::APIError;
use crate::util::ser::Writeable;
send_payment(&nodes[3], &vec!(&nodes[2])[..], 1_500_000);
let amt_msat = 1_000_000;
- let (mut route, payment_hash, payment_preimage, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[3], amt_msat);
+ 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();
+ 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();
route.paths.push(path);
route.paths[0].hops[0].pubkey = nodes[1].node.get_our_node_id();
// Initiate the MPP payment.
let payment_id = PaymentId(payment_hash.0);
- let mut route_params = RouteParameters {
- payment_params: route.payment_params.clone().unwrap(),
- final_value_msat: amt_msat,
- };
+ let mut route_params = route.route_params.clone().unwrap();
nodes[0].router.expect_find_route(route_params.clone(), Ok(route.clone()));
nodes[0].node.send_payment(payment_hash, RecipientOnionFields::secret_only(payment_secret),
route.paths.remove(0);
route_params.final_value_msat = 1_000_000;
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 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);
nodes[0].router.expect_find_route(route_params, Ok(route));
nodes[0].node.process_pending_htlc_forwards();
check_added_monitors!(nodes[0], 1);
claim_payment_along_route(&nodes[0], &[&[&nodes[1], &nodes[3]], &[&nodes[2], &nodes[3]]], false, payment_preimage);
}
+#[test]
+fn mpp_retry_overpay() {
+ // We create an MPP scenario with two paths in which we need to overpay to reach
+ // htlc_minimum_msat. We then fail the overpaid path and check that on retry our
+ // max_total_routing_fee_msat only accounts for the path's fees, but not for the fees overpaid
+ // in the first attempt.
+ let chanmon_cfgs = create_chanmon_cfgs(4);
+ let node_cfgs = create_node_cfgs(4, &chanmon_cfgs);
+ let mut user_config = test_default_channel_config();
+ user_config.channel_handshake_config.max_inbound_htlc_value_in_flight_percent_of_channel = 100;
+ let mut limited_config_1 = user_config.clone();
+ limited_config_1.channel_handshake_config.our_htlc_minimum_msat = 35_000_000;
+ let mut limited_config_2 = user_config.clone();
+ limited_config_2.channel_handshake_config.our_htlc_minimum_msat = 34_500_000;
+ let node_chanmgrs = create_node_chanmgrs(4, &node_cfgs,
+ &[Some(user_config), Some(limited_config_1), Some(limited_config_2), Some(user_config)]);
+ let nodes = create_network(4, &node_cfgs, &node_chanmgrs);
+
+ let (chan_1_update, _, _, _) = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 40_000, 0);
+ let (chan_2_update, _, _, _) = create_announced_chan_between_nodes_with_value(&nodes, 0, 2, 40_000, 0);
+ let (_chan_3_update, _, _, _) = create_announced_chan_between_nodes_with_value(&nodes, 1, 3, 40_000, 0);
+ let (chan_4_update, _, chan_4_id, _) = create_announced_chan_between_nodes_with_value(&nodes, 3, 2, 40_000, 0);
+
+ let amt_msat = 70_000_000;
+ 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();
+ 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);
+
+ // Check we overpay on the second path which we're about to fail.
+ assert_eq!(chan_1_update.contents.fee_proportional_millionths, 0);
+ let overpaid_amount_1 = route.paths[0].fee_msat() as u32 - chan_1_update.contents.fee_base_msat;
+ assert_eq!(overpaid_amount_1, 0);
+
+ assert_eq!(chan_2_update.contents.fee_proportional_millionths, 0);
+ let overpaid_amount_2 = route.paths[1].fee_msat() as u32 - chan_2_update.contents.fee_base_msat;
+
+ let total_overpaid_amount = overpaid_amount_1 + overpaid_amount_2;
+
+ // Initiate the payment.
+ let payment_id = PaymentId(payment_hash.0);
+ let mut route_params = route.route_params.clone().unwrap();
+
+ nodes[0].router.expect_find_route(route_params.clone(), Ok(route.clone()));
+ nodes[0].node.send_payment(payment_hash, RecipientOnionFields::secret_only(payment_secret),
+ payment_id, route_params.clone(), Retry::Attempts(1)).unwrap();
+ check_added_monitors!(nodes[0], 2); // one monitor per path
+ let mut events = nodes[0].node.get_and_clear_pending_msg_events();
+ assert_eq!(events.len(), 2);
+
+ // Pass half of the payment along the success path.
+ let success_path_msgs = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events);
+ pass_along_path(&nodes[0], &[&nodes[1], &nodes[3]], amt_msat, payment_hash,
+ Some(payment_secret), success_path_msgs, false, None);
+
+ // Add the HTLC along the first hop.
+ let fail_path_msgs_1 = remove_first_msg_event_to_node(&nodes[2].node.get_our_node_id(), &mut events);
+ let (update_add, commitment_signed) = match fail_path_msgs_1 {
+ MessageSendEvent::UpdateHTLCs {
+ node_id: _,
+ updates: msgs::CommitmentUpdate {
+ ref update_add_htlcs, ref update_fulfill_htlcs, ref update_fail_htlcs,
+ ref update_fail_malformed_htlcs, ref update_fee, ref commitment_signed
+ }
+ } => {
+ assert_eq!(update_add_htlcs.len(), 1);
+ assert!(update_fail_htlcs.is_empty());
+ assert!(update_fulfill_htlcs.is_empty());
+ assert!(update_fail_malformed_htlcs.is_empty());
+ assert!(update_fee.is_none());
+ (update_add_htlcs[0].clone(), commitment_signed.clone())
+ },
+ _ => panic!("Unexpected event"),
+ };
+ nodes[2].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &update_add);
+ commitment_signed_dance!(nodes[2], nodes[0], commitment_signed, false);
+
+ // Attempt to forward the payment and complete the 2nd path's failure.
+ expect_pending_htlcs_forwardable!(&nodes[2]);
+ expect_pending_htlcs_forwardable_and_htlc_handling_failed!(&nodes[2],
+ vec![HTLCDestination::NextHopChannel {
+ node_id: Some(nodes[3].node.get_our_node_id()), channel_id: chan_4_id
+ }]
+ );
+ let htlc_updates = get_htlc_update_msgs!(nodes[2], nodes[0].node.get_our_node_id());
+ assert!(htlc_updates.update_add_htlcs.is_empty());
+ assert_eq!(htlc_updates.update_fail_htlcs.len(), 1);
+ assert!(htlc_updates.update_fulfill_htlcs.is_empty());
+ assert!(htlc_updates.update_fail_malformed_htlcs.is_empty());
+ check_added_monitors!(nodes[2], 1);
+ nodes[0].node.handle_update_fail_htlc(&nodes[2].node.get_our_node_id(),
+ &htlc_updates.update_fail_htlcs[0]);
+ commitment_signed_dance!(nodes[0], nodes[2], htlc_updates.commitment_signed, false);
+ let mut events = nodes[0].node.get_and_clear_pending_events();
+ match events[1] {
+ Event::PendingHTLCsForwardable { .. } => {},
+ _ => panic!("Unexpected event")
+ }
+ events.remove(1);
+ expect_payment_failed_conditions_event(events, payment_hash, false,
+ PaymentFailedConditions::new().mpp_parts_remain());
+
+ // Rebalance the channel so the second half of the payment can succeed.
+ send_payment(&nodes[3], &vec!(&nodes[2])[..], 38_000_000);
+
+ // Retry the second half of the payment and make sure it succeeds.
+ let first_path_value = route.paths[0].final_value_msat();
+ assert_eq!(first_path_value, 36_000_000);
+
+ route.paths.remove(0);
+ route_params.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);
+ nodes[0].router.expect_find_route(route_params, Ok(route));
+ nodes[0].node.process_pending_htlc_forwards();
+
+ check_added_monitors!(nodes[0], 1);
+ let mut events = nodes[0].node.get_and_clear_pending_msg_events();
+ assert_eq!(events.len(), 1);
+ pass_along_path(&nodes[0], &[&nodes[2], &nodes[3]], amt_msat, payment_hash,
+ Some(payment_secret), events.pop().unwrap(), true, None);
+
+ // Can't use claim_payment_along_route as it doesn't support overpayment, so we break out the
+ // individual steps here.
+ let extra_fees = vec![0, total_overpaid_amount];
+ let expected_total_fee_msat = do_claim_payment_along_route_with_extra_penultimate_hop_fees(
+ &nodes[0], &[&[&nodes[1], &nodes[3]], &[&nodes[2], &nodes[3]]], &extra_fees[..], false,
+ payment_preimage);
+ expect_payment_sent!(&nodes[0], payment_preimage, Some(expected_total_fee_msat));
+}
+
fn do_mpp_receive_timeout(send_partial_mpp: bool) {
let chanmon_cfgs = create_chanmon_cfgs(4);
let node_cfgs = create_node_cfgs(4, &chanmon_cfgs);
do_mpp_receive_timeout(false);
}
+#[test]
+fn test_keysend_payments() {
+ do_test_keysend_payments(false, false);
+ do_test_keysend_payments(false, true);
+ do_test_keysend_payments(true, false);
+ do_test_keysend_payments(true, true);
+}
+
+fn do_test_keysend_payments(public_node: bool, with_retry: bool) {
+ let chanmon_cfgs = create_chanmon_cfgs(2);
+ let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
+ let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
+ let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+
+ if public_node {
+ create_announced_chan_between_nodes(&nodes, 0, 1);
+ } else {
+ create_chan_between_nodes(&nodes[0], &nodes[1]);
+ }
+ let payer_pubkey = nodes[0].node.get_our_node_id();
+ let payee_pubkey = nodes[1].node.get_our_node_id();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ PaymentParameters::for_keysend(payee_pubkey, 40, false), 10000);
+
+ let network_graph = nodes[0].network_graph.clone();
+ let channels = nodes[0].node.list_usable_channels();
+ let first_hops = channels.iter().collect::<Vec<_>>();
+ let first_hops = if public_node { None } else { Some(first_hops.as_slice()) };
+
+ let scorer = test_utils::TestScorer::new();
+ let random_seed_bytes = chanmon_cfgs[1].keys_manager.get_secure_random_bytes();
+ let route = find_route(
+ &payer_pubkey, &route_params, &network_graph, first_hops,
+ nodes[0].logger, &scorer, &Default::default(), &random_seed_bytes
+ ).unwrap();
+
+ {
+ let test_preimage = PaymentPreimage([42; 32]);
+ if with_retry {
+ nodes[0].node.send_spontaneous_payment_with_retry(Some(test_preimage),
+ RecipientOnionFields::spontaneous_empty(), PaymentId(test_preimage.0),
+ route_params, Retry::Attempts(1)).unwrap()
+ } else {
+ nodes[0].node.send_spontaneous_payment(&route, Some(test_preimage),
+ RecipientOnionFields::spontaneous_empty(), PaymentId(test_preimage.0)).unwrap()
+ };
+ }
+ check_added_monitors!(nodes[0], 1);
+ let send_event = SendEvent::from_node(&nodes[0]);
+ nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &send_event.msgs[0]);
+ do_commitment_signed_dance(&nodes[1], &nodes[0], &send_event.commitment_msg, false, false);
+ expect_pending_htlcs_forwardable!(nodes[1]);
+ // Previously, a refactor caused us to stop including the payment preimage in the onion which
+ // is sent as a part of keysend payments. Thus, to be extra careful here, we scope the preimage
+ // above to demonstrate that we have no way to get the preimage at this point except by
+ // extracting it from the onion nodes[1] received.
+ let event = nodes[1].node.get_and_clear_pending_events();
+ assert_eq!(event.len(), 1);
+ if let Event::PaymentClaimable { purpose: PaymentPurpose::SpontaneousPayment(preimage), .. } = event[0] {
+ claim_payment(&nodes[0], &[&nodes[1]], preimage);
+ } else { panic!(); }
+}
+
#[test]
fn test_mpp_keysend() {
let mut mpp_keysend_config = test_default_channel_config();
let payer_pubkey = nodes[0].node.get_our_node_id();
let payee_pubkey = nodes[3].node.get_our_node_id();
let recv_value = 15_000_000;
- let route_params = RouteParameters {
- payment_params: PaymentParameters::for_keysend(payee_pubkey, 40, true),
- final_value_msat: recv_value,
- };
+ let route_params = RouteParameters::from_payment_params_and_value(
+ PaymentParameters::for_keysend(payee_pubkey, 40, true), recv_value);
let scorer = test_utils::TestScorer::new();
let random_seed_bytes = chanmon_cfgs[0].keys_manager.get_secure_random_bytes();
let route = find_route(&payer_pubkey, &route_params, &network_graph, None, nodes[0].logger,
- &scorer, &(), &random_seed_bytes).unwrap();
+ &scorer, &Default::default(), &random_seed_bytes).unwrap();
let payment_preimage = PaymentPreimage([42; 32]);
let payment_secret = PaymentSecret(payment_preimage.0);
// which has separate codepaths for "commitment transaction already confirmed" and not.
let chanmon_cfgs = create_chanmon_cfgs(3);
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
+ let persister;
+ let new_chain_monitor;
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
- let persister: test_utils::TestPersister;
- let new_chain_monitor: test_utils::TestChainMonitor;
- let nodes_0_deserialized: ChannelManager<&test_utils::TestChainMonitor, &test_utils::TestBroadcaster, &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestFeeEstimator, &test_utils::TestRouter, &test_utils::TestLogger>;
+ let nodes_0_deserialized;
let mut nodes = create_network(3, &node_cfgs, &node_chanmgrs);
let chan_id = create_announced_chan_between_nodes(&nodes, 0, 1).2;
let amt_msat = 1_000_000;
let (route, payment_hash, payment_preimage, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[2], amt_msat);
let (payment_preimage_1, payment_hash_1, _, payment_id_1) = send_along_route(&nodes[0], route.clone(), &[&nodes[1], &nodes[2]], 1_000_000);
- let route_params = RouteParameters {
- payment_params: route.payment_params.clone().unwrap(),
- final_value_msat: amt_msat,
- };
+ let route_params = route.route_params.unwrap().clone();
nodes[0].node.send_payment(payment_hash, RecipientOnionFields::secret_only(payment_secret),
PaymentId(payment_hash.0), route_params, Retry::Attempts(1)).unwrap();
check_added_monitors!(nodes[0], 1);
// nodes[1] now immediately fails the HTLC as the next-hop channel is disconnected
let _ = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
- reconnect_nodes(&nodes[1], &nodes[2], (false, false), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (false, false));
+ reconnect_nodes(ReconnectArgs::new(&nodes[1], &nodes[2]));
let as_commitment_tx = get_local_commitment_txn!(nodes[0], chan_id)[0].clone();
if confirm_before_reload {
// On reload, the ChannelManager should realize it is stale compared to the ChannelMonitor and
// force-close the channel.
- check_closed_event!(nodes[0], 1, ClosureReason::OutdatedChannelManager);
+ check_closed_event!(nodes[0], 1, ClosureReason::OutdatedChannelManager, [nodes[1].node.get_our_node_id()], 100000);
assert!(nodes[0].node.list_channels().is_empty());
assert!(nodes[0].node.has_pending_payments());
nodes[0].node.timer_tick_occurred();
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);
- check_closed_event!(nodes[1], 1, ClosureReason::CounterpartyForceClosed { peer_msg: UntrustedString(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())) });
+ check_closed_event!(nodes[1], 1, ClosureReason::CounterpartyForceClosed { peer_msg: UntrustedString(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())) }, [nodes[0].node.get_our_node_id()], 100000);
check_added_monitors!(nodes[1], 1);
assert_eq!(nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0).len(), 1);
},
nodes[1].node.handle_update_fulfill_htlc(&nodes[2].node.get_our_node_id(), &htlc_fulfill_updates.update_fulfill_htlcs[0]);
check_added_monitors!(nodes[1], 1);
commitment_signed_dance!(nodes[1], nodes[2], htlc_fulfill_updates.commitment_signed, false);
- expect_payment_forwarded!(nodes[1], nodes[0], nodes[2], None, false, false);
+ expect_payment_forwarded!(nodes[1], nodes[0], nodes[2], None, true, false);
if confirm_before_reload {
let best_block = nodes[0].blocks.lock().unwrap().last().unwrap().clone();
mine_transaction(&nodes[0], &as_commitment_tx);
}
mine_transaction(&nodes[0], &bs_htlc_claim_txn[0]);
- expect_payment_sent!(nodes[0], payment_preimage_1);
+ expect_payment_sent(&nodes[0], payment_preimage_1, None, true, false);
connect_blocks(&nodes[0], TEST_FINAL_CLTV*4 + 20);
let (first_htlc_timeout_tx, second_htlc_timeout_tx) = {
let mut txn = nodes[0].tx_broadcaster.unique_txn_broadcast();
let mut peer_state = per_peer_state.get(&nodes[2].node.get_our_node_id())
.unwrap().lock().unwrap();
let mut channel = peer_state.channel_by_id.get_mut(&chan_id_2).unwrap();
- let mut new_config = channel.context.config();
+ let mut new_config = channel.context().config();
new_config.forwarding_fee_base_msat += 100_000;
- channel.context.update_config(&new_config);
+ channel.context_mut().update_config(&new_config);
new_route.paths[0].hops[0].fee_msat += 100_000;
}
let mut manually_accept_config = test_default_channel_config();
manually_accept_config.manually_accept_inbound_channels = true;
- let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, Some(manually_accept_config), None]);
+ let first_persister;
+ let first_new_chain_monitor;
+ let second_persister;
+ let second_new_chain_monitor;
+ let third_persister;
+ let third_new_chain_monitor;
- let first_persister: test_utils::TestPersister;
- let first_new_chain_monitor: test_utils::TestChainMonitor;
- let first_nodes_0_deserialized: ChannelManager<&test_utils::TestChainMonitor, &test_utils::TestBroadcaster, &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestFeeEstimator, &test_utils::TestRouter, &test_utils::TestLogger>;
- let second_persister: test_utils::TestPersister;
- let second_new_chain_monitor: test_utils::TestChainMonitor;
- let second_nodes_0_deserialized: ChannelManager<&test_utils::TestChainMonitor, &test_utils::TestBroadcaster, &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestFeeEstimator, &test_utils::TestRouter, &test_utils::TestLogger>;
- let third_persister: test_utils::TestPersister;
- let third_new_chain_monitor: test_utils::TestChainMonitor;
- let third_nodes_0_deserialized: ChannelManager<&test_utils::TestChainMonitor, &test_utils::TestBroadcaster, &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestFeeEstimator, &test_utils::TestRouter, &test_utils::TestLogger>;
+ let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, Some(manually_accept_config), None]);
+ let first_nodes_0_deserialized;
+ let second_nodes_0_deserialized;
+ let third_nodes_0_deserialized;
let mut nodes = create_network(3, &node_cfgs, &node_chanmgrs);
// On reload, the ChannelManager should realize it is stale compared to the ChannelMonitor and
// force-close the channel.
- check_closed_event!(nodes[0], 1, ClosureReason::OutdatedChannelManager);
+ check_closed_event!(nodes[0], 1, ClosureReason::OutdatedChannelManager, [nodes[1].node.get_our_node_id()], 100000);
nodes[0].node.timer_tick_occurred();
assert!(nodes[0].node.list_channels().is_empty());
assert!(nodes[0].node.has_pending_payments());
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);
- check_closed_event!(nodes[1], 1, ClosureReason::CounterpartyForceClosed { peer_msg: UntrustedString(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())) });
+ check_closed_event!(nodes[1], 1, ClosureReason::CounterpartyForceClosed { peer_msg: UntrustedString(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())) }
+ , [nodes[0].node.get_our_node_id()], 100000);
check_added_monitors!(nodes[1], 1);
bs_commitment_tx = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
},
nodes[0].node.test_process_background_events();
check_added_monitors(&nodes[0], 1);
- reconnect_nodes(&nodes[0], &nodes[1], (true, true), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (false, false));
+ let mut reconnect_args = ReconnectArgs::new(&nodes[0], &nodes[1]);
+ reconnect_args.send_channel_ready = (true, true);
+ reconnect_nodes(reconnect_args);
// Now resend the payment, delivering the HTLC and actually claiming it this time. This ensures
// the payment is not (spuriously) listed as still pending.
nodes[0].node.test_process_background_events();
check_added_monitors(&nodes[0], 1);
- reconnect_nodes(&nodes[0], &nodes[1], (false, false), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (false, false));
+ reconnect_nodes(ReconnectArgs::new(&nodes[0], &nodes[1]));
match nodes[0].node.send_payment_with_route(&new_route, payment_hash, RecipientOnionFields::secret_only(payment_secret), payment_id) {
Err(PaymentSendFailure::DuplicatePayment) => {},
// duplicate HTLC fail/claim (e.g. via a PaymentPathFailed event).
let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
+ let persister;
+ let new_chain_monitor;
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
- let persister: test_utils::TestPersister;
- let new_chain_monitor: test_utils::TestChainMonitor;
- let nodes_0_deserialized: ChannelManager<&test_utils::TestChainMonitor, &test_utils::TestBroadcaster, &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestFeeEstimator, &test_utils::TestRouter, &test_utils::TestLogger>;
+ let nodes_0_deserialized;
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
let (_, _, chan_id, funding_tx) = create_announced_chan_between_nodes(&nodes, 0, 1);
// Route a payment, but force-close the channel before the HTLC fulfill message arrives at
// nodes[0].
- let (payment_preimage, payment_hash, _) = route_payment(&nodes[0], &[&nodes[1]], 10_000_000);
+ let (payment_preimage, payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1]], 10_000_000);
nodes[0].node.force_close_broadcasting_latest_txn(&nodes[0].node.list_channels()[0].channel_id, &nodes[1].node.get_our_node_id()).unwrap();
check_closed_broadcast!(nodes[0], true);
check_added_monitors!(nodes[0], 1);
- check_closed_event!(nodes[0], 1, ClosureReason::HolderForceClosed);
+ check_closed_event!(nodes[0], 1, ClosureReason::HolderForceClosed, [nodes[1].node.get_our_node_id()], 100000);
nodes[0].node.peer_disconnected(&nodes[1].node.get_our_node_id());
nodes[1].node.peer_disconnected(&nodes[0].node.get_our_node_id());
connect_block(&nodes[1], &create_dummy_block(nodes[1].best_block_hash(), 42, vec![node_txn[1].clone()]));
check_closed_broadcast!(nodes[1], true);
check_added_monitors!(nodes[1], 1);
- check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [nodes[0].node.get_our_node_id()], 100000);
let claim_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
assert_eq!(claim_txn.len(), 1);
check_spends!(claim_txn[0], node_txn[1]);
if payment_timeout {
expect_payment_failed!(nodes[0], payment_hash, false);
} else {
- expect_payment_sent!(nodes[0], payment_preimage);
+ expect_payment_sent(&nodes[0], payment_preimage, None, true, false);
}
// If we persist the ChannelManager after we get the PaymentSent event, we shouldn't get it
} else if payment_timeout {
expect_payment_failed!(nodes[0], payment_hash, false);
} else {
- expect_payment_sent!(nodes[0], payment_preimage);
+ expect_payment_sent(&nodes[0], payment_preimage, None, true, false);
}
// Note that if we re-connect the block which exposed nodes[0] to the payment preimage (but
// handle it, we should test the logic for it anyway. We do that here.
let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
+ let persister;
+ let new_chain_monitor;
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
- let persister: test_utils::TestPersister;
- let new_chain_monitor: test_utils::TestChainMonitor;
- let nodes_1_deserialized: ChannelManager<&test_utils::TestChainMonitor, &test_utils::TestBroadcaster, &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestFeeEstimator, &test_utils::TestRouter, &test_utils::TestLogger>;
+ let nodes_1_deserialized;
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
let chan_id = create_announced_chan_between_nodes(&nodes, 0, 1).2;
- let (payment_preimage, payment_hash, _) = route_payment(&nodes[0], &[&nodes[1]], 100_000);
+ let (payment_preimage, payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1]], 100_000);
// The simplest way to get a failure after a fulfill is to reload nodes[1] from a state
// pre-fulfill, which we do by serializing it here.
let htlc_fulfill_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(), &htlc_fulfill_updates.update_fulfill_htlcs[0]);
- expect_payment_sent_without_paths!(nodes[0], payment_preimage);
+ expect_payment_sent(&nodes[0], payment_preimage, None, false, false);
// Now reload nodes[1]...
reload_node!(nodes[1], &chan_manager_serialized, &[&chan_0_monitor_serialized], persister, new_chain_monitor, nodes_1_deserialized);
nodes[0].node.peer_disconnected(&nodes[1].node.get_our_node_id());
- reconnect_nodes(&nodes[0], &nodes[1], (false, false), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (false, false));
+ reconnect_nodes(ReconnectArgs::new(&nodes[0], &nodes[1]));
nodes[1].node.fail_htlc_backwards(&payment_hash);
expect_pending_htlcs_forwardable_and_htlc_handling_failed!(nodes[1], vec![HTLCDestination::FailedPayment { payment_hash }]);
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();
- let route = get_route(
- &nodes[0].node.get_our_node_id(), &payment_params, &nodes[0].network_graph.read_only(),
- Some(&nodes[0].node.list_usable_channels().iter().collect::<Vec<_>>()),
- amt_msat, nodes[0].logger, &scorer, &(), &random_seed_bytes).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(),
+ Some(&nodes[0].node.list_usable_channels().iter().collect::<Vec<_>>()), nodes[0].logger,
+ &scorer, &Default::default(), &random_seed_bytes).unwrap();
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 payment_params = PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), 42);
- let (route, _, _, _) = get_route_and_payment_hash!(&nodes[0], nodes[2], &payment_params, 9_998_000);
+ let (route, _, _, _) = get_route_and_payment_hash!(&nodes[0], nodes[2], payment_params, 9_998_000);
let (payment_hash, payment_id) = nodes[0].node.send_probe(route.paths[0].clone()).unwrap();
let payment_params = PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), 42);
// Send a dust HTLC, which will be treated as if it timed out once the channel hits the chain.
- let (route, _, _, _) = get_route_and_payment_hash!(&nodes[0], nodes[2], &payment_params, 1_000);
+ let (route, _, _, _) = get_route_and_payment_hash!(&nodes[0], nodes[2], payment_params, 1_000);
let (payment_hash, payment_id) = nodes[0].node.send_probe(route.paths[0].clone()).unwrap();
// node[0] -- update_add_htlcs -> node[1]
assert!(!nodes[0].node.has_pending_payments());
}
+#[test]
+fn preflight_probes_yield_event_and_skip() {
+ let chanmon_cfgs = create_chanmon_cfgs(5);
+ let node_cfgs = create_node_cfgs(5, &chanmon_cfgs);
+
+ // We alleviate the HTLC max-in-flight limit, as otherwise we'd always be limited through that.
+ let mut no_htlc_limit_config = test_default_channel_config();
+ no_htlc_limit_config.channel_handshake_config.max_inbound_htlc_value_in_flight_percent_of_channel = 100;
+
+ let user_configs = std::iter::repeat(no_htlc_limit_config).take(5).map(|c| Some(c)).collect::<Vec<Option<UserConfig>>>();
+ let node_chanmgrs = create_node_chanmgrs(5, &node_cfgs, &user_configs);
+ let nodes = create_network(5, &node_cfgs, &node_chanmgrs);
+
+ // Setup channel topology:
+ // (30k:0)- N2 -(1M:0)
+ // / \
+ // N0 -(100k:0)-> N1 N4
+ // \ /
+ // (70k:0)- N3 -(1M:0)
+ //
+ let first_chan_update = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100_000, 0).0;
+ create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 30_000, 0);
+ create_announced_chan_between_nodes_with_value(&nodes, 1, 3, 70_000, 0);
+ create_announced_chan_between_nodes_with_value(&nodes, 2, 4, 1_000_000, 0);
+ create_announced_chan_between_nodes_with_value(&nodes, 3, 4, 1_000_000, 0);
+
+ let mut invoice_features = Bolt11InvoiceFeatures::empty();
+ invoice_features.set_basic_mpp_optional();
+
+ let mut payment_params = PaymentParameters::from_node_id(nodes[4].node.get_our_node_id(), TEST_FINAL_CLTV)
+ .with_bolt11_features(invoice_features).unwrap();
+
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, 80_000_000);
+ let res = nodes[0].node.send_preflight_probes(route_params, None).unwrap();
+
+ // We check that only one probe was sent, the other one was skipped due to limited liquidity.
+ assert_eq!(res.len(), 1);
+ let log_msg = format!("Skipped sending payment probe to avoid putting channel {} under the liquidity limit.",
+ first_chan_update.contents.short_channel_id);
+ node_cfgs[0].logger.assert_log_contains("lightning::ln::channelmanager", &log_msg, 1);
+
+ let (payment_hash, payment_id) = res.first().unwrap();
+
+ // node[0] -- update_add_htlcs -> node[1]
+ check_added_monitors!(nodes[0], 1);
+ let probe_event = SendEvent::from_node(&nodes[0]);
+ nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &probe_event.msgs[0]);
+ check_added_monitors!(nodes[1], 0);
+ commitment_signed_dance!(nodes[1], nodes[0], probe_event.commitment_msg, false);
+ expect_pending_htlcs_forwardable!(nodes[1]);
+
+ // node[1] -- update_add_htlcs -> node[2]
+ check_added_monitors!(nodes[1], 1);
+ let probe_event = SendEvent::from_node(&nodes[1]);
+ nodes[2].node.handle_update_add_htlc(&nodes[1].node.get_our_node_id(), &probe_event.msgs[0]);
+ check_added_monitors!(nodes[2], 0);
+ commitment_signed_dance!(nodes[2], nodes[1], probe_event.commitment_msg, false);
+ expect_pending_htlcs_forwardable!(nodes[2]);
+
+ // node[2] -- update_add_htlcs -> node[4]
+ check_added_monitors!(nodes[2], 1);
+ let probe_event = SendEvent::from_node(&nodes[2]);
+ nodes[4].node.handle_update_add_htlc(&nodes[2].node.get_our_node_id(), &probe_event.msgs[0]);
+ check_added_monitors!(nodes[4], 0);
+ commitment_signed_dance!(nodes[4], nodes[2], probe_event.commitment_msg, true, true);
+
+ // node[2] <- update_fail_htlcs -- node[4]
+ let updates = get_htlc_update_msgs!(nodes[4], nodes[2].node.get_our_node_id());
+ nodes[2].node.handle_update_fail_htlc(&nodes[4].node.get_our_node_id(), &updates.update_fail_htlcs[0]);
+ check_added_monitors!(nodes[2], 0);
+ commitment_signed_dance!(nodes[2], nodes[4], updates.commitment_signed, true);
+
+ // node[1] <- update_fail_htlcs -- node[2]
+ let updates = get_htlc_update_msgs!(nodes[2], nodes[1].node.get_our_node_id());
+ nodes[1].node.handle_update_fail_htlc(&nodes[2].node.get_our_node_id(), &updates.update_fail_htlcs[0]);
+ check_added_monitors!(nodes[1], 0);
+ commitment_signed_dance!(nodes[1], nodes[2], updates.commitment_signed, true);
+
+ // node[0] <- update_fail_htlcs -- node[1]
+ let updates = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
+ nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &updates.update_fail_htlcs[0]);
+ check_added_monitors!(nodes[0], 0);
+ commitment_signed_dance!(nodes[0], nodes[1], updates.commitment_signed, false);
+
+ let mut events = nodes[0].node.get_and_clear_pending_events();
+ assert_eq!(events.len(), 1);
+ match events.drain(..).next().unwrap() {
+ crate::events::Event::ProbeSuccessful { payment_id: ev_pid, payment_hash: ev_ph, .. } => {
+ assert_eq!(*payment_id, ev_pid);
+ assert_eq!(*payment_hash, ev_ph);
+ },
+ _ => panic!(),
+ };
+ assert!(!nodes[0].node.has_pending_payments());
+}
+
#[test]
fn claimed_send_payment_idempotent() {
// Tests that `send_payment` (and friends) are (reasonably) idempotent.
let (_, _, chan_2_id, _) = create_announced_chan_between_nodes(&nodes, 1, 2);
// Send and claim the payment. Inflight HTLCs should be empty.
- let payment_hash = send_payment(&nodes[0], &[&nodes[1], &nodes[2]], 500000).1;
+ let (_, payment_hash, _, payment_id) = send_payment(&nodes[0], &[&nodes[1], &nodes[2]], 500000);
let inflight_htlcs = node_chanmgrs[0].compute_inflight_htlcs();
{
let mut node_0_per_peer_lock;
let chan_1_used_liquidity = inflight_htlcs.used_liquidity_msat(
&NodeId::from_pubkey(&nodes[0].node.get_our_node_id()) ,
&NodeId::from_pubkey(&nodes[1].node.get_our_node_id()),
- channel_1.context.get_short_channel_id().unwrap()
+ channel_1.context().get_short_channel_id().unwrap()
);
assert_eq!(chan_1_used_liquidity, None);
}
let chan_2_used_liquidity = inflight_htlcs.used_liquidity_msat(
&NodeId::from_pubkey(&nodes[1].node.get_our_node_id()) ,
&NodeId::from_pubkey(&nodes[2].node.get_our_node_id()),
- channel_2.context.get_short_channel_id().unwrap()
+ channel_2.context().get_short_channel_id().unwrap()
);
assert_eq!(chan_2_used_liquidity, None);
}
let pending_payments = nodes[0].node.list_recent_payments();
assert_eq!(pending_payments.len(), 1);
- assert_eq!(pending_payments[0], RecentPaymentDetails::Fulfilled { payment_hash: Some(payment_hash) });
+ assert_eq!(pending_payments[0], RecentPaymentDetails::Fulfilled { payment_hash: Some(payment_hash), payment_id });
// Remove fulfilled payment
for _ in 0..=IDEMPOTENCY_TIMEOUT_TICKS {
}
// Send the payment, but do not claim it. Our inflight HTLCs should contain the pending payment.
- let (payment_preimage, payment_hash, _) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 500000);
+ let (payment_preimage, payment_hash, _, payment_id) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 500000);
let inflight_htlcs = node_chanmgrs[0].compute_inflight_htlcs();
{
let mut node_0_per_peer_lock;
let chan_1_used_liquidity = inflight_htlcs.used_liquidity_msat(
&NodeId::from_pubkey(&nodes[0].node.get_our_node_id()) ,
&NodeId::from_pubkey(&nodes[1].node.get_our_node_id()),
- channel_1.context.get_short_channel_id().unwrap()
+ channel_1.context().get_short_channel_id().unwrap()
);
// First hop accounts for expected 1000 msat fee
assert_eq!(chan_1_used_liquidity, Some(501000));
let chan_2_used_liquidity = inflight_htlcs.used_liquidity_msat(
&NodeId::from_pubkey(&nodes[1].node.get_our_node_id()) ,
&NodeId::from_pubkey(&nodes[2].node.get_our_node_id()),
- channel_2.context.get_short_channel_id().unwrap()
+ channel_2.context().get_short_channel_id().unwrap()
);
assert_eq!(chan_2_used_liquidity, Some(500000));
}
let pending_payments = nodes[0].node.list_recent_payments();
assert_eq!(pending_payments.len(), 1);
- assert_eq!(pending_payments[0], RecentPaymentDetails::Pending { payment_hash, total_msat: 500000 });
+ assert_eq!(pending_payments[0], RecentPaymentDetails::Pending { payment_id, payment_hash, total_msat: 500000 });
// Now, let's claim the payment. This should result in the used liquidity to return `None`.
claim_payment(&nodes[0], &[&nodes[1], &nodes[2]], payment_preimage);
let chan_1_used_liquidity = inflight_htlcs.used_liquidity_msat(
&NodeId::from_pubkey(&nodes[0].node.get_our_node_id()) ,
&NodeId::from_pubkey(&nodes[1].node.get_our_node_id()),
- channel_1.context.get_short_channel_id().unwrap()
+ channel_1.context().get_short_channel_id().unwrap()
);
assert_eq!(chan_1_used_liquidity, None);
}
let chan_2_used_liquidity = inflight_htlcs.used_liquidity_msat(
&NodeId::from_pubkey(&nodes[1].node.get_our_node_id()) ,
&NodeId::from_pubkey(&nodes[2].node.get_our_node_id()),
- channel_2.context.get_short_channel_id().unwrap()
+ channel_2.context().get_short_channel_id().unwrap()
);
assert_eq!(chan_2_used_liquidity, None);
}
let used_liquidity = inflight_htlcs.used_liquidity_msat(
&NodeId::from_pubkey(&nodes[0].node.get_our_node_id()) ,
&NodeId::from_pubkey(&nodes[1].node.get_our_node_id()),
- channel.context.get_short_channel_id().unwrap()
+ channel.context().get_short_channel_id().unwrap()
);
assert_eq!(used_liquidity, Some(2000000));
}])
]).unwrap()
.with_bolt11_features(nodes[2].node.invoice_features()).unwrap();
- let route_params = RouteParameters {
- payment_params,
- final_value_msat: amt_msat,
- };
- let route = get_route(
- &nodes[0].node.get_our_node_id(), &route_params.payment_params,
- &nodes[0].network_graph.read_only(), None, route_params.final_value_msat,
- nodes[0].logger, &scorer, &(), &random_seed_bytes,
- ).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,
};
// Check for unknown channel id error.
- let unknown_chan_id_err = nodes[1].node.forward_intercepted_htlc(intercept_id, &[42; 32], nodes[2].node.get_our_node_id(), expected_outbound_amount_msat).unwrap_err();
+ let unknown_chan_id_err = nodes[1].node.forward_intercepted_htlc(intercept_id, &ChannelId::from_bytes([42; 32]), nodes[2].node.get_our_node_id(), expected_outbound_amount_msat).unwrap_err();
assert_eq!(unknown_chan_id_err , APIError::ChannelUnavailable {
- err: format!("Funded channel with id {} not found for the passed counterparty node_id {}. Channel may still be opening.",
+ err: format!("Channel with id {} not found for the passed counterparty node_id {}.",
log_bytes!([42; 32]), nodes[2].node.get_our_node_id()) });
if test == InterceptTest::Fail {
let temp_chan_id = nodes[1].node.create_channel(nodes[2].node.get_our_node_id(), 100_000, 0, 42, None).unwrap();
let unusable_chan_err = nodes[1].node.forward_intercepted_htlc(intercept_id, &temp_chan_id, nodes[2].node.get_our_node_id(), expected_outbound_amount_msat).unwrap_err();
assert_eq!(unusable_chan_err , APIError::ChannelUnavailable {
- err: format!("Funded channel with id {} not found for the passed counterparty node_id {}. Channel may still be opening.",
- log_bytes!(temp_chan_id), nodes[2].node.get_our_node_id()) });
+ err: format!("Channel with id {} for the passed counterparty node_id {} is still opening.",
+ temp_chan_id, nodes[2].node.get_our_node_id()) });
assert_eq!(nodes[1].node.get_and_clear_pending_msg_events().len(), 1);
// Open the just-in-time channel so the payment can then be forwarded.
},
_ => panic!("Unexpected event")
}
+ check_added_monitors(&nodes[0], 1);
} else if test == InterceptTest::Timeout {
let mut block = create_dummy_block(nodes[0].best_block_hash(), 42, Vec::new());
connect_block(&nodes[0], &block);
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();
- let route_params = RouteParameters {
- payment_params,
- final_value_msat: amt_msat,
- };
+ 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),
PaymentId(payment_hash.0), route_params, Retry::Attempts(0)).unwrap();
payment_preimage);
// The sender doesn't know that the penultimate hop took an extra fee.
expect_payment_sent(&nodes[0], payment_preimage,
- Some(Some(total_fee_msat - skimmed_fee_msat * num_mpp_parts as u64)), true);
+ Some(Some(total_fee_msat - skimmed_fee_msat * num_mpp_parts as u64)), true, true);
}
#[derive(PartialEq)]
// below.
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 persister;
let new_chain_monitor;
+
+ let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
let node_0_deserialized;
let mut nodes = create_network(3, &node_cfgs, &node_chanmgrs);
#[cfg(not(feature = "std"))]
let payment_expiry_secs = 60 * 60;
let amt_msat = 1000;
- let mut invoice_features = InvoiceFeatures::empty();
+ let mut invoice_features = Bolt11InvoiceFeatures::empty();
invoice_features.set_variable_length_onion_required();
invoice_features.set_payment_secret_required();
invoice_features.set_basic_mpp_optional();
let payment_params = PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), TEST_FINAL_CLTV)
.with_expiry_time(payment_expiry_secs as u64)
.with_bolt11_features(invoice_features).unwrap();
- let route_params = RouteParameters {
- payment_params,
- final_value_msat: amt_msat,
- };
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, amt_msat);
let (_, payment_hash, payment_preimage, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[2], amt_msat);
macro_rules! pass_failed_attempt_with_retry_along_path {
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+ // Open three channels, the first has plenty of liquidity, the second and third have ~no
+ // available liquidity, causing any outbound payments routed over it to fail immediately.
let chan_1_id = create_announced_chan_between_nodes(&nodes, 0, 1).0.contents.short_channel_id;
- let chan_2_id = create_announced_chan_between_nodes(&nodes, 0, 1).0.contents.short_channel_id;
- let chan_3_id = create_announced_chan_between_nodes(&nodes, 0, 1).0.contents.short_channel_id;
+ let chan_2_id = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 989_000_000).0.contents.short_channel_id;
+ let chan_3_id = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 989_000_000).0.contents.short_channel_id;
// Marshall data to send the payment
- let amt_msat = 20_000;
+ let amt_msat = 10_000_000;
let (_, payment_hash, payment_preimage, payment_secret) = get_route_and_payment_hash!(&nodes[0], nodes[1], amt_msat);
#[cfg(feature = "std")]
let payment_expiry_secs = SystemTime::UNIX_EPOCH.elapsed().unwrap().as_secs() + 60 * 60;
#[cfg(not(feature = "std"))]
let payment_expiry_secs = 60 * 60;
- let mut invoice_features = InvoiceFeatures::empty();
+ let mut invoice_features = Bolt11InvoiceFeatures::empty();
invoice_features.set_variable_length_onion_required();
invoice_features.set_payment_secret_required();
invoice_features.set_basic_mpp_optional();
let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), TEST_FINAL_CLTV)
.with_expiry_time(payment_expiry_secs as u64)
.with_bolt11_features(invoice_features).unwrap();
- let route_params = RouteParameters {
- payment_params,
- final_value_msat: amt_msat,
- };
-
- // Ensure the first monitor update (for the initial send path1 over chan_1) succeeds, but the
- // second (for the initial send path2 over chan_2) fails.
- chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::Completed);
- chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::PermanentFailure);
- // Ensure third monitor update (for the retry1's path1 over chan_1) succeeds, but the fourth (for
- // the retry1's path2 over chan_3) fails, and monitor updates succeed after that.
- chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::Completed);
- chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::PermanentFailure);
- chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::Completed);
+ let mut route_params = RouteParameters::from_payment_params_and_value(payment_params, amt_msat);
+ route_params.max_total_routing_fee_msat = None;
// Configure the initial send, retry1 and retry2's paths.
let send_route = Route {
channel_features: nodes[1].node.channel_features(),
fee_msat: amt_msat / 2,
cltv_expiry_delta: 100,
+ maybe_announced_channel: true,
}], blinded_tail: None },
Path { hops: vec![RouteHop {
pubkey: nodes[1].node.get_our_node_id(),
channel_features: nodes[1].node.channel_features(),
fee_msat: amt_msat / 2,
cltv_expiry_delta: 100,
+ maybe_announced_channel: true,
}], blinded_tail: None },
],
- payment_params: Some(route_params.payment_params.clone()),
+ route_params: Some(route_params.clone()),
};
let retry_1_route = Route {
paths: vec![
channel_features: nodes[1].node.channel_features(),
fee_msat: amt_msat / 4,
cltv_expiry_delta: 100,
+ maybe_announced_channel: true,
}], blinded_tail: None },
Path { hops: vec![RouteHop {
pubkey: nodes[1].node.get_our_node_id(),
channel_features: nodes[1].node.channel_features(),
fee_msat: amt_msat / 4,
cltv_expiry_delta: 100,
+ maybe_announced_channel: true,
}], blinded_tail: None },
],
- payment_params: Some(route_params.payment_params.clone()),
+ route_params: Some(route_params.clone()),
};
let retry_2_route = Route {
paths: vec![
channel_features: nodes[1].node.channel_features(),
fee_msat: amt_msat / 4,
cltv_expiry_delta: 100,
+ maybe_announced_channel: true,
}], blinded_tail: None },
],
- payment_params: Some(route_params.payment_params.clone()),
+ route_params: Some(route_params.clone()),
};
nodes[0].router.expect_find_route(route_params.clone(), Ok(send_route));
let mut payment_params = route_params.payment_params.clone();
payment_params.previously_failed_channels.push(chan_2_id);
- nodes[0].router.expect_find_route(RouteParameters {
- payment_params, final_value_msat: amt_msat / 2,
- }, Ok(retry_1_route));
+
+ let mut retry_1_params = RouteParameters::from_payment_params_and_value(payment_params, amt_msat / 2);
+ retry_1_params.max_total_routing_fee_msat = None;
+ nodes[0].router.expect_find_route(retry_1_params, Ok(retry_1_route));
+
let mut payment_params = route_params.payment_params.clone();
payment_params.previously_failed_channels.push(chan_3_id);
- nodes[0].router.expect_find_route(RouteParameters {
- payment_params, final_value_msat: amt_msat / 4,
- }, Ok(retry_2_route));
+ let mut retry_2_params = RouteParameters::from_payment_params_and_value(payment_params, amt_msat / 4);
+ retry_2_params.max_total_routing_fee_msat = None;
+ nodes[0].router.expect_find_route(retry_2_params, Ok(retry_2_route));
// Send a payment that will partially fail on send, then partially fail on retry, then succeed.
nodes[0].node.send_payment(payment_hash, RecipientOnionFields::secret_only(payment_secret),
PaymentId(payment_hash.0), route_params, Retry::Attempts(3)).unwrap();
- let closed_chan_events = nodes[0].node.get_and_clear_pending_events();
- assert_eq!(closed_chan_events.len(), 4);
- match closed_chan_events[0] {
- Event::ChannelClosed { .. } => {},
- _ => panic!("Unexpected event"),
- }
- match closed_chan_events[1] {
+ let payment_failed_events = nodes[0].node.get_and_clear_pending_events();
+ assert_eq!(payment_failed_events.len(), 2);
+ match payment_failed_events[0] {
Event::PaymentPathFailed { .. } => {},
_ => panic!("Unexpected event"),
}
- match closed_chan_events[2] {
- Event::ChannelClosed { .. } => {},
- _ => panic!("Unexpected event"),
- }
- match closed_chan_events[3] {
+ match payment_failed_events[1] {
Event::PaymentPathFailed { .. } => {},
_ => panic!("Unexpected event"),
}
// Pass the first part of the payment along the path.
- check_added_monitors!(nodes[0], 5); // three outbound channel updates succeeded, two permanently failed
+ check_added_monitors!(nodes[0], 1); // only one HTLC actually made it out
let mut msg_events = nodes[0].node.get_and_clear_pending_msg_events();
- // First message is the first update_add, remaining messages are broadcasting channel updates and
- // errors for the permfailed channels
- assert_eq!(msg_events.len(), 5);
+ // Only one HTLC/channel update actually made it out
+ assert_eq!(msg_events.len(), 1);
let mut payment_event = SendEvent::from_event(msg_events.remove(0));
nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &payment_event.msgs[0]);
assert_eq!(bs_claim_update.update_fulfill_htlcs.len(), 1);
nodes[0].node.handle_update_fulfill_htlc(&nodes[1].node.get_our_node_id(), &bs_claim_update.update_fulfill_htlcs[0]);
+ expect_payment_sent(&nodes[0], payment_preimage, None, false, false);
nodes[0].node.handle_commitment_signed(&nodes[1].node.get_our_node_id(), &bs_claim_update.commitment_signed);
check_added_monitors!(nodes[0], 1);
let (as_third_raa, as_third_cs) = get_revoke_commit_msgs!(nodes[0], nodes[1].node.get_our_node_id());
nodes[0].node.handle_revoke_and_ack(&nodes[1].node.get_our_node_id(), &bs_third_raa);
check_added_monitors!(nodes[0], 1);
+ expect_payment_path_successful!(nodes[0]);
nodes[0].node.handle_update_fulfill_htlc(&nodes[1].node.get_our_node_id(), &bs_second_claim_update.update_fulfill_htlcs[0]);
nodes[0].node.handle_update_fulfill_htlc(&nodes[1].node.get_our_node_id(), &bs_second_claim_update.update_fulfill_htlcs[1]);
nodes[0].node.handle_revoke_and_ack(&nodes[1].node.get_our_node_id(), &bs_second_raa);
check_added_monitors!(nodes[0], 1);
- expect_payment_sent!(nodes[0], payment_preimage);
+ let events = nodes[0].node.get_and_clear_pending_events();
+ assert_eq!(events.len(), 2);
+ if let Event::PaymentPathSuccessful { .. } = events[0] {} else { panic!(); }
+ if let Event::PaymentPathSuccessful { .. } = events[1] {} else { panic!(); }
}
#[test]
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
- create_announced_chan_between_nodes(&nodes, 0, 1).0.contents.short_channel_id;
- create_announced_chan_between_nodes(&nodes, 0, 1).0.contents.short_channel_id;
+ // Open a single channel that does not have sufficient liquidity for the payment we want to
+ // send.
+ let chan_id = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 989_000_000).0.contents.short_channel_id;
// Marshall data to send the payment
- let amt_msat = 20_000;
- let (_, payment_hash, _, payment_secret) = get_route_and_payment_hash!(&nodes[0], nodes[1], amt_msat);
+ let amt_msat = 10_000_000;
+ let (_, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[1], Some(amt_msat), None);
#[cfg(feature = "std")]
let payment_expiry_secs = SystemTime::UNIX_EPOCH.elapsed().unwrap().as_secs() + 60 * 60;
#[cfg(not(feature = "std"))]
let payment_expiry_secs = 60 * 60;
- let mut invoice_features = InvoiceFeatures::empty();
+ let mut invoice_features = Bolt11InvoiceFeatures::empty();
invoice_features.set_variable_length_onion_required();
invoice_features.set_payment_secret_required();
invoice_features.set_basic_mpp_optional();
let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), TEST_FINAL_CLTV)
.with_expiry_time(payment_expiry_secs as u64)
.with_bolt11_features(invoice_features).unwrap();
- let route_params = RouteParameters {
- payment_params,
- final_value_msat: amt_msat,
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, amt_msat);
+
+ // Override the route search to return a route, rather than failing at the route-finding step.
+ let send_route = Route {
+ paths: vec![
+ Path { hops: vec![RouteHop {
+ pubkey: nodes[1].node.get_our_node_id(),
+ node_features: nodes[1].node.node_features(),
+ short_channel_id: chan_id,
+ channel_features: nodes[1].node.channel_features(),
+ fee_msat: amt_msat,
+ cltv_expiry_delta: 100,
+ maybe_announced_channel: true,
+ }], blinded_tail: None },
+ ],
+ route_params: Some(route_params.clone()),
};
+ nodes[0].router.expect_find_route(route_params.clone(), Ok(send_route));
- chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::PermanentFailure);
nodes[0].node.send_payment(payment_hash, RecipientOnionFields::secret_only(payment_secret),
PaymentId(payment_hash.0), route_params, Retry::Attempts(0)).unwrap();
- assert_eq!(nodes[0].node.get_and_clear_pending_msg_events().len(), 2); // channel close messages
+ assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
let events = nodes[0].node.get_and_clear_pending_events();
- assert_eq!(events.len(), 3);
- if let Event::ChannelClosed { .. } = events[0] { } else { panic!(); }
- if let Event::PaymentPathFailed { .. } = events[1] { } else { panic!(); }
- if let Event::PaymentFailed { .. } = events[2] { } else { panic!(); }
- check_added_monitors!(nodes[0], 2);
+ assert_eq!(events.len(), 2);
+ if let Event::PaymentPathFailed { .. } = events[0] { } else { panic!(); }
+ if let Event::PaymentFailed { .. } = events[1] { } else { panic!(); }
+ check_added_monitors!(nodes[0], 0);
}
#[test]
let payment_expiry_secs = SystemTime::UNIX_EPOCH.elapsed().unwrap().as_secs() + 60 * 60;
#[cfg(not(feature = "std"))]
let payment_expiry_secs = 60 * 60;
- let mut invoice_features = InvoiceFeatures::empty();
+ let mut invoice_features = Bolt11InvoiceFeatures::empty();
invoice_features.set_variable_length_onion_required();
invoice_features.set_payment_secret_required();
invoice_features.set_basic_mpp_optional();
let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), TEST_FINAL_CLTV)
.with_expiry_time(payment_expiry_secs as u64)
.with_bolt11_features(invoice_features).unwrap();
- let route_params = RouteParameters {
- payment_params,
- final_value_msat: amt_msat,
- };
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, amt_msat);
nodes[0].node.send_payment(payment_hash, RecipientOnionFields::secret_only(payment_secret),
PaymentId(payment_hash.0), route_params, Retry::Attempts(1)).unwrap();
let payment_expiry_secs = SystemTime::UNIX_EPOCH.elapsed().unwrap().as_secs() + 60 * 60;
#[cfg(not(feature = "std"))]
let payment_expiry_secs = 60 * 60;
- let mut invoice_features = InvoiceFeatures::empty();
+ let mut invoice_features = Bolt11InvoiceFeatures::empty();
invoice_features.set_variable_length_onion_required();
invoice_features.set_payment_secret_required();
invoice_features.set_basic_mpp_optional();
let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), TEST_FINAL_CLTV)
.with_expiry_time(payment_expiry_secs as u64)
.with_bolt11_features(invoice_features).unwrap();
- let route_params = RouteParameters {
- payment_params: payment_params.clone(),
- final_value_msat: amt_msat,
- };
+ let mut route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), amt_msat);
+ route_params.max_total_routing_fee_msat = None;
let chans = nodes[0].node.list_usable_channels();
let mut route = Route {
channel_features: nodes[1].node.channel_features(),
fee_msat: 10_000,
cltv_expiry_delta: 100,
+ maybe_announced_channel: true,
}], blinded_tail: None },
Path { hops: vec![RouteHop {
pubkey: nodes[1].node.get_our_node_id(),
channel_features: nodes[1].node.channel_features(),
fee_msat: 100_000_001, // Our default max-HTLC-value is 10% of the channel value, which this is one more than
cltv_expiry_delta: 100,
+ maybe_announced_channel: true,
}], blinded_tail: None },
],
- payment_params: Some(payment_params),
+ 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[0].hops[0].fee_msat = 50_000_001;
route.paths[1].hops[0].fee_msat = 50_000_000;
- let mut pay_params = route.payment_params.clone().unwrap();
+ let mut pay_params = route.route_params.clone().unwrap().payment_params;
pay_params.previously_failed_channels.push(chans[1].short_channel_id.unwrap());
- nodes[0].router.expect_find_route(RouteParameters {
- payment_params: pay_params,
- // Note that the second request here requests the amount we originally failed to send,
- // not the amount remaining on the full payment, which should be changed.
- final_value_msat: 100_000_001,
- }, Ok(route.clone()));
+
+ // Note that the second request here requests the amount we originally failed to send,
+ // not the amount remaining on the full payment, which should be changed.
+ let mut retry_params = RouteParameters::from_payment_params_and_value(pay_params, 100_000_001);
+ retry_params.max_total_routing_fee_msat = None;
+ nodes[0].router.expect_find_route(retry_params, Ok(route.clone()));
{
- let scorer = chanmon_cfgs[0].scorer.lock().unwrap();
+ let scorer = chanmon_cfgs[0].scorer.read().unwrap();
// The initial send attempt, 2 paths
scorer.expect_usage(chans[0].short_channel_id.unwrap(), ChannelUsage { amount_msat: 10_000, inflight_htlc_msat: 0, effective_capacity: EffectiveCapacity::Unknown });
scorer.expect_usage(chans[1].short_channel_id.unwrap(), ChannelUsage { amount_msat: 100_000_001, inflight_htlc_msat: 0, effective_capacity: EffectiveCapacity::Unknown });
let payment_expiry_secs = SystemTime::UNIX_EPOCH.elapsed().unwrap().as_secs() + 60 * 60;
#[cfg(not(feature = "std"))]
let payment_expiry_secs = 60 * 60;
- let mut invoice_features = InvoiceFeatures::empty();
+ let mut invoice_features = Bolt11InvoiceFeatures::empty();
invoice_features.set_variable_length_onion_required();
invoice_features.set_payment_secret_required();
invoice_features.set_basic_mpp_optional();
let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), TEST_FINAL_CLTV)
.with_expiry_time(payment_expiry_secs as u64)
.with_bolt11_features(invoice_features).unwrap();
- let route_params = RouteParameters {
- payment_params,
- final_value_msat: amt_msat,
- };
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, amt_msat);
let chans = nodes[0].node.list_usable_channels();
let mut route = Route {
channel_features: nodes[1].node.channel_features(),
fee_msat: 100_000_001, // Our default max-HTLC-value is 10% of the channel value, which this is one more than
cltv_expiry_delta: 100,
+ maybe_announced_channel: true,
}], blinded_tail: None },
],
- payment_params: Some(PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), TEST_FINAL_CLTV)),
+ 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)),
};
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 {
- payment_params: pay_params, final_value_msat: amt_msat,
- }, Ok(route.clone()));
+ nodes[0].router.expect_find_route(
+ RouteParameters::from_payment_params_and_value(pay_params, amt_msat),
+ 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();
let payment_expiry_secs = SystemTime::UNIX_EPOCH.elapsed().unwrap().as_secs() + 60 * 60;
#[cfg(not(feature = "std"))]
let payment_expiry_secs = 60 * 60;
- let mut invoice_features = InvoiceFeatures::empty();
+ let mut invoice_features = Bolt11InvoiceFeatures::empty();
invoice_features.set_variable_length_onion_required();
invoice_features.set_payment_secret_required();
invoice_features.set_basic_mpp_optional();
let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), TEST_FINAL_CLTV)
.with_expiry_time(payment_expiry_secs as u64)
.with_bolt11_features(invoice_features).unwrap();
- let route_params = RouteParameters {
- payment_params,
- final_value_msat: amt_msat,
- };
+ let mut route_params = RouteParameters::from_payment_params_and_value(payment_params, amt_msat);
+ route_params.max_total_routing_fee_msat = None;
let mut route = Route {
paths: vec![
channel_features: nodes[1].node.channel_features(),
fee_msat: 0, // nodes[1] will fail the payment as we don't pay its fee
cltv_expiry_delta: 100,
+ maybe_announced_channel: true,
}, RouteHop {
pubkey: nodes[2].node.get_our_node_id(),
node_features: nodes[2].node.node_features(),
channel_features: nodes[2].node.channel_features(),
fee_msat: 100_000_000,
cltv_expiry_delta: 100,
+ maybe_announced_channel: true,
}], blinded_tail: None },
Path { hops: vec![RouteHop {
pubkey: nodes[1].node.get_our_node_id(),
channel_features: nodes[1].node.channel_features(),
fee_msat: 0, // nodes[1] will fail the payment as we don't pay its fee
cltv_expiry_delta: 100,
+ maybe_announced_channel: true,
}, RouteHop {
pubkey: nodes[2].node.get_our_node_id(),
node_features: nodes[2].node.node_features(),
channel_features: nodes[2].node.channel_features(),
fee_msat: 100_000_000,
cltv_expiry_delta: 100,
+ maybe_announced_channel: true,
}], blinded_tail: None }
],
- payment_params: Some(PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), TEST_FINAL_CLTV)),
+ route_params: Some(RouteParameters::from_payment_params_and_value(
+ PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), TEST_FINAL_CLTV),
+ 100_000_000)),
};
+ route.route_params.as_mut().unwrap().max_total_routing_fee_msat = None;
nodes[0].router.expect_find_route(route_params.clone(), Ok(route.clone()));
let mut second_payment_params = route_params.payment_params.clone();
second_payment_params.previously_failed_channels = vec![chan_2_scid, chan_2_scid];
// On retry, we'll only return one path
route.paths.remove(1);
route.paths[0].hops[1].fee_msat = amt_msat;
- nodes[0].router.expect_find_route(RouteParameters {
- payment_params: second_payment_params,
- final_value_msat: amt_msat,
- }, Ok(route.clone()));
+ let mut retry_params = RouteParameters::from_payment_params_and_value(second_payment_params, amt_msat);
+ retry_params.max_total_routing_fee_msat = None;
+ 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();
let payment_expiry_secs = SystemTime::UNIX_EPOCH.elapsed().unwrap().as_secs() + 60 * 60;
#[cfg(not(feature = "std"))]
let payment_expiry_secs = 60 * 60;
- let mut invoice_features = InvoiceFeatures::empty();
+ let mut invoice_features = Bolt11InvoiceFeatures::empty();
invoice_features.set_variable_length_onion_required();
invoice_features.set_payment_secret_required();
invoice_features.set_basic_mpp_optional();
let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), TEST_FINAL_CLTV)
.with_expiry_time(payment_expiry_secs as u64)
.with_bolt11_features(invoice_features).unwrap();
- let route_params = RouteParameters {
- payment_params,
- final_value_msat: amt_msat,
- };
+ let mut route_params = RouteParameters::from_payment_params_and_value(payment_params, amt_msat);
+ route_params.max_total_routing_fee_msat = None;
let mut route = Route {
paths: vec![
channel_features: nodes[1].node.channel_features(),
fee_msat: 0, // nodes[1] will fail the payment as we don't pay its fee
cltv_expiry_delta: 100,
+ maybe_announced_channel: true,
}, RouteHop {
pubkey: nodes[2].node.get_our_node_id(),
node_features: nodes[2].node.node_features(),
channel_features: nodes[2].node.channel_features(),
fee_msat: 100_000_000,
cltv_expiry_delta: 100,
+ maybe_announced_channel: true,
}], blinded_tail: None },
Path { hops: vec![RouteHop {
pubkey: nodes[1].node.get_our_node_id(),
channel_features: nodes[1].node.channel_features(),
fee_msat: 100_000,
cltv_expiry_delta: 100,
+ maybe_announced_channel: true,
}, RouteHop {
pubkey: nodes[2].node.get_our_node_id(),
node_features: nodes[2].node.node_features(),
channel_features: nodes[2].node.channel_features(),
fee_msat: 100_000_000,
cltv_expiry_delta: 100,
+ maybe_announced_channel: true,
}], blinded_tail: None }
],
- payment_params: Some(PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), TEST_FINAL_CLTV)),
+ route_params: Some(RouteParameters::from_payment_params_and_value(
+ PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), TEST_FINAL_CLTV),
+ 100_000_000)),
};
+ route.route_params.as_mut().unwrap().max_total_routing_fee_msat = None;
nodes[0].router.expect_find_route(route_params.clone(), Ok(route.clone()));
let mut second_payment_params = route_params.payment_params.clone();
second_payment_params.previously_failed_channels = vec![chan_2_scid];
// On retry, we'll only be asked for one path (or 100k sats)
route.paths.remove(0);
- nodes[0].router.expect_find_route(RouteParameters {
- payment_params: second_payment_params,
- final_value_msat: amt_msat / 2,
- }, Ok(route.clone()));
+ let mut retry_params = RouteParameters::from_payment_params_and_value(second_payment_params, amt_msat / 2);
+ retry_params.max_total_routing_fee_msat = None;
+ 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();
let payment_expiry_secs = SystemTime::UNIX_EPOCH.elapsed().unwrap().as_secs() + 60 * 60;
#[cfg(not(feature = "std"))]
let payment_expiry_secs = 60 * 60;
- let mut invoice_features = InvoiceFeatures::empty();
+ let mut invoice_features = Bolt11InvoiceFeatures::empty();
invoice_features.set_variable_length_onion_required();
invoice_features.set_payment_secret_required();
invoice_features.set_basic_mpp_optional();
.with_expiry_time(payment_expiry_secs as u64)
.with_bolt11_features(invoice_features).unwrap();
let mut route_params = RouteParameters {
- payment_params,
- final_value_msat: amt_msat,
+ payment_params, final_value_msat: amt_msat, max_total_routing_fee_msat: Some(500_000),
};
let mut route = Route {
channel_features: nodes[1].node.channel_features(),
fee_msat: 0,
cltv_expiry_delta: 100,
+ maybe_announced_channel: true,
}, RouteHop {
pubkey: nodes[3].node.get_our_node_id(),
node_features: nodes[2].node.node_features(),
channel_features: nodes[2].node.channel_features(),
fee_msat: amt_msat / 1000,
cltv_expiry_delta: 100,
+ maybe_announced_channel: true,
}], blinded_tail: None },
Path { hops: vec![RouteHop {
pubkey: nodes[2].node.get_our_node_id(),
channel_features: nodes[2].node.channel_features(),
fee_msat: 100_000,
cltv_expiry_delta: 100,
+ maybe_announced_channel: true,
}, RouteHop {
pubkey: nodes[3].node.get_our_node_id(),
node_features: nodes[3].node.node_features(),
channel_features: nodes[3].node.channel_features(),
fee_msat: amt_msat - amt_msat / 1000,
cltv_expiry_delta: 100,
+ maybe_announced_channel: true,
}], blinded_tail: None }
],
- payment_params: Some(PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), TEST_FINAL_CLTV)),
+ route_params: Some(RouteParameters {
+ payment_params: PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), TEST_FINAL_CLTV),
+ final_value_msat: amt_msat - amt_msat / 1000,
+ max_total_routing_fee_msat: Some(500_000),
+ }),
};
nodes[0].router.expect_find_route(route_params.clone(), Ok(route.clone()));
let mut new_route_params = route_params.clone();
previously_failed_channels.push(route.paths[0].hops[1].short_channel_id);
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;
nodes[0].router.expect_find_route(new_route_params, Ok(route.clone()));
}
}
-fn do_no_missing_sent_on_midpoint_reload(persist_manager_with_payment: bool) {
+fn do_no_missing_sent_on_reload(persist_manager_with_payment: bool, at_midpoint: bool) {
// Test that if we reload in the middle of an HTLC claim commitment signed dance we'll still
// receive the PaymentSent event even if the ChannelManager had no idea about the payment when
// it was last persisted.
let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
- let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
let (persister_a, persister_b, persister_c);
let (chain_monitor_a, chain_monitor_b, chain_monitor_c);
+ let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
let (nodes_0_deserialized, nodes_0_deserialized_b, nodes_0_deserialized_c);
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
nodes_0_serialized = nodes[0].node.encode();
}
- let (our_payment_preimage, our_payment_hash, _) = route_payment(&nodes[0], &[&nodes[1]], 1_000_000);
+ let (our_payment_preimage, our_payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1]], 1_000_000);
if persist_manager_with_payment {
nodes_0_serialized = nodes[0].node.encode();
check_added_monitors!(nodes[1], 1);
expect_payment_claimed!(nodes[1], our_payment_hash, 1_000_000);
- let 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(), &updates.update_fulfill_htlcs[0]);
- nodes[0].node.handle_commitment_signed(&nodes[1].node.get_our_node_id(), &updates.commitment_signed);
- check_added_monitors!(nodes[0], 1);
+ if at_midpoint {
+ let 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(), &updates.update_fulfill_htlcs[0]);
+ nodes[0].node.handle_commitment_signed(&nodes[1].node.get_our_node_id(), &updates.commitment_signed);
+ check_added_monitors!(nodes[0], 1);
+ } else {
+ let htlc_fulfill_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(), &htlc_fulfill_updates.update_fulfill_htlcs[0]);
+ commitment_signed_dance!(nodes[0], nodes[1], htlc_fulfill_updates.commitment_signed, false);
+ // Ignore the PaymentSent event which is now pending on nodes[0] - if we were to handle it we'd
+ // be expected to ignore the eventual conflicting PaymentFailed, but by not looking at it we
+ // expect to get the PaymentSent again later.
+ check_added_monitors(&nodes[0], 0);
+ }
// The ChannelMonitor should always be the latest version, as we're required to persist it
// during the commitment signed handling.
#[test]
fn no_missing_sent_on_midpoint_reload() {
- do_no_missing_sent_on_midpoint_reload(false);
- do_no_missing_sent_on_midpoint_reload(true);
+ do_no_missing_sent_on_reload(false, true);
+ do_no_missing_sent_on_reload(true, true);
+}
+
+#[test]
+fn no_missing_sent_on_reload() {
+ do_no_missing_sent_on_reload(false, false);
+ do_no_missing_sent_on_reload(true, false);
}
fn do_claim_from_closed_chan(fail_payment: bool) {
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 {
- payment_params: PaymentParameters::from_node_id(nodes[3].node.get_our_node_id(), TEST_FINAL_CLTV)
+ 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(),
- final_value_msat: 10_000_000,
- };
+ 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();
+ None, nodes[0].node.compute_inflight_htlcs()).unwrap();
// Make sure the route is ordered as the B->D path before C->D
route.paths.sort_by(|a, _| if a.hops[0].pubkey == nodes[1].node.get_our_node_id() {
std::cmp::Ordering::Less } else { std::cmp::Ordering::Greater });
pass_failed_payment_back(&nodes[0], &[&[&nodes[1], &nodes[3]], &[&nodes[2], &nodes[3]]], false, payment_hash, PaymentFailureReason::RecipientRejected);
} else {
nodes[1].node.force_close_broadcasting_latest_txn(&chan_bd, &nodes[3].node.get_our_node_id()).unwrap();
- check_closed_event(&nodes[1], 1, ClosureReason::HolderForceClosed, false);
+ check_closed_event!(&nodes[1], 1, ClosureReason::HolderForceClosed, false,
+ [nodes[3].node.get_our_node_id()], 1000000);
check_closed_broadcast(&nodes[1], 1, true);
let bs_tx = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
assert_eq!(bs_tx.len(), 1);
mine_transaction(&nodes[3], &bs_tx[0]);
check_added_monitors(&nodes[3], 1);
check_closed_broadcast(&nodes[3], 1, true);
- check_closed_event(&nodes[3], 1, ClosureReason::CommitmentTxConfirmed, false);
+ check_closed_event!(&nodes[3], 1, ClosureReason::CommitmentTxConfirmed, false,
+ [nodes[1].node.get_our_node_id()], 1000000);
nodes[3].node.claim_funds(payment_preimage);
check_added_monitors(&nodes[3], 2);
do_claim_from_closed_chan(false);
}
+#[test]
+fn test_custom_tlvs_basic() {
+ do_test_custom_tlvs(false, false, false);
+ do_test_custom_tlvs(true, false, false);
+}
+
+#[test]
+fn test_custom_tlvs_explicit_claim() {
+ // Test that when receiving even custom TLVs the user must explicitly accept in case they
+ // are unknown.
+ do_test_custom_tlvs(false, true, false);
+ do_test_custom_tlvs(false, true, true);
+}
+
+fn do_test_custom_tlvs(spontaneous: bool, even_tlvs: bool, known_tlvs: bool) {
+ let chanmon_cfgs = create_chanmon_cfgs(2);
+ let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
+ let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None; 2]);
+ let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+
+ create_announced_chan_between_nodes(&nodes, 0, 1);
+
+ let amt_msat = 100_000;
+ let (mut route, our_payment_hash, our_payment_preimage, our_payment_secret) = get_route_and_payment_hash!(&nodes[0], &nodes[1], amt_msat);
+ let payment_id = PaymentId(our_payment_hash.0);
+ let custom_tlvs = vec![
+ (if even_tlvs { 5482373482 } else { 5482373483 }, vec![1, 2, 3, 4]),
+ (5482373487, vec![0x42u8; 16]),
+ ];
+ let onion_fields = RecipientOnionFields {
+ payment_secret: if spontaneous { None } else { Some(our_payment_secret) },
+ payment_metadata: None,
+ custom_tlvs: custom_tlvs.clone()
+ };
+ if spontaneous {
+ nodes[0].node.send_spontaneous_payment(&route, Some(our_payment_preimage), onion_fields, payment_id).unwrap();
+ } else {
+ nodes[0].node.send_payment_with_route(&route, our_payment_hash, onion_fields, payment_id).unwrap();
+ }
+ check_added_monitors(&nodes[0], 1);
+
+ let mut events = nodes[0].node.get_and_clear_pending_msg_events();
+ let ev = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events);
+ let mut payment_event = SendEvent::from_event(ev);
+
+ nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &payment_event.msgs[0]);
+ check_added_monitors!(&nodes[1], 0);
+ commitment_signed_dance!(nodes[1], nodes[0], payment_event.commitment_msg, false);
+ expect_pending_htlcs_forwardable!(nodes[1]);
+
+ let events = nodes[1].node.get_and_clear_pending_events();
+ assert_eq!(events.len(), 1);
+ match events[0] {
+ Event::PaymentClaimable { ref onion_fields, .. } => {
+ assert_eq!(onion_fields.clone().unwrap().custom_tlvs().clone(), custom_tlvs);
+ },
+ _ => panic!("Unexpected event"),
+ }
+
+ match (known_tlvs, even_tlvs) {
+ (true, _) => {
+ nodes[1].node.claim_funds_with_known_custom_tlvs(our_payment_preimage);
+ let expected_total_fee_msat = pass_claimed_payment_along_route(&nodes[0], &[&[&nodes[1]]], &[0; 1], false, our_payment_preimage);
+ expect_payment_sent!(&nodes[0], our_payment_preimage, Some(expected_total_fee_msat));
+ },
+ (false, false) => {
+ claim_payment(&nodes[0], &[&nodes[1]], our_payment_preimage);
+ },
+ (false, true) => {
+ nodes[1].node.claim_funds(our_payment_preimage);
+ let expected_destinations = vec![HTLCDestination::FailedPayment { payment_hash: our_payment_hash }];
+ expect_pending_htlcs_forwardable_and_htlc_handling_failed!(nodes[1], expected_destinations);
+ pass_failed_payment_back(&nodes[0], &[&[&nodes[1]]], false, our_payment_hash, PaymentFailureReason::RecipientRejected);
+ }
+ }
+}
+
+#[test]
+fn test_retry_custom_tlvs() {
+ // Test that custom TLVs are successfully sent on retries
+ let chanmon_cfgs = create_chanmon_cfgs(3);
+ let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
+ let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
+ let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
+
+ create_announced_chan_between_nodes(&nodes, 0, 1);
+ let (chan_2_update, _, chan_2_id, _) = create_announced_chan_between_nodes(&nodes, 2, 1);
+
+ // Rebalance
+ send_payment(&nodes[2], &vec!(&nodes[1])[..], 1_500_000);
+
+ let amt_msat = 1_000_000;
+ let (route, payment_hash, payment_preimage, payment_secret) =
+ get_route_and_payment_hash!(nodes[0], nodes[2], amt_msat);
+
+ // Initiate the payment
+ let payment_id = PaymentId(payment_hash.0);
+ let mut route_params = route.route_params.clone().unwrap();
+
+ let custom_tlvs = vec![((1 << 16) + 1, vec![0x42u8; 16])];
+ let onion_fields = RecipientOnionFields::secret_only(payment_secret);
+ let onion_fields = onion_fields.with_custom_tlvs(custom_tlvs.clone()).unwrap();
+
+ nodes[0].router.expect_find_route(route_params.clone(), Ok(route.clone()));
+ nodes[0].node.send_payment(payment_hash, onion_fields,
+ payment_id, route_params.clone(), Retry::Attempts(1)).unwrap();
+ check_added_monitors!(nodes[0], 1); // one monitor per path
+
+ // Add the HTLC along the first hop.
+ let htlc_updates = get_htlc_update_msgs(&nodes[0], &nodes[1].node.get_our_node_id());
+ let msgs::CommitmentUpdate { update_add_htlcs, commitment_signed, .. } = htlc_updates;
+ assert_eq!(update_add_htlcs.len(), 1);
+ nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &update_add_htlcs[0]);
+ commitment_signed_dance!(nodes[1], nodes[0], commitment_signed, false);
+
+ // Attempt to forward the payment and complete the path's failure.
+ 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_2_id
+ }]);
+ check_added_monitors!(nodes[1], 1);
+
+ let htlc_updates = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
+ let msgs::CommitmentUpdate { update_fail_htlcs, commitment_signed, .. } = htlc_updates;
+ assert_eq!(update_fail_htlcs.len(), 1);
+ nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &update_fail_htlcs[0]);
+ commitment_signed_dance!(nodes[0], nodes[1], commitment_signed, false);
+
+ let mut events = nodes[0].node.get_and_clear_pending_events();
+ match events[1] {
+ Event::PendingHTLCsForwardable { .. } => {},
+ _ => panic!("Unexpected event")
+ }
+ events.remove(1);
+ expect_payment_failed_conditions_event(events, payment_hash, false,
+ PaymentFailedConditions::new().mpp_parts_remain());
+
+ // Rebalance the channel so the retry of the payment can succeed.
+ send_payment(&nodes[2], &vec!(&nodes[1])[..], 1_500_000);
+
+ // Retry the payment and make sure it succeeds
+ route_params.payment_params.previously_failed_channels.push(chan_2_update.contents.short_channel_id);
+ nodes[0].router.expect_find_route(route_params, Ok(route));
+ nodes[0].node.process_pending_htlc_forwards();
+ check_added_monitors!(nodes[0], 1);
+ let mut events = nodes[0].node.get_and_clear_pending_msg_events();
+ assert_eq!(events.len(), 1);
+ let payment_claimable = pass_along_path(&nodes[0], &[&nodes[1], &nodes[2]], 1_000_000,
+ payment_hash, Some(payment_secret), events.pop().unwrap(), true, None).unwrap();
+ match payment_claimable {
+ Event::PaymentClaimable { onion_fields, .. } => {
+ assert_eq!(onion_fields.unwrap().custom_tlvs(), &custom_tlvs);
+ },
+ _ => panic!("Unexpected event"),
+ };
+ claim_payment_along_route(&nodes[0], &[&[&nodes[1], &nodes[2]]], false, payment_preimage);
+}
+
+#[test]
+fn test_custom_tlvs_consistency() {
+ let even_type_1 = 1 << 16;
+ let odd_type_1 = (1 << 16)+ 1;
+ let even_type_2 = (1 << 16) + 2;
+ let odd_type_2 = (1 << 16) + 3;
+ let value_1 = || vec![1, 2, 3, 4];
+ let differing_value_1 = || vec![1, 2, 3, 5];
+ let value_2 = || vec![42u8; 16];
+
+ // Drop missing odd tlvs
+ do_test_custom_tlvs_consistency(
+ vec![(odd_type_1, value_1()), (odd_type_2, value_2())],
+ vec![(odd_type_1, value_1())],
+ Some(vec![(odd_type_1, value_1())]),
+ );
+ // Drop non-matching odd tlvs
+ do_test_custom_tlvs_consistency(
+ vec![(odd_type_1, value_1()), (odd_type_2, value_2())],
+ vec![(odd_type_1, differing_value_1()), (odd_type_2, value_2())],
+ Some(vec![(odd_type_2, value_2())]),
+ );
+ // Fail missing even tlvs
+ do_test_custom_tlvs_consistency(
+ vec![(odd_type_1, value_1()), (even_type_2, value_2())],
+ vec![(odd_type_1, value_1())],
+ None,
+ );
+ // Fail non-matching even tlvs
+ do_test_custom_tlvs_consistency(
+ vec![(even_type_1, value_1()), (odd_type_2, value_2())],
+ vec![(even_type_1, differing_value_1()), (odd_type_2, value_2())],
+ None,
+ );
+}
+
+fn do_test_custom_tlvs_consistency(first_tlvs: Vec<(u64, Vec<u8>)>, second_tlvs: Vec<(u64, Vec<u8>)>,
+ expected_receive_tlvs: Option<Vec<(u64, Vec<u8>)>>) {
+
+ let chanmon_cfgs = create_chanmon_cfgs(4);
+ let node_cfgs = create_node_cfgs(4, &chanmon_cfgs);
+ let node_chanmgrs = create_node_chanmgrs(4, &node_cfgs, &[None, None, None, None]);
+ let nodes = create_network(4, &node_cfgs, &node_chanmgrs);
+
+ create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100_000, 0);
+ create_announced_chan_between_nodes_with_value(&nodes, 0, 2, 100_000, 0);
+ create_announced_chan_between_nodes_with_value(&nodes, 1, 3, 100_000, 0);
+ 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();
+ 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, _| {
+ // Sort the path so that the path through nodes[1] comes first
+ if path_a.hops[0].pubkey == nodes[1].node.get_our_node_id() {
+ core::cmp::Ordering::Less } else { core::cmp::Ordering::Greater }
+ });
+
+ let (our_payment_preimage, our_payment_hash, our_payment_secret) = get_payment_preimage_hash!(&nodes[3]);
+ let payment_id = PaymentId([42; 32]);
+ let amt_msat = 15_000_000;
+
+ // Send first part
+ let onion_fields = RecipientOnionFields {
+ payment_secret: Some(our_payment_secret),
+ payment_metadata: None,
+ custom_tlvs: first_tlvs
+ };
+ let session_privs = nodes[0].node.test_add_new_pending_payment(our_payment_hash,
+ onion_fields.clone(), payment_id, &route).unwrap();
+ let cur_height = nodes[0].best_block_info().1;
+ nodes[0].node.test_send_payment_along_path(&route.paths[0], &our_payment_hash,
+ onion_fields.clone(), amt_msat, cur_height, payment_id,
+ &None, session_privs[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);
+ pass_along_path(&nodes[0], &[&nodes[1], &nodes[3]], amt_msat, our_payment_hash,
+ Some(our_payment_secret), events.pop().unwrap(), false, None);
+ }
+ assert!(nodes[3].node.get_and_clear_pending_events().is_empty());
+
+ // Send second part
+ let onion_fields = RecipientOnionFields {
+ payment_secret: Some(our_payment_secret),
+ payment_metadata: None,
+ custom_tlvs: second_tlvs
+ };
+ nodes[0].node.test_send_payment_along_path(&route.paths[1], &our_payment_hash,
+ onion_fields.clone(), amt_msat, cur_height, payment_id, &None, session_privs[1]).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 payment_event = SendEvent::from_event(events.pop().unwrap());
+
+ nodes[2].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &payment_event.msgs[0]);
+ commitment_signed_dance!(nodes[2], nodes[0], payment_event.commitment_msg, false);
+
+ expect_pending_htlcs_forwardable!(nodes[2]);
+ check_added_monitors!(nodes[2], 1);
+
+ let mut events = nodes[2].node.get_and_clear_pending_msg_events();
+ assert_eq!(events.len(), 1);
+ let payment_event = SendEvent::from_event(events.pop().unwrap());
+
+ nodes[3].node.handle_update_add_htlc(&nodes[2].node.get_our_node_id(), &payment_event.msgs[0]);
+ check_added_monitors!(nodes[3], 0);
+ commitment_signed_dance!(nodes[3], nodes[2], payment_event.commitment_msg, true, true);
+ }
+ expect_pending_htlcs_forwardable_ignore!(nodes[3]);
+ nodes[3].node.process_pending_htlc_forwards();
+
+ if let Some(expected_tlvs) = expected_receive_tlvs {
+ // Claim and match expected
+ let events = nodes[3].node.get_and_clear_pending_events();
+ assert_eq!(events.len(), 1);
+ match events[0] {
+ Event::PaymentClaimable { ref onion_fields, .. } => {
+ assert_eq!(onion_fields.clone().unwrap().custom_tlvs, expected_tlvs);
+ },
+ _ => panic!("Unexpected event"),
+ }
+
+ do_claim_payment_along_route(&nodes[0], &[&[&nodes[1], &nodes[3]], &[&nodes[2], &nodes[3]]],
+ false, our_payment_preimage);
+ expect_payment_sent(&nodes[0], our_payment_preimage, Some(Some(2000)), true, true);
+ } else {
+ // Expect fail back
+ let expected_destinations = vec![HTLCDestination::FailedPayment { payment_hash: our_payment_hash }];
+ expect_pending_htlcs_forwardable_and_htlc_handling_failed!(nodes[3], expected_destinations);
+ check_added_monitors!(nodes[3], 1);
+
+ let fail_updates_1 = get_htlc_update_msgs!(nodes[3], nodes[2].node.get_our_node_id());
+ nodes[2].node.handle_update_fail_htlc(&nodes[3].node.get_our_node_id(), &fail_updates_1.update_fail_htlcs[0]);
+ commitment_signed_dance!(nodes[2], nodes[3], fail_updates_1.commitment_signed, false);
+
+ expect_pending_htlcs_forwardable_and_htlc_handling_failed!(nodes[2], vec![
+ HTLCDestination::NextHopChannel {
+ node_id: Some(nodes[3].node.get_our_node_id()),
+ channel_id: chan_2_3.2
+ }]);
+ check_added_monitors!(nodes[2], 1);
+
+ let fail_updates_2 = get_htlc_update_msgs!(nodes[2], nodes[0].node.get_our_node_id());
+ nodes[0].node.handle_update_fail_htlc(&nodes[2].node.get_our_node_id(), &fail_updates_2.update_fail_htlcs[0]);
+ commitment_signed_dance!(nodes[0], nodes[2], fail_updates_2.commitment_signed, false);
+
+ expect_payment_failed_conditions(&nodes[0], our_payment_hash, true,
+ PaymentFailedConditions::new().mpp_parts_remain());
+ }
+}
+
fn do_test_payment_metadata_consistency(do_reload: bool, do_modify: bool) {
// Check that a payment metadata received on one HTLC that doesn't match the one received on
// another results in the HTLC being rejected.
// modified payment metadata, which will in turn result in it being failed by the recipient.
let chanmon_cfgs = create_chanmon_cfgs(4);
let node_cfgs = create_node_cfgs(4, &chanmon_cfgs);
+ let persister;
+ let new_chain_monitor;
+
let mut config = test_default_channel_config();
config.channel_handshake_config.max_inbound_htlc_value_in_flight_percent_of_channel = 50;
let node_chanmgrs = create_node_chanmgrs(4, &node_cfgs, &[None, Some(config), Some(config), Some(config)]);
-
- let persister;
- let new_chain_monitor;
let nodes_0_deserialized;
let mut nodes = create_network(4, &node_cfgs, &node_chanmgrs);
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();
- let mut route_params = RouteParameters {
- payment_params,
- final_value_msat: amt_msat,
- };
+ 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].
nodes[0].node.send_payment(payment_hash, RecipientOnionFields {
- payment_secret: Some(payment_secret), payment_metadata: Some(payment_metadata),
+ payment_secret: Some(payment_secret), payment_metadata: Some(payment_metadata), custom_tlvs: vec![],
}, payment_id, route_params.clone(), Retry::Attempts(1)).unwrap();
check_added_monitors!(nodes[0], 2);
reload_node!(nodes[3], config, &nodes[3].node.encode(), &[&mon_bd, &mon_cd],
persister, new_chain_monitor, nodes_0_deserialized);
nodes[1].node.peer_disconnected(&nodes[3].node.get_our_node_id());
- reconnect_nodes(&nodes[1], &nodes[3], (false, false), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (false, false));
+ reconnect_nodes(ReconnectArgs::new(&nodes[1], &nodes[3]));
}
- reconnect_nodes(&nodes[2], &nodes[3], (true, true), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (false, false));
+ let mut reconnect_args = ReconnectArgs::new(&nodes[2], &nodes[3]);
+ reconnect_args.send_channel_ready = (true, true);
+ reconnect_nodes(reconnect_args);
// Create a new channel between C and D as A will refuse to retry on the existing one because
// it just failed.
use crate::sign::{KeysManager, NodeSigner, Recipient};
use crate::events::{MessageSendEvent, MessageSendEventsProvider, OnionMessageProvider};
+use crate::ln::ChannelId;
use crate::ln::features::{InitFeatures, NodeFeatures};
use crate::ln::msgs;
-use crate::ln::msgs::{ChannelMessageHandler, LightningError, NetAddress, OnionMessageHandler, RoutingMessageHandler};
+use crate::ln::msgs::{ChannelMessageHandler, LightningError, SocketAddress, OnionMessageHandler, RoutingMessageHandler};
use crate::ln::channelmanager::{SimpleArcChannelManager, SimpleRefChannelManager};
use crate::util::ser::{VecWriter, Writeable, Writer};
use crate::ln::peer_channel_encryptor::{PeerChannelEncryptor,NextNoiseStep};
pub fn new() -> Self {
Self { message_queue: Mutex::new(Vec::new()) }
}
- fn push_error(&self, node_id: &PublicKey, channel_id: [u8; 32]) {
+ fn push_error(&self, node_id: &PublicKey, channel_id: ChannelId) {
self.message_queue.lock().unwrap().push(MessageSendEvent::HandleError {
action: msgs::ErrorAction::SendErrorMessage {
msg: msgs::ErrorMessage { channel_id, data: "We do not support channel messages, sorry.".to_owned() },
/// handshake and can talk to this peer normally (though use [`Peer::handshake_complete`] to
/// check this.
their_features: Option<InitFeatures>,
- their_net_address: Option<NetAddress>,
+ their_socket_address: Option<SocketAddress>,
pending_outbound_buffer: LinkedList<Vec<u8>>,
pending_outbound_buffer_first_msg_offset: usize,
/// A generic trait which is implemented for all [`PeerManager`]s. This makes bounding functions or
/// structs on any [`PeerManager`] much simpler as only this trait is needed as a bound, rather
/// than the full set of bounds on [`PeerManager`] itself.
+///
+/// This is not exported to bindings users as general cover traits aren't useful in other
+/// languages.
#[allow(missing_docs)]
pub trait APeerManager {
type Descriptor: SocketDescriptor;
/// A function used to filter out local or private addresses
/// <https://www.iana.org./assignments/ipv4-address-space/ipv4-address-space.xhtml>
/// <https://www.iana.org/assignments/ipv6-address-space/ipv6-address-space.xhtml>
-fn filter_addresses(ip_address: Option<NetAddress>) -> Option<NetAddress> {
+fn filter_addresses(ip_address: Option<SocketAddress>) -> Option<SocketAddress> {
match ip_address{
// For IPv4 range 10.0.0.0 - 10.255.255.255 (10/8)
- Some(NetAddress::IPv4{addr: [10, _, _, _], port: _}) => None,
+ Some(SocketAddress::TcpIpV4{addr: [10, _, _, _], port: _}) => None,
// For IPv4 range 0.0.0.0 - 0.255.255.255 (0/8)
- Some(NetAddress::IPv4{addr: [0, _, _, _], port: _}) => None,
+ Some(SocketAddress::TcpIpV4{addr: [0, _, _, _], port: _}) => None,
// For IPv4 range 100.64.0.0 - 100.127.255.255 (100.64/10)
- Some(NetAddress::IPv4{addr: [100, 64..=127, _, _], port: _}) => None,
+ Some(SocketAddress::TcpIpV4{addr: [100, 64..=127, _, _], port: _}) => None,
// For IPv4 range 127.0.0.0 - 127.255.255.255 (127/8)
- Some(NetAddress::IPv4{addr: [127, _, _, _], port: _}) => None,
+ Some(SocketAddress::TcpIpV4{addr: [127, _, _, _], port: _}) => None,
// For IPv4 range 169.254.0.0 - 169.254.255.255 (169.254/16)
- Some(NetAddress::IPv4{addr: [169, 254, _, _], port: _}) => None,
+ Some(SocketAddress::TcpIpV4{addr: [169, 254, _, _], port: _}) => None,
// For IPv4 range 172.16.0.0 - 172.31.255.255 (172.16/12)
- Some(NetAddress::IPv4{addr: [172, 16..=31, _, _], port: _}) => None,
+ Some(SocketAddress::TcpIpV4{addr: [172, 16..=31, _, _], port: _}) => None,
// For IPv4 range 192.168.0.0 - 192.168.255.255 (192.168/16)
- Some(NetAddress::IPv4{addr: [192, 168, _, _], port: _}) => None,
+ Some(SocketAddress::TcpIpV4{addr: [192, 168, _, _], port: _}) => None,
// For IPv4 range 192.88.99.0 - 192.88.99.255 (192.88.99/24)
- Some(NetAddress::IPv4{addr: [192, 88, 99, _], port: _}) => None,
+ Some(SocketAddress::TcpIpV4{addr: [192, 88, 99, _], port: _}) => None,
// For IPv6 range 2000:0000:0000:0000:0000:0000:0000:0000 - 3fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff (2000::/3)
- Some(NetAddress::IPv6{addr: [0x20..=0x3F, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _], port: _}) => ip_address,
+ Some(SocketAddress::TcpIpV6{addr: [0x20..=0x3F, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _], port: _}) => ip_address,
// For remaining addresses
- Some(NetAddress::IPv6{addr: _, port: _}) => None,
+ Some(SocketAddress::TcpIpV6{addr: _, port: _}) => None,
Some(..) => ip_address,
None => None,
}
///
/// The returned `Option`s will only be `Some` if an address had been previously given via
/// [`Self::new_outbound_connection`] or [`Self::new_inbound_connection`].
- pub fn get_peer_node_ids(&self) -> Vec<(PublicKey, Option<NetAddress>)> {
+ pub fn get_peer_node_ids(&self) -> Vec<(PublicKey, Option<SocketAddress>)> {
let peers = self.peers.read().unwrap();
peers.values().filter_map(|peer_mutex| {
let p = peer_mutex.lock().unwrap();
if !p.handshake_complete() {
return None;
}
- Some((p.their_node_id.unwrap().0, p.their_net_address.clone()))
+ Some((p.their_node_id.unwrap().0, p.their_socket_address.clone()))
}).collect()
}
/// [`socket_disconnected`].
///
/// [`socket_disconnected`]: PeerManager::socket_disconnected
- pub fn new_outbound_connection(&self, their_node_id: PublicKey, descriptor: Descriptor, remote_network_address: Option<NetAddress>) -> Result<Vec<u8>, PeerHandleError> {
+ pub fn new_outbound_connection(&self, their_node_id: PublicKey, descriptor: Descriptor, remote_network_address: Option<SocketAddress>) -> Result<Vec<u8>, PeerHandleError> {
let mut peer_encryptor = PeerChannelEncryptor::new_outbound(their_node_id.clone(), self.get_ephemeral_key());
let res = peer_encryptor.get_act_one(&self.secp_ctx).to_vec();
let pending_read_buffer = [0; 50].to_vec(); // Noise act two is 50 bytes
channel_encryptor: peer_encryptor,
their_node_id: None,
their_features: None,
- their_net_address: remote_network_address,
+ their_socket_address: remote_network_address,
pending_outbound_buffer: LinkedList::new(),
pending_outbound_buffer_first_msg_offset: 0,
/// [`socket_disconnected`].
///
/// [`socket_disconnected`]: PeerManager::socket_disconnected
- pub fn new_inbound_connection(&self, descriptor: Descriptor, remote_network_address: Option<NetAddress>) -> Result<(), PeerHandleError> {
+ pub fn new_inbound_connection(&self, descriptor: Descriptor, remote_network_address: Option<SocketAddress>) -> Result<(), PeerHandleError> {
let peer_encryptor = PeerChannelEncryptor::new_inbound(&self.node_signer);
let pending_read_buffer = [0; 50].to_vec(); // Noise act one is 50 bytes
channel_encryptor: peer_encryptor,
their_node_id: None,
their_features: None,
- their_net_address: remote_network_address,
+ their_socket_address: remote_network_address,
pending_outbound_buffer: LinkedList::new(),
pending_outbound_buffer_first_msg_offset: 0,
insert_node_id!();
let features = self.init_features(&their_node_id);
let networks = self.message_handler.chan_handler.get_genesis_hashes();
- let resp = msgs::Init { features, networks, remote_network_address: filter_addresses(peer.their_net_address.clone()) };
+ 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;
},
insert_node_id!();
let features = self.init_features(&their_node_id);
let networks = self.message_handler.chan_handler.get_genesis_hashes();
- let resp = msgs::Init { features, networks, remote_network_address: filter_addresses(peer.their_net_address.clone()) };
+ 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;
},
}
(msgs::DecodeError::UnsupportedCompression, _) => {
log_gossip!(self.logger, "We don't support zlib-compressed message fields, sending a warning and ignoring message");
- self.enqueue_message(peer, &msgs::WarningMessage { channel_id: [0; 32], data: "Unsupported message compression: zlib".to_owned() });
+ self.enqueue_message(peer, &msgs::WarningMessage { channel_id: ChannelId::new_zero(), data: "Unsupported message compression: zlib".to_owned() });
continue;
}
(_, Some(ty)) if is_gossip_msg(ty) => {
log_gossip!(self.logger, "Got an invalid value while deserializing a gossip message");
self.enqueue_message(peer, &msgs::WarningMessage {
- channel_id: [0; 32],
+ channel_id: ChannelId::new_zero(),
data: format!("Unreadable/bogus gossip message of type {}", ty),
});
continue;
wire::Message::Error(msg) => {
log_debug!(self.logger, "Got Err message from {}: {}", log_pubkey!(their_node_id), PrintableString(&msg.data));
self.message_handler.chan_handler.handle_error(&their_node_id, &msg);
- if msg.channel_id == [0; 32] {
+ if msg.channel_id.is_zero() {
return Err(PeerHandleError { }.into());
}
},
MessageSendEvent::SendAcceptChannel { ref node_id, ref msg } => {
log_debug!(self.logger, "Handling SendAcceptChannel event in peer_handler for node {} for channel {}",
log_pubkey!(node_id),
- log_bytes!(msg.temporary_channel_id));
+ &msg.temporary_channel_id);
self.enqueue_message(&mut *get_peer_for_forwarding!(node_id), msg);
},
MessageSendEvent::SendAcceptChannelV2 { ref node_id, ref msg } => {
log_debug!(self.logger, "Handling SendAcceptChannelV2 event in peer_handler for node {} for channel {}",
log_pubkey!(node_id),
- log_bytes!(msg.temporary_channel_id));
+ &msg.temporary_channel_id);
self.enqueue_message(&mut *get_peer_for_forwarding!(node_id), msg);
},
MessageSendEvent::SendOpenChannel { ref node_id, ref msg } => {
log_debug!(self.logger, "Handling SendOpenChannel event in peer_handler for node {} for channel {}",
log_pubkey!(node_id),
- log_bytes!(msg.temporary_channel_id));
+ &msg.temporary_channel_id);
self.enqueue_message(&mut *get_peer_for_forwarding!(node_id), msg);
},
MessageSendEvent::SendOpenChannelV2 { ref node_id, ref msg } => {
log_debug!(self.logger, "Handling SendOpenChannelV2 event in peer_handler for node {} for channel {}",
log_pubkey!(node_id),
- log_bytes!(msg.temporary_channel_id));
+ &msg.temporary_channel_id);
self.enqueue_message(&mut *get_peer_for_forwarding!(node_id), msg);
},
MessageSendEvent::SendFundingCreated { ref node_id, ref msg } => {
log_debug!(self.logger, "Handling SendFundingCreated event in peer_handler for node {} for channel {} (which becomes {})",
log_pubkey!(node_id),
- log_bytes!(msg.temporary_channel_id),
+ &msg.temporary_channel_id,
log_funding_channel_id!(msg.funding_txid, msg.funding_output_index));
// TODO: If the peer is gone we should generate a DiscardFunding event
// indicating to the wallet that they should just throw away this funding transaction
MessageSendEvent::SendFundingSigned { ref node_id, ref msg } => {
log_debug!(self.logger, "Handling SendFundingSigned event in peer_handler for node {} for channel {}",
log_pubkey!(node_id),
- log_bytes!(msg.channel_id));
+ &msg.channel_id);
self.enqueue_message(&mut *get_peer_for_forwarding!(node_id), msg);
},
MessageSendEvent::SendChannelReady { ref node_id, ref msg } => {
log_debug!(self.logger, "Handling SendChannelReady event in peer_handler for node {} for channel {}",
log_pubkey!(node_id),
- log_bytes!(msg.channel_id));
+ &msg.channel_id);
self.enqueue_message(&mut *get_peer_for_forwarding!(node_id), msg);
},
MessageSendEvent::SendTxAddInput { ref node_id, ref msg } => {
log_debug!(self.logger, "Handling SendTxAddInput event in peer_handler for node {} for channel {}",
log_pubkey!(node_id),
- log_bytes!(msg.channel_id));
+ &msg.channel_id);
self.enqueue_message(&mut *get_peer_for_forwarding!(node_id), msg);
},
MessageSendEvent::SendTxAddOutput { ref node_id, ref msg } => {
log_debug!(self.logger, "Handling SendTxAddOutput event in peer_handler for node {} for channel {}",
log_pubkey!(node_id),
- log_bytes!(msg.channel_id));
+ &msg.channel_id);
self.enqueue_message(&mut *get_peer_for_forwarding!(node_id), msg);
},
MessageSendEvent::SendTxRemoveInput { ref node_id, ref msg } => {
log_debug!(self.logger, "Handling SendTxRemoveInput event in peer_handler for node {} for channel {}",
log_pubkey!(node_id),
- log_bytes!(msg.channel_id));
+ &msg.channel_id);
self.enqueue_message(&mut *get_peer_for_forwarding!(node_id), msg);
},
MessageSendEvent::SendTxRemoveOutput { ref node_id, ref msg } => {
log_debug!(self.logger, "Handling SendTxRemoveOutput event in peer_handler for node {} for channel {}",
log_pubkey!(node_id),
- log_bytes!(msg.channel_id));
+ &msg.channel_id);
self.enqueue_message(&mut *get_peer_for_forwarding!(node_id), msg);
},
MessageSendEvent::SendTxComplete { ref node_id, ref msg } => {
log_debug!(self.logger, "Handling SendTxComplete event in peer_handler for node {} for channel {}",
log_pubkey!(node_id),
- log_bytes!(msg.channel_id));
+ &msg.channel_id);
self.enqueue_message(&mut *get_peer_for_forwarding!(node_id), msg);
},
MessageSendEvent::SendTxSignatures { ref node_id, ref msg } => {
log_debug!(self.logger, "Handling SendTxSignatures event in peer_handler for node {} for channel {}",
log_pubkey!(node_id),
- log_bytes!(msg.channel_id));
+ &msg.channel_id);
self.enqueue_message(&mut *get_peer_for_forwarding!(node_id), msg);
},
MessageSendEvent::SendTxInitRbf { ref node_id, ref msg } => {
log_debug!(self.logger, "Handling SendTxInitRbf event in peer_handler for node {} for channel {}",
log_pubkey!(node_id),
- log_bytes!(msg.channel_id));
+ &msg.channel_id);
self.enqueue_message(&mut *get_peer_for_forwarding!(node_id), msg);
},
MessageSendEvent::SendTxAckRbf { ref node_id, ref msg } => {
log_debug!(self.logger, "Handling SendTxAckRbf event in peer_handler for node {} for channel {}",
log_pubkey!(node_id),
- log_bytes!(msg.channel_id));
+ &msg.channel_id);
self.enqueue_message(&mut *get_peer_for_forwarding!(node_id), msg);
},
MessageSendEvent::SendTxAbort { ref node_id, ref msg } => {
log_debug!(self.logger, "Handling SendTxAbort event in peer_handler for node {} for channel {}",
log_pubkey!(node_id),
- log_bytes!(msg.channel_id));
+ &msg.channel_id);
self.enqueue_message(&mut *get_peer_for_forwarding!(node_id), msg);
},
MessageSendEvent::SendAnnouncementSignatures { ref node_id, ref msg } => {
log_debug!(self.logger, "Handling SendAnnouncementSignatures event in peer_handler for node {} for channel {})",
log_pubkey!(node_id),
- log_bytes!(msg.channel_id));
+ &msg.channel_id);
self.enqueue_message(&mut *get_peer_for_forwarding!(node_id), msg);
},
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, ref update_fee, ref commitment_signed } } => {
update_add_htlcs.len(),
update_fulfill_htlcs.len(),
update_fail_htlcs.len(),
- log_bytes!(commitment_signed.channel_id));
+ &commitment_signed.channel_id);
let mut peer = get_peer_for_forwarding!(node_id);
for msg in update_add_htlcs {
self.enqueue_message(&mut *peer, msg);
MessageSendEvent::SendRevokeAndACK { ref node_id, ref msg } => {
log_debug!(self.logger, "Handling SendRevokeAndACK event in peer_handler for node {} for channel {}",
log_pubkey!(node_id),
- log_bytes!(msg.channel_id));
+ &msg.channel_id);
self.enqueue_message(&mut *get_peer_for_forwarding!(node_id), msg);
},
MessageSendEvent::SendClosingSigned { ref node_id, ref msg } => {
log_debug!(self.logger, "Handling SendClosingSigned event in peer_handler for node {} for channel {}",
log_pubkey!(node_id),
- log_bytes!(msg.channel_id));
+ &msg.channel_id);
self.enqueue_message(&mut *get_peer_for_forwarding!(node_id), msg);
},
MessageSendEvent::SendShutdown { ref node_id, ref msg } => {
log_debug!(self.logger, "Handling Shutdown event in peer_handler for node {} for channel {}",
log_pubkey!(node_id),
- log_bytes!(msg.channel_id));
+ &msg.channel_id);
self.enqueue_message(&mut *get_peer_for_forwarding!(node_id), msg);
},
MessageSendEvent::SendChannelReestablish { ref node_id, ref msg } => {
log_debug!(self.logger, "Handling SendChannelReestablish event in peer_handler for node {} for channel {}",
log_pubkey!(node_id),
- log_bytes!(msg.channel_id));
+ &msg.channel_id);
self.enqueue_message(&mut *get_peer_for_forwarding!(node_id), msg);
},
MessageSendEvent::SendChannelAnnouncement { ref node_id, ref msg, ref update_msg } => {
}
},
MessageSendEvent::BroadcastChannelUpdate { msg } => {
- log_debug!(self.logger, "Handling BroadcastChannelUpdate event in peer_handler for short channel id {}", msg.contents.short_channel_id);
+ log_debug!(self.logger, "Handling BroadcastChannelUpdate event in peer_handler for contents {:?}", msg.contents);
match self.message_handler.route_handler.handle_channel_update(&msg) {
Ok(_) | Err(LightningError { action: msgs::ErrorAction::IgnoreDuplicateGossip, .. }) =>
self.forward_broadcast_msg(peers, &wire::Message::ChannelUpdate(msg), None),
// be absurd. We ensure this by checking that at least 100 (our stated public contract on when
// broadcast_node_announcement panics) of the maximum-length addresses would fit in a 64KB
// message...
- const HALF_MESSAGE_IS_ADDRS: u32 = ::core::u16::MAX as u32 / (NetAddress::MAX_LEN as u32 + 1) / 2;
+ const HALF_MESSAGE_IS_ADDRS: u32 = ::core::u16::MAX as u32 / (SocketAddress::MAX_LEN as u32 + 1) / 2;
#[deny(const_err)]
#[allow(dead_code)]
// ...by failing to compile if the number of addresses that would be half of a message is
/// Panics if `addresses` is absurdly large (more than 100).
///
/// [`get_and_clear_pending_msg_events`]: MessageSendEventsProvider::get_and_clear_pending_msg_events
- pub fn broadcast_node_announcement(&self, rgb: [u8; 3], alias: [u8; 32], mut addresses: Vec<NetAddress>) {
+ pub fn broadcast_node_announcement(&self, rgb: [u8; 3], alias: [u8; 32], mut addresses: Vec<SocketAddress>) {
if addresses.len() > 100 {
panic!("More than half the message size was taken up by public addresses!");
}
use crate::sign::{NodeSigner, Recipient};
use crate::events;
use crate::io;
+ use crate::ln::ChannelId;
use crate::ln::features::{InitFeatures, NodeFeatures};
use crate::ln::peer_channel_encryptor::PeerChannelEncryptor;
use crate::ln::peer_handler::{CustomMessageHandler, PeerManager, MessageHandler, SocketDescriptor, IgnoringMessageHandler, filter_addresses};
use crate::ln::{msgs, wire};
- use crate::ln::msgs::{LightningError, NetAddress};
+ use crate::ln::msgs::{LightningError, SocketAddress};
use crate::util::test_utils;
use bitcoin::Network;
fd: 1, outbound_data: Arc::new(Mutex::new(Vec::new())),
disconnect: Arc::new(AtomicBool::new(false)),
};
- let addr_a = NetAddress::IPv4{addr: [127, 0, 0, 1], port: 1000};
+ let addr_a = SocketAddress::TcpIpV4{addr: [127, 0, 0, 1], port: 1000};
let id_b = peer_b.node_signer.get_node_id(Recipient::Node).unwrap();
let mut fd_b = FileDescriptor {
fd: 1, outbound_data: Arc::new(Mutex::new(Vec::new())),
disconnect: Arc::new(AtomicBool::new(false)),
};
- let addr_b = NetAddress::IPv4{addr: [127, 0, 0, 1], port: 1001};
+ let addr_b = SocketAddress::TcpIpV4{addr: [127, 0, 0, 1], port: 1001};
let initial_data = peer_b.new_outbound_connection(id_a, fd_b.clone(), Some(addr_a.clone())).unwrap();
peer_a.new_inbound_connection(fd_a.clone(), Some(addr_b.clone())).unwrap();
assert_eq!(peer_a.read_event(&mut fd_a, &initial_data).unwrap(), false);
fd: $id + ctr * 3, outbound_data: Arc::new(Mutex::new(Vec::new())),
disconnect: Arc::new(AtomicBool::new(false)),
};
- let addr_a = NetAddress::IPv4{addr: [127, 0, 0, 1], port: 1000};
+ let addr_a = SocketAddress::TcpIpV4{addr: [127, 0, 0, 1], port: 1000};
let mut fd_b = FileDescriptor {
fd: $id + ctr * 3, outbound_data: Arc::new(Mutex::new(Vec::new())),
disconnect: Arc::new(AtomicBool::new(false)),
};
- let addr_b = NetAddress::IPv4{addr: [127, 0, 0, 1], port: 1001};
+ let addr_b = SocketAddress::TcpIpV4{addr: [127, 0, 0, 1], port: 1001};
let initial_data = peers[1].new_outbound_connection(id_a, fd_b.clone(), Some(addr_a.clone())).unwrap();
peers[0].new_inbound_connection(fd_a.clone(), Some(addr_b.clone())).unwrap();
if peers[0].read_event(&mut fd_a, &initial_data).is_err() { break; }
.push(crate::events::MessageSendEvent::SendShutdown {
node_id: peers[1].node_signer.get_node_id(Recipient::Node).unwrap(),
msg: msgs::Shutdown {
- channel_id: [0; 32],
+ channel_id: ChannelId::new_zero(),
scriptpubkey: bitcoin::Script::new(),
},
});
.push(crate::events::MessageSendEvent::SendShutdown {
node_id: peers[0].node_signer.get_node_id(Recipient::Node).unwrap(),
msg: msgs::Shutdown {
- channel_id: [0; 32],
+ channel_id: ChannelId::new_zero(),
scriptpubkey: bitcoin::Script::new(),
},
});
fd: 1, outbound_data: Arc::new(Mutex::new(Vec::new())),
disconnect: Arc::new(AtomicBool::new(false)),
};
- let addr_a = NetAddress::IPv4{addr: [127, 0, 0, 1], port: 1000};
+ let addr_a = SocketAddress::TcpIpV4{addr: [127, 0, 0, 1], port: 1000};
let mut fd_b = FileDescriptor {
fd: 1, outbound_data: Arc::new(Mutex::new(Vec::new())),
disconnect: Arc::new(AtomicBool::new(false)),
};
- let addr_b = NetAddress::IPv4{addr: [127, 0, 0, 1], port: 1001};
+ let addr_b = SocketAddress::TcpIpV4{addr: [127, 0, 0, 1], port: 1001};
let initial_data = peer_b.new_outbound_connection(id_a, fd_b.clone(), Some(addr_a.clone())).unwrap();
peer_a.new_inbound_connection(fd_a.clone(), Some(addr_b.clone())).unwrap();
assert_eq!(peer_a.read_event(&mut fd_a, &initial_data).unwrap(), false);
fd: 1, outbound_data: Arc::new(Mutex::new(Vec::new())),
disconnect: Arc::new(AtomicBool::new(false)),
};
- let addr_a = NetAddress::IPv4{addr: [127, 0, 0, 1], port: 1000};
+ let addr_a = SocketAddress::TcpIpV4{addr: [127, 0, 0, 1], port: 1000};
let mut fd_b = FileDescriptor {
fd: 1, outbound_data: Arc::new(Mutex::new(Vec::new())),
disconnect: Arc::new(AtomicBool::new(false)),
};
- let addr_b = NetAddress::IPv4{addr: [127, 0, 0, 1], port: 1001};
+ let addr_b = SocketAddress::TcpIpV4{addr: [127, 0, 0, 1], port: 1001};
let initial_data = peer_b.new_outbound_connection(id_a, fd_b.clone(), Some(addr_a.clone())).unwrap();
peer_a.new_inbound_connection(fd_a.clone(), Some(addr_b.clone())).unwrap();
assert_eq!(peer_a.read_event(&mut fd_a, &initial_data).unwrap(), false);
let their_id = peers[1].node_signer.get_node_id(Recipient::Node).unwrap();
- let msg = msgs::Shutdown { channel_id: [42; 32], scriptpubkey: bitcoin::Script::new() };
+ let msg = msgs::Shutdown { channel_id: ChannelId::from_bytes([42; 32]), scriptpubkey: bitcoin::Script::new() };
a_chan_handler.pending_events.lock().unwrap().push(events::MessageSendEvent::SendShutdown {
node_id: their_id, msg: msg.clone()
});
fd: 3, outbound_data: Arc::new(Mutex::new(Vec::new())),
disconnect: Arc::new(AtomicBool::new(false)),
};
- let addr_dup = NetAddress::IPv4{addr: [127, 0, 0, 1], port: 1003};
+ let addr_dup = SocketAddress::TcpIpV4{addr: [127, 0, 0, 1], port: 1003};
let id_a = cfgs[0].node_signer.get_node_id(Recipient::Node).unwrap();
peers[0].new_inbound_connection(fd_dup.clone(), Some(addr_dup.clone())).unwrap();
// Tests the filter_addresses function.
// For (10/8)
- let ip_address = NetAddress::IPv4{addr: [10, 0, 0, 0], port: 1000};
+ let ip_address = SocketAddress::TcpIpV4{addr: [10, 0, 0, 0], port: 1000};
assert_eq!(filter_addresses(Some(ip_address.clone())), None);
- let ip_address = NetAddress::IPv4{addr: [10, 0, 255, 201], port: 1000};
+ let ip_address = SocketAddress::TcpIpV4{addr: [10, 0, 255, 201], port: 1000};
assert_eq!(filter_addresses(Some(ip_address.clone())), None);
- let ip_address = NetAddress::IPv4{addr: [10, 255, 255, 255], port: 1000};
+ let ip_address = SocketAddress::TcpIpV4{addr: [10, 255, 255, 255], port: 1000};
assert_eq!(filter_addresses(Some(ip_address.clone())), None);
// For (0/8)
- let ip_address = NetAddress::IPv4{addr: [0, 0, 0, 0], port: 1000};
+ let ip_address = SocketAddress::TcpIpV4{addr: [0, 0, 0, 0], port: 1000};
assert_eq!(filter_addresses(Some(ip_address.clone())), None);
- let ip_address = NetAddress::IPv4{addr: [0, 0, 255, 187], port: 1000};
+ let ip_address = SocketAddress::TcpIpV4{addr: [0, 0, 255, 187], port: 1000};
assert_eq!(filter_addresses(Some(ip_address.clone())), None);
- let ip_address = NetAddress::IPv4{addr: [0, 255, 255, 255], port: 1000};
+ let ip_address = SocketAddress::TcpIpV4{addr: [0, 255, 255, 255], port: 1000};
assert_eq!(filter_addresses(Some(ip_address.clone())), None);
// For (100.64/10)
- let ip_address = NetAddress::IPv4{addr: [100, 64, 0, 0], port: 1000};
+ let ip_address = SocketAddress::TcpIpV4{addr: [100, 64, 0, 0], port: 1000};
assert_eq!(filter_addresses(Some(ip_address.clone())), None);
- let ip_address = NetAddress::IPv4{addr: [100, 78, 255, 0], port: 1000};
+ let ip_address = SocketAddress::TcpIpV4{addr: [100, 78, 255, 0], port: 1000};
assert_eq!(filter_addresses(Some(ip_address.clone())), None);
- let ip_address = NetAddress::IPv4{addr: [100, 127, 255, 255], port: 1000};
+ let ip_address = SocketAddress::TcpIpV4{addr: [100, 127, 255, 255], port: 1000};
assert_eq!(filter_addresses(Some(ip_address.clone())), None);
// For (127/8)
- let ip_address = NetAddress::IPv4{addr: [127, 0, 0, 0], port: 1000};
+ let ip_address = SocketAddress::TcpIpV4{addr: [127, 0, 0, 0], port: 1000};
assert_eq!(filter_addresses(Some(ip_address.clone())), None);
- let ip_address = NetAddress::IPv4{addr: [127, 65, 73, 0], port: 1000};
+ let ip_address = SocketAddress::TcpIpV4{addr: [127, 65, 73, 0], port: 1000};
assert_eq!(filter_addresses(Some(ip_address.clone())), None);
- let ip_address = NetAddress::IPv4{addr: [127, 255, 255, 255], port: 1000};
+ let ip_address = SocketAddress::TcpIpV4{addr: [127, 255, 255, 255], port: 1000};
assert_eq!(filter_addresses(Some(ip_address.clone())), None);
// For (169.254/16)
- let ip_address = NetAddress::IPv4{addr: [169, 254, 0, 0], port: 1000};
+ let ip_address = SocketAddress::TcpIpV4{addr: [169, 254, 0, 0], port: 1000};
assert_eq!(filter_addresses(Some(ip_address.clone())), None);
- let ip_address = NetAddress::IPv4{addr: [169, 254, 221, 101], port: 1000};
+ let ip_address = SocketAddress::TcpIpV4{addr: [169, 254, 221, 101], port: 1000};
assert_eq!(filter_addresses(Some(ip_address.clone())), None);
- let ip_address = NetAddress::IPv4{addr: [169, 254, 255, 255], port: 1000};
+ let ip_address = SocketAddress::TcpIpV4{addr: [169, 254, 255, 255], port: 1000};
assert_eq!(filter_addresses(Some(ip_address.clone())), None);
// For (172.16/12)
- let ip_address = NetAddress::IPv4{addr: [172, 16, 0, 0], port: 1000};
+ let ip_address = SocketAddress::TcpIpV4{addr: [172, 16, 0, 0], port: 1000};
assert_eq!(filter_addresses(Some(ip_address.clone())), None);
- let ip_address = NetAddress::IPv4{addr: [172, 27, 101, 23], port: 1000};
+ let ip_address = SocketAddress::TcpIpV4{addr: [172, 27, 101, 23], port: 1000};
assert_eq!(filter_addresses(Some(ip_address.clone())), None);
- let ip_address = NetAddress::IPv4{addr: [172, 31, 255, 255], port: 1000};
+ let ip_address = SocketAddress::TcpIpV4{addr: [172, 31, 255, 255], port: 1000};
assert_eq!(filter_addresses(Some(ip_address.clone())), None);
// For (192.168/16)
- let ip_address = NetAddress::IPv4{addr: [192, 168, 0, 0], port: 1000};
+ let ip_address = SocketAddress::TcpIpV4{addr: [192, 168, 0, 0], port: 1000};
assert_eq!(filter_addresses(Some(ip_address.clone())), None);
- let ip_address = NetAddress::IPv4{addr: [192, 168, 205, 159], port: 1000};
+ let ip_address = SocketAddress::TcpIpV4{addr: [192, 168, 205, 159], port: 1000};
assert_eq!(filter_addresses(Some(ip_address.clone())), None);
- let ip_address = NetAddress::IPv4{addr: [192, 168, 255, 255], port: 1000};
+ let ip_address = SocketAddress::TcpIpV4{addr: [192, 168, 255, 255], port: 1000};
assert_eq!(filter_addresses(Some(ip_address.clone())), None);
// For (192.88.99/24)
- let ip_address = NetAddress::IPv4{addr: [192, 88, 99, 0], port: 1000};
+ let ip_address = SocketAddress::TcpIpV4{addr: [192, 88, 99, 0], port: 1000};
assert_eq!(filter_addresses(Some(ip_address.clone())), None);
- let ip_address = NetAddress::IPv4{addr: [192, 88, 99, 140], port: 1000};
+ let ip_address = SocketAddress::TcpIpV4{addr: [192, 88, 99, 140], port: 1000};
assert_eq!(filter_addresses(Some(ip_address.clone())), None);
- let ip_address = NetAddress::IPv4{addr: [192, 88, 99, 255], port: 1000};
+ let ip_address = SocketAddress::TcpIpV4{addr: [192, 88, 99, 255], port: 1000};
assert_eq!(filter_addresses(Some(ip_address.clone())), None);
// For other IPv4 addresses
- let ip_address = NetAddress::IPv4{addr: [188, 255, 99, 0], port: 1000};
+ let ip_address = SocketAddress::TcpIpV4{addr: [188, 255, 99, 0], port: 1000};
assert_eq!(filter_addresses(Some(ip_address.clone())), Some(ip_address.clone()));
- let ip_address = NetAddress::IPv4{addr: [123, 8, 129, 14], port: 1000};
+ let ip_address = SocketAddress::TcpIpV4{addr: [123, 8, 129, 14], port: 1000};
assert_eq!(filter_addresses(Some(ip_address.clone())), Some(ip_address.clone()));
- let ip_address = NetAddress::IPv4{addr: [2, 88, 9, 255], port: 1000};
+ let ip_address = SocketAddress::TcpIpV4{addr: [2, 88, 9, 255], port: 1000};
assert_eq!(filter_addresses(Some(ip_address.clone())), Some(ip_address.clone()));
// For (2000::/3)
- let ip_address = NetAddress::IPv6{addr: [32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], port: 1000};
+ let ip_address = SocketAddress::TcpIpV6{addr: [32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], port: 1000};
assert_eq!(filter_addresses(Some(ip_address.clone())), Some(ip_address.clone()));
- let ip_address = NetAddress::IPv6{addr: [45, 34, 209, 190, 0, 123, 55, 34, 0, 0, 3, 27, 201, 0, 0, 0], port: 1000};
+ let ip_address = SocketAddress::TcpIpV6{addr: [45, 34, 209, 190, 0, 123, 55, 34, 0, 0, 3, 27, 201, 0, 0, 0], port: 1000};
assert_eq!(filter_addresses(Some(ip_address.clone())), Some(ip_address.clone()));
- let ip_address = NetAddress::IPv6{addr: [63, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], port: 1000};
+ let ip_address = SocketAddress::TcpIpV6{addr: [63, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], port: 1000};
assert_eq!(filter_addresses(Some(ip_address.clone())), Some(ip_address.clone()));
// For other IPv6 addresses
- let ip_address = NetAddress::IPv6{addr: [24, 240, 12, 32, 0, 0, 0, 0, 20, 97, 0, 32, 121, 254, 0, 0], port: 1000};
+ let ip_address = SocketAddress::TcpIpV6{addr: [24, 240, 12, 32, 0, 0, 0, 0, 20, 97, 0, 32, 121, 254, 0, 0], port: 1000};
assert_eq!(filter_addresses(Some(ip_address.clone())), None);
- let ip_address = NetAddress::IPv6{addr: [68, 23, 56, 63, 0, 0, 2, 7, 75, 109, 0, 39, 0, 0, 0, 0], port: 1000};
+ let ip_address = SocketAddress::TcpIpV6{addr: [68, 23, 56, 63, 0, 0, 2, 7, 75, 109, 0, 39, 0, 0, 0, 0], port: 1000};
assert_eq!(filter_addresses(Some(ip_address.clone())), None);
- let ip_address = NetAddress::IPv6{addr: [101, 38, 140, 230, 100, 0, 30, 98, 0, 26, 0, 0, 57, 96, 0, 0], port: 1000};
+ let ip_address = SocketAddress::TcpIpV6{addr: [101, 38, 140, 230, 100, 0, 30, 98, 0, 26, 0, 0, 57, 96, 0, 0], port: 1000};
assert_eq!(filter_addresses(Some(ip_address.clone())), None);
// For (None)
use crate::chain::ChannelMonitorUpdateStatus;
use crate::sign::NodeSigner;
use crate::events::{ClosureReason, Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider};
-use crate::ln::channelmanager::{ChannelManager, MIN_CLTV_EXPIRY_DELTA, PaymentId, RecipientOnionFields};
+use crate::ln::channelmanager::{MIN_CLTV_EXPIRY_DELTA, PaymentId, RecipientOnionFields};
use crate::routing::gossip::RoutingFees;
use crate::routing::router::{PaymentParameters, RouteHint, RouteHintHop};
use crate::ln::features::ChannelTypeFeatures;
use crate::ln::msgs;
use crate::ln::msgs::{ChannelMessageHandler, RoutingMessageHandler, ChannelUpdate, ErrorAction};
use crate::ln::wire::Encode;
-use crate::util::config::UserConfig;
+use crate::util::config::{UserConfig, MaxDustHTLCExposure};
use crate::util::ser::Writeable;
use crate::util::test_utils;
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
let mut no_announce_cfg = test_default_channel_config();
no_announce_cfg.accept_forwards_to_priv_channels = false;
+ let persister;
+ let new_chain_monitor;
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, Some(no_announce_cfg), None]);
- let persister: test_utils::TestPersister;
- let new_chain_monitor: test_utils::TestChainMonitor;
- let nodes_1_deserialized: ChannelManager<&test_utils::TestChainMonitor, &test_utils::TestBroadcaster, &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestFeeEstimator, &test_utils::TestRouter, &test_utils::TestLogger>;
+ let nodes_1_deserialized;
let mut nodes = create_network(3, &node_cfgs, &node_chanmgrs);
let chan_id_1 = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 500_000_000).2;
alice_config.channel_handshake_config.minimum_depth = 1;
alice_config.channel_handshake_config.announced_channel = true;
alice_config.channel_handshake_limits.force_announced_channel_preference = false;
+ alice_config.channel_config.max_dust_htlc_exposure = MaxDustHTLCExposure::FeeRateMultiplier(5_000_000 / 253);
let mut bob_config = UserConfig::default();
bob_config.channel_handshake_config.minimum_depth = 1;
bob_config.channel_handshake_config.announced_channel = true;
bob_config.channel_handshake_limits.force_announced_channel_preference = false;
+ bob_config.channel_config.max_dust_htlc_exposure = MaxDustHTLCExposure::FeeRateMultiplier(5_000_000 / 253);
let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(alice_config), Some(bob_config)]);
nodes[0].node.force_close_all_channels_broadcasting_latest_txn();
check_added_monitors!(nodes[0], 1);
- check_closed_event!(&nodes[0], 1, ClosureReason::HolderForceClosed);
+ check_closed_event!(&nodes[0], 1, ClosureReason::HolderForceClosed, [nodes[1].node.get_our_node_id()], 100000);
let _ = get_err_msg(&nodes[0], &nodes[1].node.get_our_node_id());
}
// now we force-close the channel here.
check_closed_event!(&nodes[0], 1, ClosureReason::ProcessingError {
err: "Funding transaction was un-confirmed. Locked at 0 confs, now have 0 confs.".to_owned()
- });
+ }, [nodes[1].node.get_our_node_id()], 100000);
check_closed_broadcast!(nodes[0], true);
check_added_monitors(&nodes[0], 1);
check_closed_event!(&nodes[1], 1, ClosureReason::ProcessingError {
err: "Funding transaction was un-confirmed. Locked at 0 confs, now have 0 confs.".to_owned()
- });
+ }, [nodes[0].node.get_our_node_id()], 100000);
check_closed_broadcast!(nodes[1], true);
check_added_monitors(&nodes[1], 1);
}
connect_blocks(&nodes[0], 1);
connect_blocks(&nodes[1], 1);
}
+
+#[test]
+fn test_0conf_ann_sigs_racing_conf() {
+ // Previously we had a bug where we'd panic when receiving a counterparty's
+ // announcement_signatures message for a 0conf channel pending confirmation on-chain. Here we
+ // check that we just error out, ignore the announcement_signatures message, and proceed
+ // instead.
+ let chanmon_cfgs = create_chanmon_cfgs(2);
+ let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
+ let mut chan_config = test_default_channel_config();
+ chan_config.manually_accept_inbound_channels = true;
+
+ let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, Some(chan_config)]);
+ let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+
+ // This is the default but we force it on anyway
+ chan_config.channel_handshake_config.announced_channel = true;
+ let (tx, ..) = open_zero_conf_channel(&nodes[0], &nodes[1], Some(chan_config));
+
+ // We can use the channel immediately, but we can't announce it until we get 6+ confirmations
+ send_payment(&nodes[0], &[&nodes[1]], 100_000);
+
+ let scid = confirm_transaction(&nodes[0], &tx);
+ let as_announcement_sigs = get_event_msg!(nodes[0], MessageSendEvent::SendAnnouncementSignatures, nodes[1].node.get_our_node_id());
+
+ // Handling the announcement_signatures prior to the first confirmation would panic before.
+ nodes[1].node.handle_announcement_signatures(&nodes[0].node.get_our_node_id(), &as_announcement_sigs);
+
+ assert_eq!(confirm_transaction(&nodes[1], &tx), scid);
+ let bs_announcement_sigs = get_event_msg!(nodes[1], MessageSendEvent::SendAnnouncementSignatures, nodes[0].node.get_our_node_id());
+
+ nodes[0].node.handle_announcement_signatures(&nodes[1].node.get_our_node_id(), &bs_announcement_sigs);
+ let as_announcement = nodes[0].node.get_and_clear_pending_msg_events();
+ assert_eq!(as_announcement.len(), 1);
+}
use crate::chain::{ChannelMonitorUpdateStatus, Watch};
use crate::chain::chaininterface::LowerBoundedFeeEstimator;
-use crate::chain::channelmonitor::ChannelMonitor;
+use crate::chain::channelmonitor::{CLOSED_CHANNEL_UPDATE_ID, ChannelMonitor};
use crate::sign::EntropySource;
use crate::chain::transaction::OutPoint;
use crate::events::{ClosureReason, Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider};
use crate::ln::channelmanager::{ChannelManager, ChannelManagerReadArgs, PaymentId, RecipientOnionFields};
use crate::ln::msgs;
use crate::ln::msgs::{ChannelMessageHandler, RoutingMessageHandler, ErrorAction};
-use crate::util::enforcing_trait_impls::EnforcingSigner;
+use crate::util::test_channel_signer::TestChannelSigner;
use crate::util::test_utils;
use crate::util::errors::APIError;
use crate::util::ser::{Writeable, ReadableArgs};
use crate::util::config::UserConfig;
use crate::util::string::UntrustedString;
+use bitcoin::{PackedLockTime, Transaction, TxOut};
use bitcoin::hash_types::BlockHash;
use crate::prelude::*;
// Test that we can lock in our funding tx while disconnected
let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
+ let persister;
+ let new_chain_monitor;
+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
- let persister: test_utils::TestPersister;
- let new_chain_monitor: test_utils::TestChainMonitor;
- let nodes_0_deserialized: ChannelManager<&test_utils::TestChainMonitor, &test_utils::TestBroadcaster, &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestFeeEstimator, &test_utils::TestRouter, &test_utils::TestLogger>;
+ let nodes_0_deserialized;
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
let tx = create_chan_between_nodes_with_value_init(&nodes[0], &nodes[1], 100000, 10001);
let events_1 = nodes[0].node.get_and_clear_pending_msg_events();
assert!(events_1.is_empty());
- reconnect_nodes(&nodes[0], &nodes[1], (false, true), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (false, false));
+ let mut reconnect_args = ReconnectArgs::new(&nodes[0], &nodes[1]);
+ reconnect_args.send_channel_ready.1 = true;
+ reconnect_nodes(reconnect_args);
nodes[0].node.peer_disconnected(&nodes[1].node.get_our_node_id());
nodes[1].node.peer_disconnected(&nodes[0].node.get_our_node_id());
reload_node!(nodes[0], &nodes[0].node.encode(), &[&chan_0_monitor_serialized], persister, new_chain_monitor, nodes_0_deserialized);
- reconnect_nodes(&nodes[0], &nodes[1], (false, false), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (false, false));
+ reconnect_nodes(ReconnectArgs::new(&nodes[0], &nodes[1]));
}
#[test]
fn test_no_txn_manager_serialize_deserialize() {
let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
+ let persister;
+ let new_chain_monitor;
+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
- let persister: test_utils::TestPersister;
- let new_chain_monitor: test_utils::TestChainMonitor;
- let nodes_0_deserialized: ChannelManager<&test_utils::TestChainMonitor, &test_utils::TestBroadcaster, &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestFeeEstimator, &test_utils::TestRouter, &test_utils::TestLogger>;
+ let nodes_0_deserialized;
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
let tx = create_chan_between_nodes_with_value_init(&nodes[0], &nodes[1], 100000, 10001);
// This test makes sure the events field in ChannelManager survives de/serialization
let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
+ let persister;
+ let new_chain_monitor;
+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
- let persister: test_utils::TestPersister;
- let new_chain_monitor: test_utils::TestChainMonitor;
- let nodes_0_deserialized: ChannelManager<&test_utils::TestChainMonitor, &test_utils::TestBroadcaster, &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestFeeEstimator, &test_utils::TestRouter, &test_utils::TestLogger>;
+ let nodes_0_deserialized;
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
// Start creating a channel, but stop right before broadcasting the funding transaction
fn test_simple_manager_serialize_deserialize() {
let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
+ let persister;
+ let new_chain_monitor;
+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
- let persister: test_utils::TestPersister;
- let new_chain_monitor: test_utils::TestChainMonitor;
- let nodes_0_deserialized: ChannelManager<&test_utils::TestChainMonitor, &test_utils::TestBroadcaster, &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestFeeEstimator, &test_utils::TestRouter, &test_utils::TestLogger>;
+ let nodes_0_deserialized;
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
let chan_id = create_announced_chan_between_nodes(&nodes, 0, 1).2;
- let (our_payment_preimage, _, _) = route_payment(&nodes[0], &[&nodes[1]], 1000000);
- let (_, our_payment_hash, _) = route_payment(&nodes[0], &[&nodes[1]], 1000000);
+ let (our_payment_preimage, ..) = route_payment(&nodes[0], &[&nodes[1]], 1000000);
+ let (_, our_payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1]], 1000000);
nodes[1].node.peer_disconnected(&nodes[0].node.get_our_node_id());
let chan_0_monitor_serialized = get_monitor!(nodes[0], chan_id).encode();
reload_node!(nodes[0], nodes[0].node.encode(), &[&chan_0_monitor_serialized], persister, new_chain_monitor, nodes_0_deserialized);
- reconnect_nodes(&nodes[0], &nodes[1], (false, false), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (false, false));
+ reconnect_nodes(ReconnectArgs::new(&nodes[0], &nodes[1]));
fail_payment(&nodes[0], &[&nodes[1]], our_payment_hash);
claim_payment(&nodes[0], &[&nodes[1]], our_payment_preimage);
// Test deserializing a ChannelManager with an out-of-date ChannelMonitor
let chanmon_cfgs = create_chanmon_cfgs(4);
let node_cfgs = create_node_cfgs(4, &chanmon_cfgs);
+ let logger;
+ let fee_estimator;
+ let persister;
+ let new_chain_monitor;
+
let node_chanmgrs = create_node_chanmgrs(4, &node_cfgs, &[None, None, None, None]);
- let logger: test_utils::TestLogger;
- let fee_estimator: test_utils::TestFeeEstimator;
- let persister: test_utils::TestPersister;
- let new_chain_monitor: test_utils::TestChainMonitor;
- let nodes_0_deserialized: ChannelManager<&test_utils::TestChainMonitor, &test_utils::TestBroadcaster, &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestFeeEstimator, &test_utils::TestRouter, &test_utils::TestLogger>;
+ let nodes_0_deserialized;
let mut nodes = create_network(4, &node_cfgs, &node_chanmgrs);
+
let chan_id_1 = create_announced_chan_between_nodes(&nodes, 0, 1).2;
let chan_id_2 = create_announced_chan_between_nodes(&nodes, 2, 0).2;
let (_, _, channel_id, funding_tx) = create_announced_chan_between_nodes(&nodes, 0, 3);
node_0_stale_monitors_serialized.push(writer.0);
}
- let (our_payment_preimage, _, _) = route_payment(&nodes[2], &[&nodes[0], &nodes[1]], 1000000);
+ let (our_payment_preimage, ..) = route_payment(&nodes[2], &[&nodes[0], &nodes[1]], 1000000);
// Serialize the ChannelManager here, but the monitor we keep up-to-date
let nodes_0_serialized = nodes[0].node.encode();
let mut node_0_stale_monitors = Vec::new();
for serialized in node_0_stale_monitors_serialized.iter() {
let mut read = &serialized[..];
- let (_, monitor) = <(BlockHash, ChannelMonitor<EnforcingSigner>)>::read(&mut read, (keys_manager, keys_manager)).unwrap();
+ let (_, monitor) = <(BlockHash, ChannelMonitor<TestChannelSigner>)>::read(&mut read, (keys_manager, keys_manager)).unwrap();
assert!(read.is_empty());
node_0_stale_monitors.push(monitor);
}
let mut node_0_monitors = Vec::new();
for serialized in node_0_monitors_serialized.iter() {
let mut read = &serialized[..];
- let (_, monitor) = <(BlockHash, ChannelMonitor<EnforcingSigner>)>::read(&mut read, (keys_manager, keys_manager)).unwrap();
+ let (_, monitor) = <(BlockHash, ChannelMonitor<TestChannelSigner>)>::read(&mut read, (keys_manager, keys_manager)).unwrap();
assert!(read.is_empty());
node_0_monitors.push(monitor);
}
for monitor in node_0_monitors.drain(..) {
assert_eq!(nodes[0].chain_monitor.watch_channel(monitor.get_funding_txo().0, monitor),
- ChannelMonitorUpdateStatus::Completed);
+ Ok(ChannelMonitorUpdateStatus::Completed));
check_added_monitors!(nodes[0], 1);
}
nodes[0].node = &nodes_0_deserialized;
- check_closed_event!(nodes[0], 1, ClosureReason::OutdatedChannelManager);
+ check_closed_event!(nodes[0], 1, ClosureReason::OutdatedChannelManager, [nodes[3].node.get_our_node_id()], 100000);
{ // Channel close should result in a commitment tx
nodes[0].node.timer_tick_occurred();
let txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap();
check_added_monitors!(nodes[0], 1);
// nodes[1] and nodes[2] have no lost state with nodes[0]...
- reconnect_nodes(&nodes[0], &nodes[1], (false, false), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (false, false));
- reconnect_nodes(&nodes[0], &nodes[2], (false, false), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (false, false));
+ reconnect_nodes(ReconnectArgs::new(&nodes[0], &nodes[1]));
+ reconnect_nodes(ReconnectArgs::new(&nodes[0], &nodes[2]));
//... and we can even still claim the payment!
claim_payment(&nodes[2], &[&nodes[0], &nodes[1]], our_payment_preimage);
// We broadcast during Drop because chanmon is out of sync with chanmgr, which would cause a panic
// during signing due to revoked tx
chanmon_cfgs[0].keys_manager.disable_revocation_policy_check = true;
+ let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
let persister;
let new_chain_monitor;
- let nodes_0_deserialized;
- let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
+ let nodes_0_deserialized;
+
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
let chan = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1000000, 1000000);
nodes[0].node.force_close_without_broadcasting_txn(&chan.2, &nodes[1].node.get_our_node_id()).unwrap();
check_added_monitors!(nodes[0], 1);
- check_closed_event!(nodes[0], 1, ClosureReason::HolderForceClosed);
+ check_closed_event!(nodes[0], 1, ClosureReason::HolderForceClosed, [nodes[1].node.get_our_node_id()], 1000000);
{
let node_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap();
assert_eq!(node_txn.len(), 0);
nodes[1].node.handle_error(&nodes[0].node.get_our_node_id(), &err_msgs_0[0]);
assert!(nodes[1].node.list_usable_channels().is_empty());
check_added_monitors!(nodes[1], 1);
- check_closed_event!(nodes[1], 1, ClosureReason::CounterpartyForceClosed { peer_msg: UntrustedString(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())) });
+ check_closed_event!(nodes[1], 1, ClosureReason::CounterpartyForceClosed { peer_msg: UntrustedString(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())) }
+ , [nodes[0].node.get_our_node_id()], 1000000);
check_closed_broadcast!(nodes[1], false);
}
let chanmon_cfgs = create_chanmon_cfgs(3);
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
+ let persister;
+ let new_chain_monitor;
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
- let persister: test_utils::TestPersister;
- let new_chain_monitor: test_utils::TestChainMonitor;
- let nodes_1_deserialized: ChannelManager<&test_utils::TestChainMonitor, &test_utils::TestBroadcaster, &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestFeeEstimator, &test_utils::TestRouter, &test_utils::TestLogger>;
+ let nodes_1_deserialized;
let mut nodes = create_network(3, &node_cfgs, &node_chanmgrs);
let chan_id_1 = create_announced_chan_between_nodes(&nodes, 0, 1).2;
let chan_id_2 = create_announced_chan_between_nodes(&nodes, 1, 2).2;
let chan_1_monitor_serialized = get_monitor!(nodes[1], chan_id_2).encode();
reload_node!(nodes[1], nodes[1].node.encode(), &[&chan_0_monitor_serialized, &chan_1_monitor_serialized], persister, new_chain_monitor, nodes_1_deserialized);
- reconnect_nodes(&nodes[0], &nodes[1], (false, false), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (false, false));
+ reconnect_nodes(ReconnectArgs::new(&nodes[0], &nodes[1]));
// Note that nodes[1] and nodes[2] resend their channel_ready here since they haven't updated
// the commitment state.
- reconnect_nodes(&nodes[1], &nodes[2], (true, true), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (false, false));
+ let mut reconnect_args = ReconnectArgs::new(&nodes[1], &nodes[2]);
+ reconnect_args.send_channel_ready = (true, true);
+ reconnect_nodes(reconnect_args);
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
// definitely claimed.
let chanmon_cfgs = create_chanmon_cfgs(4);
let node_cfgs = create_node_cfgs(4, &chanmon_cfgs);
- let node_chanmgrs = create_node_chanmgrs(4, &node_cfgs, &[None, None, None, None]);
+ let persister;
+ let new_chain_monitor;
- let persister: test_utils::TestPersister;
- let new_chain_monitor: test_utils::TestChainMonitor;
- let nodes_3_deserialized: ChannelManager<&test_utils::TestChainMonitor, &test_utils::TestBroadcaster, &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestFeeEstimator, &test_utils::TestRouter, &test_utils::TestLogger>;
+ let node_chanmgrs = create_node_chanmgrs(4, &node_cfgs, &[None, None, None, None]);
+ let nodes_3_deserialized;
let mut nodes = create_network(4, &node_cfgs, &node_chanmgrs);
// This was never an issue, but it may be easy to regress here going forward.
let chanmon_cfgs = create_chanmon_cfgs(3);
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
+ let persister;
+ let new_chain_monitor;
+
let mut intercept_forwards_config = test_default_channel_config();
intercept_forwards_config.accept_intercept_htlcs = true;
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, Some(intercept_forwards_config), None]);
-
- let persister;
- let new_chain_monitor;
let nodes_1_deserialized;
let mut nodes = create_network(3, &node_cfgs, &node_chanmgrs);
assert_eq!(cs_commitment_tx.len(), if claim_htlc { 2 } else { 1 });
check_added_monitors!(nodes[2], 1);
- check_closed_event!(nodes[2], 1, ClosureReason::HolderForceClosed);
+ check_closed_event!(nodes[2], 1, ClosureReason::HolderForceClosed, [nodes[1].node.get_our_node_id()], 100000);
check_closed_broadcast!(nodes[2], true);
let chan_0_monitor_serialized = get_monitor!(nodes[1], chan_id_1).encode();
// Note that this checks that this is the only event on nodes[1], implying the
// `HTLCIntercepted` event has been removed in the `use_intercept` case.
- check_closed_event!(nodes[1], 1, ClosureReason::OutdatedChannelManager);
+ check_closed_event!(nodes[1], 1, ClosureReason::OutdatedChannelManager, [nodes[2].node.get_our_node_id()], 100000);
if use_intercept {
// Attempt to forward the HTLC back out over nodes[1]' still-open channel, ensuring we get
check_added_monitors!(nodes[1], 1);
nodes[0].node.peer_disconnected(&nodes[1].node.get_our_node_id());
- reconnect_nodes(&nodes[0], &nodes[1], (false, false), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (false, false));
+ reconnect_nodes(ReconnectArgs::new(&nodes[0], &nodes[1]));
if use_cs_commitment {
// If we confirm a commitment transaction that has the HTLC on-chain, nodes[1] should wait
// were left dangling when a channel was force-closed due to a stale ChannelManager.
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 persister;
let new_chain_monitor;
+
+ let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
let nodes_1_deserialized;
let mut nodes = create_network(3, &node_cfgs, &node_chanmgrs);
let chan_id_1 = create_announced_chan_between_nodes(&nodes, 0, 1).2;
let chan_id_2 = create_announced_chan_between_nodes(&nodes, 1, 2).2;
- let (_, payment_hash, _) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 1_000_000);
+ let (_, payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 1_000_000);
let node_encoded = nodes[1].node.encode();
// now forgotten everywhere. The ChannelManager should have, as a side-effect of reload,
// learned that the HTLC is gone from the ChannelMonitor and added it to the to-fail-back set.
nodes[0].node.peer_disconnected(&nodes[1].node.get_our_node_id());
- reconnect_nodes(&nodes[0], &nodes[1], (false, false), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (false, false));
+ reconnect_nodes(ReconnectArgs::new(&nodes[0], &nodes[1]));
expect_pending_htlcs_forwardable_and_htlc_handling_failed!(nodes[1], [HTLCDestination::NextHopChannel { node_id: Some(nodes[2].node.get_our_node_id()), channel_id: chan_id_2 }]);
check_added_monitors!(nodes[1], 1);
expect_payment_failed!(nodes[0], payment_hash, false);
}
+
+#[test]
+fn test_reload_partial_funding_batch() {
+ let chanmon_cfgs = create_chanmon_cfgs(3);
+ let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
+ let new_persister;
+ let new_chain_monitor;
+
+ let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
+ let new_channel_manager;
+ let mut nodes = create_network(3, &node_cfgs, &node_chanmgrs);
+
+ // Initiate channel opening and create the batch channel funding transaction.
+ let (tx, funding_created_msgs) = create_batch_channel_funding(&nodes[0], &[
+ (&nodes[1], 100_000, 0, 42, None),
+ (&nodes[2], 200_000, 0, 43, None),
+ ]);
+
+ // Go through the funding_created and funding_signed flow with node 1.
+ nodes[1].node.handle_funding_created(&nodes[0].node.get_our_node_id(), &funding_created_msgs[0]);
+ check_added_monitors(&nodes[1], 1);
+ expect_channel_pending_event(&nodes[1], &nodes[0].node.get_our_node_id());
+
+ // The monitor is persisted when receiving funding_signed.
+ let funding_signed_msg = get_event_msg!(nodes[1], MessageSendEvent::SendFundingSigned, nodes[0].node.get_our_node_id());
+ nodes[0].node.handle_funding_signed(&nodes[1].node.get_our_node_id(), &funding_signed_msg);
+ check_added_monitors(&nodes[0], 1);
+
+ // The transaction should not have been broadcast before all channels are ready.
+ assert_eq!(nodes[0].tx_broadcaster.txn_broadcast().len(), 0);
+
+ // Reload the node while a subset of the channels in the funding batch have persisted monitors.
+ let channel_id_1 = OutPoint { txid: tx.txid(), index: 0 }.to_channel_id();
+ let node_encoded = nodes[0].node.encode();
+ let channel_monitor_1_serialized = get_monitor!(nodes[0], channel_id_1).encode();
+ reload_node!(nodes[0], node_encoded, &[&channel_monitor_1_serialized], new_persister, new_chain_monitor, new_channel_manager);
+
+ // Process monitor events.
+ assert!(nodes[0].node.get_and_clear_pending_events().is_empty());
+
+ // The monitor should become closed.
+ check_added_monitors(&nodes[0], 1);
+ {
+ let mut monitor_updates = nodes[0].chain_monitor.monitor_updates.lock().unwrap();
+ let monitor_updates_1 = monitor_updates.get(&channel_id_1).unwrap();
+ assert_eq!(monitor_updates_1.len(), 1);
+ assert_eq!(monitor_updates_1[0].update_id, CLOSED_CHANNEL_UPDATE_ID);
+ }
+
+ // The funding transaction should not have been broadcast, but we broadcast the force-close
+ // transaction as part of closing the monitor.
+ {
+ let broadcasted_txs = nodes[0].tx_broadcaster.txn_broadcast();
+ assert_eq!(broadcasted_txs.len(), 1);
+ assert!(broadcasted_txs[0].txid() != tx.txid());
+ assert_eq!(broadcasted_txs[0].input.len(), 1);
+ assert_eq!(broadcasted_txs[0].input[0].previous_output.txid, tx.txid());
+ }
+
+ // Ensure the channels don't exist anymore.
+ assert!(nodes[0].node.list_channels().is_empty());
+}
use crate::chain::transaction::OutPoint;
use crate::chain::Confirm;
use crate::events::{Event, MessageSendEventsProvider, ClosureReason, HTLCDestination};
-use crate::ln::channelmanager::ChannelManager;
use crate::ln::msgs::{ChannelMessageHandler, Init};
use crate::util::test_utils;
use crate::util::ser::Writeable;
connect_blocks(&nodes[1], 2*CHAN_CONFIRM_DEPTH + 1 - nodes[1].best_block_info().1);
connect_blocks(&nodes[2], 2*CHAN_CONFIRM_DEPTH + 1 - nodes[2].best_block_info().1);
- let (our_payment_preimage, our_payment_hash, _) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 1_000_000);
+ let (our_payment_preimage, our_payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 1_000_000);
// Provide preimage to node 2 by claiming payment
nodes[2].node.claim_funds(our_payment_preimage);
connect_block(&nodes[2], &create_dummy_block(nodes[2].best_block_hash(), 42, node_1_commitment_txn.clone()));
check_added_monitors!(nodes[2], 1);
check_closed_broadcast!(nodes[2], true); // We should get a BroadcastChannelUpdate (and *only* a BroadcstChannelUpdate)
- check_closed_event!(nodes[2], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[2], 1, ClosureReason::CommitmentTxConfirmed, [nodes[1].node.get_our_node_id()], 100000);
let node_2_commitment_txn = nodes[2].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
assert_eq!(node_2_commitment_txn.len(), 1); // ChannelMonitor: 1 offered HTLC-Claim
check_spends!(node_2_commitment_txn[0], node_1_commitment_txn[0]);
};
check_added_monitors!(nodes[1], 1);
check_closed_broadcast!(nodes[1], true); // We should get a BroadcastChannelUpdate (and *only* a BroadcstChannelUpdate)
- check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [nodes[2].node.get_our_node_id()], 100000);
// Connect ANTI_REORG_DELAY - 2 blocks, giving us a confirmation count of ANTI_REORG_DELAY - 1.
connect_blocks(&nodes[1], ANTI_REORG_DELAY - 2);
check_added_monitors!(nodes[1], 0);
// on any of the HTLCs, at least until we get six confirmations (which we won't get).
mine_transaction(&nodes[1], &revoked_local_txn[0]);
check_added_monitors!(nodes[1], 1);
- check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [nodes[0].node.get_our_node_id()], 1000000);
check_closed_broadcast!(nodes[1], true);
// Connect up to one block before the revoked transaction would be considered final, then do a
// Connect the HTLC claim transaction for HTLC 3
mine_transaction(&nodes[1], &unrevoked_local_txn[2]);
- expect_payment_sent!(nodes[1], payment_preimage_3);
+ expect_payment_sent(&nodes[1], payment_preimage_3, None, true, false);
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
// Connect blocks to confirm the unrevoked commitment transaction
// around freeing background events which store monitor updates during block_[dis]connected.
let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
+ let persister;
+ let new_chain_monitor;
+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
- let persister: test_utils::TestPersister;
- let new_chain_monitor: test_utils::TestChainMonitor;
- let nodes_0_deserialized: ChannelManager<&test_utils::TestChainMonitor, &test_utils::TestBroadcaster, &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestFeeEstimator, &test_utils::TestRouter, &test_utils::TestLogger>;
+ let nodes_0_deserialized;
+
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
*nodes[0].connect_style.borrow_mut() = connect_style;
if reorg_after_reload || !reload_node {
handle_announce_close_broadcast_events(&nodes, 0, 1, true, "Channel closed because of an exception: Funding transaction was un-confirmed. Locked at 6 confs, now have 0 confs.");
check_added_monitors!(nodes[1], 1);
- check_closed_event!(nodes[1], 1, ClosureReason::CounterpartyForceClosed { peer_msg: UntrustedString(format!("Channel closed because of an exception: {}", expected_err)) });
+ check_closed_event!(nodes[1], 1, ClosureReason::CounterpartyForceClosed { peer_msg: UntrustedString(format!("Channel closed because of an exception: {}", expected_err)) }
+ , [nodes[0].node.get_our_node_id()], 100000);
}
- check_closed_event!(nodes[0], 1, ClosureReason::ProcessingError { err: expected_err.to_owned() });
+ check_closed_event!(nodes[0], 1, ClosureReason::ProcessingError { err: expected_err.to_owned() },
+ [nodes[1].node.get_our_node_id()], 100000);
assert_eq!(nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().len(), 1);
nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().clear();
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
let chan = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1000000, 59000000);
- let (payment_preimage_1, payment_hash_1, _) = route_payment(&nodes[1], &[&nodes[0]], 3_000_000);
- let (payment_preimage_2, payment_hash_2, _) = route_payment(&nodes[1], &[&nodes[0]], 3_000_000);
+ let (payment_preimage_1, payment_hash_1, ..) = route_payment(&nodes[1], &[&nodes[0]], 3_000_000);
+ let (payment_preimage_2, payment_hash_2, ..) = route_payment(&nodes[1], &[&nodes[0]], 3_000_000);
// Remote commitment txn with 4 outputs: to_local, to_remote, 2 outgoing HTLC
let remote_txn = get_local_commitment_txn!(nodes[1], chan.2);
// Connect blocks on node A commitment transaction
mine_transaction(&nodes[0], &remote_txn[0]);
check_closed_broadcast!(nodes[0], true);
- check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed, [nodes[1].node.get_our_node_id()], 1000000);
check_added_monitors!(nodes[0], 1);
// Verify node A broadcast tx claiming both HTLCs
{
// Connect blocks on node B
connect_blocks(&nodes[1], TEST_FINAL_CLTV + LATENCY_GRACE_PERIOD_BLOCKS + 1);
check_closed_broadcast!(nodes[1], true);
- check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[1], 1, ClosureReason::HolderForceClosed, [nodes[0].node.get_our_node_id()], 1000000);
check_added_monitors!(nodes[1], 1);
// Verify node B broadcast 2 HTLC-timeout txn
let partial_claim_tx = {
assert!(nodes[0].node.list_channels().is_empty());
check_closed_broadcast!(nodes[0], true);
check_added_monitors!(nodes[0], 1);
- check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed, [nodes[1].node.get_our_node_id()], 1000000);
assert!(nodes[1].node.list_channels().is_empty());
check_closed_broadcast!(nodes[1], true);
check_added_monitors!(nodes[1], 1);
- check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [nodes[0].node.get_our_node_id()], 1000000);
assert!(nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events().is_empty());
assert!(nodes[1].chain_monitor.chain_monitor.get_and_clear_pending_events().is_empty());
let mut node_a_spendable = nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events();
assert_eq!(node_a_spendable.len(), 1);
- if let Event::SpendableOutputs { outputs } = node_a_spendable.pop().unwrap() {
+ if let Event::SpendableOutputs { outputs, channel_id } = node_a_spendable.pop().unwrap() {
assert_eq!(outputs.len(), 1);
+ assert_eq!(channel_id, Some(chan_id));
let spend_tx = nodes[0].keys_manager.backing.spend_spendable_outputs(&[&outputs[0]], Vec::new(),
Builder::new().push_opcode(opcodes::all::OP_RETURN).into_script(), 253, None, &Secp256k1::new()).unwrap();
check_spends!(spend_tx, remote_txn_b[0]);
let mut node_b_spendable = nodes[1].chain_monitor.chain_monitor.get_and_clear_pending_events();
assert_eq!(node_b_spendable.len(), 1);
- if let Event::SpendableOutputs { outputs } = node_b_spendable.pop().unwrap() {
+ if let Event::SpendableOutputs { outputs, channel_id } = node_b_spendable.pop().unwrap() {
assert_eq!(outputs.len(), 1);
+ assert_eq!(channel_id, Some(chan_id));
let spend_tx = nodes[1].keys_manager.backing.spend_spendable_outputs(&[&outputs[0]], Vec::new(),
Builder::new().push_opcode(opcodes::all::OP_RETURN).into_script(), 253, None, &Secp256k1::new()).unwrap();
check_spends!(spend_tx, remote_txn_a[0]);
use crate::sign::{EntropySource, SignerProvider};
use crate::chain::transaction::OutPoint;
-use crate::events::{Event, MessageSendEvent, MessageSendEventsProvider, ClosureReason};
+use crate::events::{MessageSendEvent, MessageSendEventsProvider, ClosureReason};
use crate::ln::channelmanager::{self, PaymentSendFailure, PaymentId, RecipientOnionFields, ChannelShutdownState, ChannelDetails};
-use crate::routing::router::{PaymentParameters, get_route};
+use crate::routing::router::{PaymentParameters, get_route, RouteParameters};
use crate::ln::msgs;
use crate::ln::msgs::{ChannelMessageHandler, ErrorAction};
use crate::ln::script::ShutdownScript;
assert!(nodes[0].node.list_channels().is_empty());
assert!(nodes[1].node.list_channels().is_empty());
- check_closed_event!(nodes[0], 1, ClosureReason::CooperativeClosure);
- check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure);
+ check_closed_event!(nodes[0], 1, ClosureReason::CooperativeClosure, [nodes[1].node.get_our_node_id()], 8000000);
+ check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure, [nodes[0].node.get_our_node_id()], 8000000);
}
#[test]
assert!(nodes[0].node.list_channels().is_empty());
assert!(nodes[1].node.list_channels().is_empty());
- check_closed_event!(nodes[0], 1, ClosureReason::CooperativeClosure);
- check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure);
+ check_closed_event!(nodes[0], 1, ClosureReason::CooperativeClosure, [nodes[1].node.get_our_node_id()], 100000);
+ check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure, [nodes[0].node.get_our_node_id()], 100000);
}
#[test]
let chan_1 = create_announced_chan_between_nodes(&nodes, 0, 1);
let _chan_2 = create_announced_chan_between_nodes(&nodes, 1, 2);
- let (payment_preimage_0, payment_hash_0, _) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 100_000);
+ let (payment_preimage_0, payment_hash_0, ..) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 100_000);
expect_channel_shutdown_state!(nodes[0], chan_1.2, ChannelShutdownState::NotShuttingDown);
expect_channel_shutdown_state!(nodes[1], chan_1.2, ChannelShutdownState::NotShuttingDown);
nodes[1].node.handle_closing_signed(&nodes[0].node.get_our_node_id(), &node_0_2nd_closing_signed.unwrap());
let (_, node_1_none) = get_closing_signed_broadcast!(nodes[1].node, nodes[0].node.get_our_node_id());
assert!(node_1_none.is_none());
- check_closed_event!(nodes[0], 1, ClosureReason::CooperativeClosure);
- check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure);
+ check_closed_event!(nodes[0], 1, ClosureReason::CooperativeClosure, [nodes[1].node.get_our_node_id()], 100000);
+ check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure, [nodes[0].node.get_our_node_id()], 100000);
// Shutdown basically removes the channelDetails, testing of shutdowncomplete state unnecessary
assert!(nodes[0].node.list_channels().is_empty());
}
+#[test]
+fn test_lnd_bug_6039() {
+ // LND sends a nonsense error message any time it gets a shutdown if there are still HTLCs
+ // pending. We currently swallow that error to work around LND's bug #6039. This test emulates
+ // the LND nonsense and ensures we at least kinda handle it.
+ let chanmon_cfgs = create_chanmon_cfgs(2);
+ let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
+ let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
+ let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+ let chan = create_announced_chan_between_nodes(&nodes, 0, 1);
+
+ let (payment_preimage, ..) = route_payment(&nodes[0], &[&nodes[1]], 100_000);
+
+ nodes[0].node.close_channel(&chan.2, &nodes[1].node.get_our_node_id()).unwrap();
+ let node_0_shutdown = get_event_msg!(nodes[0], MessageSendEvent::SendShutdown, nodes[1].node.get_our_node_id());
+ nodes[1].node.handle_shutdown(&nodes[0].node.get_our_node_id(), &node_0_shutdown);
+
+ // Generate an lnd-like error message and check that we respond by simply screaming louder to
+ // see if LND will accept our protocol compliance.
+ let err_msg = msgs::ErrorMessage { channel_id: chan.2, data: "link failed to shutdown".to_string() };
+ nodes[0].node.handle_error(&nodes[1].node.get_our_node_id(), &err_msg);
+ let node_a_responses = nodes[0].node.get_and_clear_pending_msg_events();
+ assert_eq!(node_a_responses[0], MessageSendEvent::SendShutdown {
+ node_id: nodes[1].node.get_our_node_id(),
+ msg: node_0_shutdown,
+ });
+ if let MessageSendEvent::HandleError { action: msgs::ErrorAction::SendWarningMessage { .. }, .. }
+ = node_a_responses[1] {} else { panic!(); }
+
+ let node_1_shutdown = get_event_msg!(nodes[1], MessageSendEvent::SendShutdown, nodes[0].node.get_our_node_id());
+
+ assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
+ assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
+
+ claim_payment(&nodes[0], &[&nodes[1]], payment_preimage);
+
+ // Assume that LND will eventually respond to our Shutdown if we clear all the remaining HTLCs
+ nodes[0].node.handle_shutdown(&nodes[1].node.get_our_node_id(), &node_1_shutdown);
+
+ // ClosingSignNegotion process
+ let node_0_closing_signed = get_event_msg!(nodes[0], MessageSendEvent::SendClosingSigned, nodes[1].node.get_our_node_id());
+ nodes[1].node.handle_closing_signed(&nodes[0].node.get_our_node_id(), &node_0_closing_signed);
+ let node_1_closing_signed = get_event_msg!(nodes[1], MessageSendEvent::SendClosingSigned, nodes[0].node.get_our_node_id());
+ nodes[0].node.handle_closing_signed(&nodes[1].node.get_our_node_id(), &node_1_closing_signed);
+ let (_, node_0_2nd_closing_signed) = get_closing_signed_broadcast!(nodes[0].node, nodes[1].node.get_our_node_id());
+ nodes[1].node.handle_closing_signed(&nodes[0].node.get_our_node_id(), &node_0_2nd_closing_signed.unwrap());
+ let (_, node_1_none) = get_closing_signed_broadcast!(nodes[1].node, nodes[0].node.get_our_node_id());
+ assert!(node_1_none.is_none());
+ check_closed_event!(nodes[0], 1, ClosureReason::CooperativeClosure, [nodes[1].node.get_our_node_id()], 100000);
+ check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure, [nodes[0].node.get_our_node_id()], 100000);
+
+ // Shutdown basically removes the channelDetails, testing of shutdowncomplete state unnecessary
+ assert!(nodes[0].node.list_channels().is_empty());
+}
+
+#[test]
+fn shutdown_on_unfunded_channel() {
+ // Test receiving a shutdown prior to funding generation
+ let chanmon_cfgs = create_chanmon_cfgs(2);
+ let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
+ let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
+ let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+
+ nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 1_000_000, 100_000, 0, None).unwrap();
+ let open_chan = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, nodes[1].node.get_our_node_id());
+
+ // P2WSH
+ let script = Builder::new().push_int(0)
+ .push_slice(&[0; 20])
+ .into_script();
+
+ nodes[0].node.handle_shutdown(&nodes[1].node.get_our_node_id(), &msgs::Shutdown {
+ channel_id: open_chan.temporary_channel_id, scriptpubkey: script,
+ });
+ check_closed_event!(nodes[0], 1, ClosureReason::CounterpartyCoopClosedUnfundedChannel, [nodes[1].node.get_our_node_id()], 1_000_000);
+}
+
#[test]
fn expect_channel_shutdown_state_with_force_closure() {
// Test sending a shutdown prior to channel_ready after funding generation
assert!(nodes[0].node.list_channels().is_empty());
assert!(nodes[1].node.list_channels().is_empty());
check_closed_broadcast!(nodes[0], true);
- check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed);
- check_closed_event!(nodes[1], 1, ClosureReason::HolderForceClosed);
+ check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed, [nodes[1].node.get_our_node_id()], 100000);
+ check_closed_event!(nodes[1], 1, ClosureReason::HolderForceClosed, [nodes[0].node.get_our_node_id()], 100000);
}
#[test]
let keys_manager = test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
- let (payment_preimage_0, payment_hash_0, _) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 100_000);
+ let (payment_preimage_0, payment_hash_0, ..) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 100_000);
nodes[0].node.close_channel(&chan_1.2, &nodes[1].node.get_our_node_id()).unwrap();
let node_0_shutdown = get_event_msg!(nodes[0], MessageSendEvent::SendShutdown, nodes[1].node.get_our_node_id());
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 route_1 = get_route(&nodes[0].node.get_our_node_id(), &payment_params_1, &nodes[0].network_graph.read_only(), None, 100000, &logger, &scorer, &(), &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 route_2 = get_route(&nodes[1].node.get_our_node_id(), &payment_params_2, &nodes[1].network_graph.read_only(), None, 100000, &logger, &scorer, &(), &random_seed_bytes).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 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();
unwrap_send_err!(nodes[0].node.send_payment_with_route(&route_1, payment_hash,
RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_hash.0)
), true, APIError::ChannelUnavailable {..}, {});
nodes[1].node.handle_closing_signed(&nodes[0].node.get_our_node_id(), &node_0_2nd_closing_signed.unwrap());
let (_, node_1_none) = get_closing_signed_broadcast!(nodes[1].node, nodes[0].node.get_our_node_id());
assert!(node_1_none.is_none());
- check_closed_event!(nodes[0], 1, ClosureReason::CooperativeClosure);
- check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure);
+ check_closed_event!(nodes[0], 1, ClosureReason::CooperativeClosure, [nodes[1].node.get_our_node_id()], 100000);
+ check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure, [nodes[0].node.get_our_node_id()], 100000);
assert!(nodes[0].node.list_channels().is_empty());
close_channel(&nodes[1], &nodes[2], &chan_2.2, chan_2.3, true);
assert!(nodes[1].node.list_channels().is_empty());
assert!(nodes[2].node.list_channels().is_empty());
- check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure);
- check_closed_event!(nodes[2], 1, ClosureReason::CooperativeClosure);
+ check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure, [nodes[2].node.get_our_node_id()], 100000);
+ check_closed_event!(nodes[2], 1, ClosureReason::CooperativeClosure, [nodes[1].node.get_our_node_id()], 100000);
}
#[test]
nodes[1].node.handle_commitment_signed(&nodes[0].node.get_our_node_id(), &updates.commitment_signed);
check_added_monitors!(nodes[1], 1);
nodes[1].node.handle_shutdown(&nodes[0].node.get_our_node_id(), &node_0_shutdown);
- commitment_signed_dance!(nodes[1], nodes[0], (), false, true, false);
+ commitment_signed_dance!(nodes[1], nodes[0], (), false, true, false, false);
let updates_2 = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
assert!(updates_2.update_add_htlcs.is_empty());
close_channel(&nodes[1], &nodes[2], &chan_2.2, chan_2.3, true);
assert!(nodes[1].node.list_channels().is_empty());
assert!(nodes[2].node.list_channels().is_empty());
- check_closed_event!(nodes[0], 1, ClosureReason::CooperativeClosure);
- check_closed_event!(nodes[1], 2, ClosureReason::CooperativeClosure);
- check_closed_event!(nodes[2], 1, ClosureReason::CooperativeClosure);
+ check_closed_event!(nodes[0], 1, ClosureReason::CooperativeClosure, [nodes[1].node.get_our_node_id()], 100000);
+ check_closed_event!(nodes[1], 2, ClosureReason::CooperativeClosure, [nodes[0].node.get_our_node_id(), nodes[2].node.get_our_node_id()], 100000);
+ check_closed_event!(nodes[2], 1, ClosureReason::CooperativeClosure, [nodes[1].node.get_our_node_id()], 100000);
}
fn do_test_shutdown_rebroadcast(recv_count: u8) {
let chan_1 = create_announced_chan_between_nodes(&nodes, 0, 1);
let chan_2 = create_announced_chan_between_nodes(&nodes, 1, 2);
- let (payment_preimage, payment_hash, _) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 100_000);
+ let (payment_preimage, payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 100_000);
nodes[1].node.close_channel(&chan_1.2, &nodes[0].node.get_our_node_id()).unwrap();
let node_1_shutdown = get_event_msg!(nodes[1], MessageSendEvent::SendShutdown, nodes[0].node.get_our_node_id());
nodes[1].node.handle_closing_signed(&nodes[0].node.get_our_node_id(), &node_0_2nd_closing_signed.unwrap());
let (_, node_1_none) = get_closing_signed_broadcast!(nodes[1].node, nodes[0].node.get_our_node_id());
assert!(node_1_none.is_none());
- check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure);
+ check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure, [nodes[0].node.get_our_node_id()], 100000);
} else {
// If one node, however, received + responded with an identical closing_signed we end
// up erroring and node[0] will try to broadcast its own latest commitment transaction.
// closing_signed so we do it ourselves
check_closed_broadcast!(nodes[1], false);
check_added_monitors!(nodes[1], 1);
- check_closed_event!(nodes[1], 1, ClosureReason::CounterpartyForceClosed { peer_msg: UntrustedString(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())) });
+ check_closed_event!(nodes[1], 1, ClosureReason::CounterpartyForceClosed { peer_msg: UntrustedString(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())) }
+ , [nodes[0].node.get_our_node_id()], 100000);
}
assert!(nodes[0].node.list_channels().is_empty());
close_channel(&nodes[1], &nodes[2], &chan_2.2, chan_2.3, true);
assert!(nodes[1].node.list_channels().is_empty());
assert!(nodes[2].node.list_channels().is_empty());
- check_closed_event!(nodes[0], 1, ClosureReason::CooperativeClosure);
- check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure);
- check_closed_event!(nodes[2], 1, ClosureReason::CooperativeClosure);
+ check_closed_event!(nodes[0], 1, ClosureReason::CooperativeClosure, [nodes[1].node.get_our_node_id()], 100000);
+ check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure, [nodes[2].node.get_our_node_id()], 100000);
+ check_closed_event!(nodes[2], 1, ClosureReason::CooperativeClosure, [nodes[1].node.get_our_node_id()], 100000);
}
#[test]
},
_ => panic!("Unexpected event"),
}
- check_closed_event!(nodes[0], 1, ClosureReason::ProcessingError { err: "Peer is signaling upfront_shutdown but has provided an unacceptable scriptpubkey format: Script(OP_PUSHNUM_16 OP_PUSHBYTES_2 0028)".to_string() });
+ check_closed_event!(nodes[0], 1, ClosureReason::ProcessingError { err: "Peer is signaling upfront_shutdown but has provided an unacceptable scriptpubkey format: Script(OP_PUSHNUM_16 OP_PUSHBYTES_2 0028)".to_string() }
+ , [nodes[1].node.get_our_node_id()], 100000);
}
#[test]
open_channel.shutdown_scriptpubkey = Some(Builder::new().push_int(0)
.push_slice(&[0, 0])
.into_script());
- nodes[0].node.handle_open_channel(&nodes[1].node.get_our_node_id(), &open_channel);
+ nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &open_channel);
- let events = nodes[0].node.get_and_clear_pending_msg_events();
+ let events = nodes[1].node.get_and_clear_pending_msg_events();
assert_eq!(events.len(), 1);
match events[0] {
MessageSendEvent::HandleError { action: ErrorAction::SendErrorMessage { ref msg }, node_id } => {
- assert_eq!(node_id, nodes[1].node.get_our_node_id());
+ assert_eq!(node_id, nodes[0].node.get_our_node_id());
assert_eq!(msg.data, "Peer is signaling upfront_shutdown but has provided an unacceptable scriptpubkey format: Script(OP_0 OP_PUSHBYTES_2 0000)");
},
_ => panic!("Unexpected event"),
{
let mut node_0_per_peer_lock;
let mut node_0_peer_state_lock;
- get_channel_ref!(nodes[0], nodes[1], node_0_per_peer_lock, node_0_peer_state_lock, chan_id).context.closing_fee_limits.as_mut().unwrap().1 *= 10;
+ get_channel_ref!(nodes[0], nodes[1], node_0_per_peer_lock, node_0_peer_state_lock, chan_id).context_mut().closing_fee_limits.as_mut().unwrap().1 *= 10;
}
nodes[1].node.handle_closing_signed(&nodes[0].node.get_our_node_id(), &node_0_closing_signed);
let node_1_closing_signed = get_event_msg!(nodes[1], MessageSendEvent::SendClosingSigned, nodes[0].node.get_our_node_id());
let node_0_2nd_closing_signed = get_closing_signed_broadcast!(nodes[0].node, nodes[1].node.get_our_node_id());
if timeout_step == TimeoutStep::NoTimeout {
nodes[1].node.handle_closing_signed(&nodes[0].node.get_our_node_id(), &node_0_2nd_closing_signed.1.unwrap());
- check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure);
+ check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure, [nodes[0].node.get_our_node_id()], 100000);
}
- check_closed_event!(nodes[0], 1, ClosureReason::CooperativeClosure);
+ check_closed_event!(nodes[0], 1, ClosureReason::CooperativeClosure, [nodes[1].node.get_our_node_id()], 100000);
}
if timeout_step != TimeoutStep::NoTimeout {
txn[0].output[0].script_pubkey.is_v0_p2wsh()));
check_closed_broadcast!(nodes[1], true);
check_added_monitors!(nodes[1], 1);
- check_closed_event!(nodes[1], 1, ClosureReason::ProcessingError { err: "closing_signed negotiation failed to finish within two timer ticks".to_string() });
+ check_closed_event!(nodes[1], 1, ClosureReason::ProcessingError { err: "closing_signed negotiation failed to finish within two timer ticks".to_string() }
+ , [nodes[0].node.get_our_node_id()], 100000);
} else {
assert!(txn[0].output[0].script_pubkey.is_v0_p2wpkh());
assert!(txn[0].output[1].script_pubkey.is_v0_p2wpkh());
nodes[0].node.handle_closing_signed(&nodes[1].node.get_our_node_id(), &node_1_closing_signed.unwrap());
let (_, node_0_none) = get_closing_signed_broadcast!(nodes[0].node, nodes[1].node.get_our_node_id());
assert!(node_0_none.is_none());
- check_closed_event!(nodes[0], 1, ClosureReason::CooperativeClosure);
- check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure);
+ check_closed_event!(nodes[0], 1, ClosureReason::CooperativeClosure, [nodes[1].node.get_our_node_id()], 100000);
+ check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure, [nodes[0].node.get_our_node_id()], 100000);
}
#[test]
nodes[0].node.handle_closing_signed(&nodes[1].node.get_our_node_id(), &node_1_closing_signed);
let (_, node_0_none) = get_closing_signed_broadcast!(nodes[0].node, nodes[1].node.get_our_node_id());
assert!(node_0_none.is_none());
- check_closed_event!(nodes[0], 1, ClosureReason::CooperativeClosure);
- check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure);
+ check_closed_event!(nodes[0], 1, ClosureReason::CooperativeClosure, [nodes[1].node.get_our_node_id()], 100000);
+ check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure, [nodes[0].node.get_our_node_id()], 100000);
}
//! Data structures and encoding for `invoice` messages.
//!
-//! An [`Invoice`] can be built from a parsed [`InvoiceRequest`] for the "offer to be paid" flow or
-//! from a [`Refund`] as an "offer for money" flow. The expected recipient of the payment then sends
-//! the invoice to the intended payer, who will then pay it.
+//! A [`Bolt12Invoice`] can be built from a parsed [`InvoiceRequest`] for the "offer to be paid"
+//! flow or from a [`Refund`] as an "offer for money" flow. The expected recipient of the payment
+//! then sends the invoice to the intended payer, who will then pay it.
//!
//! The payment recipient must include a [`PaymentHash`], so as to reveal the preimage upon payment
//! receipt, and one or more [`BlindedPath`]s for the payer to use when sending the payment.
//! # fn create_payment_paths() -> Vec<(BlindedPayInfo, BlindedPath)> { unimplemented!() }
//! # fn create_payment_hash() -> PaymentHash { unimplemented!() }
//! #
-//! # fn parse_invoice_request(bytes: Vec<u8>) -> Result<(), lightning::offers::parse::ParseError> {
+//! # fn parse_invoice_request(bytes: Vec<u8>) -> Result<(), lightning::offers::parse::Bolt12ParseError> {
//! let payment_paths = create_payment_paths();
//! let payment_hash = create_payment_hash();
//! let secp_ctx = Secp256k1::new();
//! .allow_mpp()
//! .fallback_v0_p2wpkh(&wpubkey_hash)
//! .build()?
-//! .sign::<_, Infallible>(|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys)))
+//! .sign::<_, Infallible>(
+//! |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
+//! )
//! .expect("failed verifying signature")
//! .write(&mut buffer)
//! .unwrap();
//! # Ok(())
//! # }
//!
-//! # fn parse_refund(bytes: Vec<u8>) -> Result<(), lightning::offers::parse::ParseError> {
+//! # fn parse_refund(bytes: Vec<u8>) -> Result<(), lightning::offers::parse::Bolt12ParseError> {
//! # let payment_paths = create_payment_paths();
//! # let payment_hash = create_payment_hash();
//! # let secp_ctx = Secp256k1::new();
//! .allow_mpp()
//! .fallback_v0_p2wpkh(&wpubkey_hash)
//! .build()?
-//! .sign::<_, Infallible>(|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys)))
+//! .sign::<_, Infallible>(
+//! |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
+//! )
//! .expect("failed verifying signature")
//! .write(&mut buffer)
//! .unwrap();
use bitcoin::hash_types::{WPubkeyHash, WScriptHash};
use bitcoin::hashes::Hash;
use bitcoin::network::constants::Network;
-use bitcoin::secp256k1::{KeyPair, Message, PublicKey, Secp256k1, self};
+use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, self};
use bitcoin::secp256k1::schnorr::Signature;
use bitcoin::util::address::{Address, Payload, WitnessVersion};
use bitcoin::util::schnorr::TweakedPublicKey;
-use core::convert::{Infallible, TryFrom};
+use core::convert::{AsRef, Infallible, TryFrom};
use core::time::Duration;
use crate::io;
use crate::blinded_path::BlindedPath;
use crate::ln::PaymentHash;
-use crate::ln::features::{BlindedHopFeatures, Bolt12InvoiceFeatures};
+use crate::ln::channelmanager::PaymentId;
+use crate::ln::features::{BlindedHopFeatures, Bolt12InvoiceFeatures, InvoiceRequestFeatures, OfferFeatures};
use crate::ln::inbound_payment::ExpandedKey;
use crate::ln::msgs::DecodeError;
use crate::offers::invoice_request::{INVOICE_REQUEST_PAYER_ID_TYPE, INVOICE_REQUEST_TYPES, IV_BYTES as INVOICE_REQUEST_IV_BYTES, InvoiceRequest, InvoiceRequestContents, InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef};
-use crate::offers::merkle::{SignError, SignatureTlvStream, SignatureTlvStreamRef, TlvStream, WithoutSignatures, self};
-use crate::offers::offer::{Amount, OFFER_TYPES, OfferTlvStream, OfferTlvStreamRef};
-use crate::offers::parse::{ParseError, ParsedMessage, SemanticError};
+use crate::offers::merkle::{SignError, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, TlvStream, WithoutSignatures, self};
+use crate::offers::offer::{Amount, OFFER_TYPES, OfferTlvStream, OfferTlvStreamRef, Quantity};
+use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError, ParsedMessage};
use crate::offers::payer::{PAYER_METADATA_TYPE, PayerTlvStream, PayerTlvStreamRef};
use crate::offers::refund::{IV_BYTES as REFUND_IV_BYTES, Refund, RefundContents};
use crate::offers::signer;
#[cfg(feature = "std")]
use std::time::SystemTime;
-const DEFAULT_RELATIVE_EXPIRY: Duration = Duration::from_secs(7200);
+pub(crate) const DEFAULT_RELATIVE_EXPIRY: Duration = Duration::from_secs(7200);
-pub(super) const SIGNATURE_TAG: &'static str = concat!("lightning", "invoice", "signature");
+/// Tag for the hash function used when signing a [`Bolt12Invoice`]'s merkle root.
+pub const SIGNATURE_TAG: &'static str = concat!("lightning", "invoice", "signature");
-/// Builds an [`Invoice`] from either:
+/// Builds a [`Bolt12Invoice`] from either:
/// - an [`InvoiceRequest`] for the "offer to be paid" flow or
/// - a [`Refund`] for the "offer for money" flow.
///
pub struct InvoiceBuilder<'a, S: SigningPubkeyStrategy> {
invreq_bytes: &'a Vec<u8>,
invoice: InvoiceContents,
- keys: Option<KeyPair>,
- signing_pubkey_strategy: core::marker::PhantomData<S>,
+ signing_pubkey_strategy: S,
}
-/// Indicates how [`Invoice::signing_pubkey`] was set.
+/// Indicates how [`Bolt12Invoice::signing_pubkey`] was set.
///
/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
pub trait SigningPubkeyStrategy {}
-/// [`Invoice::signing_pubkey`] was explicitly set.
+/// [`Bolt12Invoice::signing_pubkey`] was explicitly set.
///
/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
pub struct ExplicitSigningPubkey {}
-/// [`Invoice::signing_pubkey`] was derived.
+/// [`Bolt12Invoice::signing_pubkey`] was derived.
///
/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
-pub struct DerivedSigningPubkey {}
+pub struct DerivedSigningPubkey(KeyPair);
impl SigningPubkeyStrategy for ExplicitSigningPubkey {}
impl SigningPubkeyStrategy for DerivedSigningPubkey {}
pub(super) fn for_offer(
invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>,
created_at: Duration, payment_hash: PaymentHash
- ) -> Result<Self, SemanticError> {
+ ) -> Result<Self, Bolt12SemanticError> {
let amount_msats = Self::check_amount_msats(invoice_request)?;
let signing_pubkey = invoice_request.contents.inner.offer.signing_pubkey();
let contents = InvoiceContents::ForOffer {
),
};
- Self::new(&invoice_request.bytes, contents, None)
+ Self::new(&invoice_request.bytes, contents, ExplicitSigningPubkey {})
}
pub(super) fn for_refund(
refund: &'a Refund, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, created_at: Duration,
payment_hash: PaymentHash, signing_pubkey: PublicKey
- ) -> Result<Self, SemanticError> {
+ ) -> Result<Self, Bolt12SemanticError> {
let amount_msats = refund.amount_msats();
let contents = InvoiceContents::ForRefund {
refund: refund.contents.clone(),
),
};
- Self::new(&refund.bytes, contents, None)
+ Self::new(&refund.bytes, contents, ExplicitSigningPubkey {})
}
}
pub(super) fn for_offer_using_keys(
invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>,
created_at: Duration, payment_hash: PaymentHash, keys: KeyPair
- ) -> Result<Self, SemanticError> {
+ ) -> Result<Self, Bolt12SemanticError> {
let amount_msats = Self::check_amount_msats(invoice_request)?;
let signing_pubkey = invoice_request.contents.inner.offer.signing_pubkey();
let contents = InvoiceContents::ForOffer {
),
};
- Self::new(&invoice_request.bytes, contents, Some(keys))
+ Self::new(&invoice_request.bytes, contents, DerivedSigningPubkey(keys))
}
pub(super) fn for_refund_using_keys(
refund: &'a Refund, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, created_at: Duration,
payment_hash: PaymentHash, keys: KeyPair,
- ) -> Result<Self, SemanticError> {
+ ) -> Result<Self, Bolt12SemanticError> {
let amount_msats = refund.amount_msats();
let signing_pubkey = keys.public_key();
let contents = InvoiceContents::ForRefund {
),
};
- Self::new(&refund.bytes, contents, Some(keys))
+ Self::new(&refund.bytes, contents, DerivedSigningPubkey(keys))
}
}
impl<'a, S: SigningPubkeyStrategy> InvoiceBuilder<'a, S> {
- fn check_amount_msats(invoice_request: &InvoiceRequest) -> Result<u64, SemanticError> {
+ fn check_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() {
Some(Amount::Bitcoin { amount_msats }) => {
amount_msats.checked_mul(invoice_request.quantity().unwrap_or(1))
- .ok_or(SemanticError::InvalidAmount)
+ .ok_or(Bolt12SemanticError::InvalidAmount)
},
- Some(Amount::Currency { .. }) => Err(SemanticError::UnsupportedCurrency),
- None => Err(SemanticError::MissingAmount),
+ Some(Amount::Currency { .. }) => Err(Bolt12SemanticError::UnsupportedCurrency),
+ None => Err(Bolt12SemanticError::MissingAmount),
},
}
}
}
fn new(
- invreq_bytes: &'a Vec<u8>, contents: InvoiceContents, keys: Option<KeyPair>
- ) -> Result<Self, SemanticError> {
+ invreq_bytes: &'a Vec<u8>, contents: InvoiceContents, signing_pubkey_strategy: S
+ ) -> Result<Self, Bolt12SemanticError> {
if contents.fields().payment_paths.is_empty() {
- return Err(SemanticError::MissingPaths);
+ return Err(Bolt12SemanticError::MissingPaths);
}
- Ok(Self {
- invreq_bytes,
- invoice: contents,
- keys,
- signing_pubkey_strategy: core::marker::PhantomData,
- })
+ Ok(Self { invreq_bytes, invoice: contents, signing_pubkey_strategy })
}
- /// Sets the [`Invoice::relative_expiry`] as seconds since [`Invoice::created_at`]. Any expiry
- /// that has already passed is valid and can be checked for using [`Invoice::is_expired`].
+ /// Sets the [`Bolt12Invoice::relative_expiry`] as seconds since [`Bolt12Invoice::created_at`].
+ /// Any expiry that has already passed is valid and can be checked for using
+ /// [`Bolt12Invoice::is_expired`].
///
/// Successive calls to this method will override the previous setting.
pub fn relative_expiry(mut self, relative_expiry_secs: u32) -> Self {
self
}
- /// Adds a P2WSH address to [`Invoice::fallbacks`].
+ /// Adds a P2WSH address to [`Bolt12Invoice::fallbacks`].
///
/// Successive calls to this method will add another address. Caller is responsible for not
/// adding duplicate addresses and only calling if capable of receiving to P2WSH addresses.
self
}
- /// Adds a P2WPKH address to [`Invoice::fallbacks`].
+ /// Adds a P2WPKH address to [`Bolt12Invoice::fallbacks`].
///
/// Successive calls to this method will add another address. Caller is responsible for not
/// adding duplicate addresses and only calling if capable of receiving to P2WPKH addresses.
self
}
- /// Adds a P2TR address to [`Invoice::fallbacks`].
+ /// Adds a P2TR address to [`Bolt12Invoice::fallbacks`].
///
/// Successive calls to this method will add another address. Caller is responsible for not
/// adding duplicate addresses and only calling if capable of receiving to P2TR addresses.
self
}
- /// Sets [`Invoice::features`] to indicate MPP may be used. Otherwise, MPP is disallowed.
+ /// Sets [`Bolt12Invoice::invoice_features`] to indicate MPP may be used. Otherwise, MPP is
+ /// disallowed.
pub fn allow_mpp(mut self) -> Self {
self.invoice.fields_mut().features.set_basic_mpp_optional();
self
}
impl<'a> InvoiceBuilder<'a, ExplicitSigningPubkey> {
- /// Builds an unsigned [`Invoice`] after checking for valid semantics. It can be signed by
- /// [`UnsignedInvoice::sign`].
- pub fn build(self) -> Result<UnsignedInvoice<'a>, SemanticError> {
+ /// Builds an unsigned [`Bolt12Invoice`] after checking for valid semantics. It can be signed by
+ /// [`UnsignedBolt12Invoice::sign`].
+ pub fn build(self) -> Result<UnsignedBolt12Invoice, Bolt12SemanticError> {
#[cfg(feature = "std")] {
if self.invoice.is_offer_or_refund_expired() {
- return Err(SemanticError::AlreadyExpired);
+ return Err(Bolt12SemanticError::AlreadyExpired);
}
}
let InvoiceBuilder { invreq_bytes, invoice, .. } = self;
- Ok(UnsignedInvoice { invreq_bytes, invoice })
+ Ok(UnsignedBolt12Invoice::new(invreq_bytes, invoice))
}
}
impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> {
- /// Builds a signed [`Invoice`] after checking for valid semantics.
+ /// Builds a signed [`Bolt12Invoice`] after checking for valid semantics.
pub fn build_and_sign<T: secp256k1::Signing>(
self, secp_ctx: &Secp256k1<T>
- ) -> Result<Invoice, SemanticError> {
+ ) -> Result<Bolt12Invoice, Bolt12SemanticError> {
#[cfg(feature = "std")] {
if self.invoice.is_offer_or_refund_expired() {
- return Err(SemanticError::AlreadyExpired);
+ return Err(Bolt12SemanticError::AlreadyExpired);
}
}
- let InvoiceBuilder { invreq_bytes, invoice, keys, .. } = self;
- let unsigned_invoice = UnsignedInvoice { invreq_bytes, invoice };
+ let InvoiceBuilder {
+ invreq_bytes, invoice, signing_pubkey_strategy: DerivedSigningPubkey(keys)
+ } = self;
+ let unsigned_invoice = UnsignedBolt12Invoice::new(invreq_bytes, invoice);
- let keys = keys.unwrap();
let invoice = unsigned_invoice
- .sign::<_, Infallible>(|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys)))
+ .sign::<_, Infallible>(
+ |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
+ )
.unwrap();
Ok(invoice)
}
}
-/// A semantically valid [`Invoice`] that hasn't been signed.
-pub struct UnsignedInvoice<'a> {
- invreq_bytes: &'a Vec<u8>,
- invoice: InvoiceContents,
+/// A semantically valid [`Bolt12Invoice`] that hasn't been signed.
+///
+/// # Serialization
+///
+/// This is serialized as a TLV stream, which includes TLV records from the originating message. As
+/// such, it may include unknown, odd TLV records.
+pub struct UnsignedBolt12Invoice {
+ bytes: Vec<u8>,
+ contents: InvoiceContents,
+ tagged_hash: TaggedHash,
}
-impl<'a> UnsignedInvoice<'a> {
- /// The public key corresponding to the key needed to sign the invoice.
- pub fn signing_pubkey(&self) -> PublicKey {
- self.invoice.fields().signing_pubkey
- }
-
- /// Signs the invoice using the given function.
- ///
- /// This is not exported to bindings users as functions aren't currently mapped.
- pub fn sign<F, E>(self, sign: F) -> Result<Invoice, SignError<E>>
- where
- F: FnOnce(&Message) -> Result<Signature, E>
- {
+impl UnsignedBolt12Invoice {
+ fn new(invreq_bytes: &[u8], contents: InvoiceContents) -> Self {
// Use the invoice_request bytes instead of the invoice_request TLV stream as the latter may
// have contained unknown TLV records, which are not stored in `InvoiceRequestContents` or
// `RefundContents`.
- let (_, _, _, invoice_tlv_stream) = self.invoice.as_tlv_stream();
- let invoice_request_bytes = WithoutSignatures(self.invreq_bytes);
+ let (_, _, _, invoice_tlv_stream) = contents.as_tlv_stream();
+ let invoice_request_bytes = WithoutSignatures(invreq_bytes);
let unsigned_tlv_stream = (invoice_request_bytes, invoice_tlv_stream);
let mut bytes = Vec::new();
unsigned_tlv_stream.write(&mut bytes).unwrap();
- let pubkey = self.invoice.fields().signing_pubkey;
- let signature = merkle::sign_message(sign, SIGNATURE_TAG, &bytes, pubkey)?;
+ let tagged_hash = TaggedHash::new(SIGNATURE_TAG, &bytes);
+
+ Self { bytes, contents, tagged_hash }
+ }
+
+ /// Returns the [`TaggedHash`] of the invoice to sign.
+ pub fn tagged_hash(&self) -> &TaggedHash {
+ &self.tagged_hash
+ }
+
+ /// Signs the [`TaggedHash`] of the invoice using the given function.
+ ///
+ /// Note: The hash computation may have included unknown, odd TLV records.
+ ///
+ /// This is not exported to bindings users as functions aren't currently mapped.
+ pub fn sign<F, E>(mut self, sign: F) -> Result<Bolt12Invoice, SignError<E>>
+ where
+ F: FnOnce(&Self) -> Result<Signature, E>
+ {
+ let pubkey = self.contents.fields().signing_pubkey;
+ let signature = merkle::sign_message(sign, &self, pubkey)?;
// Append the signature TLV record to the bytes.
let signature_tlv_stream = SignatureTlvStreamRef {
signature: Some(&signature),
};
- signature_tlv_stream.write(&mut bytes).unwrap();
+ signature_tlv_stream.write(&mut self.bytes).unwrap();
- Ok(Invoice {
- bytes,
- contents: self.invoice,
+ Ok(Bolt12Invoice {
+ bytes: self.bytes,
+ contents: self.contents,
signature,
})
}
}
-/// An `Invoice` is a payment request, typically corresponding to an [`Offer`] or a [`Refund`].
+impl AsRef<TaggedHash> for UnsignedBolt12Invoice {
+ fn as_ref(&self) -> &TaggedHash {
+ &self.tagged_hash
+ }
+}
+
+/// A `Bolt12Invoice` is a payment request, typically corresponding to an [`Offer`] or a [`Refund`].
///
/// An invoice may be sent in response to an [`InvoiceRequest`] in the case of an offer or sent
/// directly after scanning a refund. It includes all the information needed to pay a recipient.
///
-/// This is not exported to bindings users as its name conflicts with the BOLT 11 Invoice type.
-///
/// [`Offer`]: crate::offers::offer::Offer
/// [`Refund`]: crate::offers::refund::Refund
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
#[derive(Clone, Debug)]
#[cfg_attr(test, derive(PartialEq))]
-pub struct Invoice {
+pub struct Bolt12Invoice {
bytes: Vec<u8>,
contents: InvoiceContents,
signature: Signature,
}
-/// The contents of an [`Invoice`] for responding to either an [`Offer`] or a [`Refund`].
+/// The contents of an [`Bolt12Invoice`] for responding to either an [`Offer`] or a [`Refund`].
///
/// [`Offer`]: crate::offers::offer::Offer
/// [`Refund`]: crate::offers::refund::Refund
#[derive(Clone, Debug)]
#[cfg_attr(test, derive(PartialEq))]
enum InvoiceContents {
- /// Contents for an [`Invoice`] corresponding to an [`Offer`].
+ /// Contents for an [`Bolt12Invoice`] corresponding to an [`Offer`].
///
/// [`Offer`]: crate::offers::offer::Offer
ForOffer {
invoice_request: InvoiceRequestContents,
fields: InvoiceFields,
},
- /// Contents for an [`Invoice`] corresponding to a [`Refund`].
+ /// Contents for an [`Bolt12Invoice`] corresponding to a [`Refund`].
///
/// [`Refund`]: crate::offers::refund::Refund
ForRefund {
signing_pubkey: PublicKey,
}
-impl Invoice {
- /// A complete description of the purpose of the originating offer or refund. Intended to be
- /// displayed to the user but with the caveat that it has not been verified in any way.
- pub fn description(&self) -> PrintableString {
- self.contents.description()
+macro_rules! invoice_accessors { ($self: ident, $contents: expr) => {
+ /// The chains that may be used when paying a requested invoice.
+ ///
+ /// From [`Offer::chains`]; `None` if the invoice was created in response to a [`Refund`].
+ ///
+ /// [`Offer::chains`]: crate::offers::offer::Offer::chains
+ pub fn offer_chains(&$self) -> Option<Vec<ChainHash>> {
+ $contents.offer_chains()
+ }
+
+ /// The chain that must be used when paying the invoice; selected from [`offer_chains`] if the
+ /// invoice originated from an offer.
+ ///
+ /// From [`InvoiceRequest::chain`] or [`Refund::chain`].
+ ///
+ /// [`offer_chains`]: Self::offer_chains
+ /// [`InvoiceRequest::chain`]: crate::offers::invoice_request::InvoiceRequest::chain
+ pub fn chain(&$self) -> ChainHash {
+ $contents.chain()
+ }
+
+ /// Opaque bytes set by the originating [`Offer`].
+ ///
+ /// From [`Offer::metadata`]; `None` if the invoice was created in response to a [`Refund`] or
+ /// if the [`Offer`] did not set it.
+ ///
+ /// [`Offer`]: crate::offers::offer::Offer
+ /// [`Offer::metadata`]: crate::offers::offer::Offer::metadata
+ pub fn metadata(&$self) -> Option<&Vec<u8>> {
+ $contents.metadata()
+ }
+
+ /// The minimum amount required for a successful payment of a single item.
+ ///
+ /// From [`Offer::amount`]; `None` if the invoice was created in response to a [`Refund`] or if
+ /// the [`Offer`] did not set it.
+ ///
+ /// [`Offer`]: crate::offers::offer::Offer
+ /// [`Offer::amount`]: crate::offers::offer::Offer::amount
+ pub fn amount(&$self) -> Option<&Amount> {
+ $contents.amount()
+ }
+
+ /// Features pertaining to the originating [`Offer`].
+ ///
+ /// From [`Offer::offer_features`]; `None` if the invoice was created in response to a
+ /// [`Refund`].
+ ///
+ /// [`Offer`]: crate::offers::offer::Offer
+ /// [`Offer::offer_features`]: crate::offers::offer::Offer::offer_features
+ pub fn offer_features(&$self) -> Option<&OfferFeatures> {
+ $contents.offer_features()
+ }
+
+ /// A complete description of the purpose of the originating offer or refund.
+ ///
+ /// From [`Offer::description`] or [`Refund::description`].
+ ///
+ /// [`Offer::description`]: crate::offers::offer::Offer::description
+ pub fn description(&$self) -> PrintableString {
+ $contents.description()
+ }
+
+ /// Duration since the Unix epoch when an invoice should no longer be requested.
+ ///
+ /// From [`Offer::absolute_expiry`] or [`Refund::absolute_expiry`].
+ ///
+ /// [`Offer::absolute_expiry`]: crate::offers::offer::Offer::absolute_expiry
+ pub fn absolute_expiry(&$self) -> Option<Duration> {
+ $contents.absolute_expiry()
+ }
+
+ /// The issuer of the offer or refund.
+ ///
+ /// From [`Offer::issuer`] or [`Refund::issuer`].
+ ///
+ /// [`Offer::issuer`]: crate::offers::offer::Offer::issuer
+ pub fn issuer(&$self) -> Option<PrintableString> {
+ $contents.issuer()
+ }
+
+ /// Paths to the recipient originating from publicly reachable nodes.
+ ///
+ /// From [`Offer::paths`] or [`Refund::paths`].
+ ///
+ /// [`Offer::paths`]: crate::offers::offer::Offer::paths
+ pub fn message_paths(&$self) -> &[BlindedPath] {
+ $contents.message_paths()
+ }
+
+ /// The quantity of items supported.
+ ///
+ /// From [`Offer::supported_quantity`]; `None` if the invoice was created in response to a
+ /// [`Refund`].
+ ///
+ /// [`Offer::supported_quantity`]: crate::offers::offer::Offer::supported_quantity
+ pub fn supported_quantity(&$self) -> Option<Quantity> {
+ $contents.supported_quantity()
+ }
+
+ /// An unpredictable series of bytes from the payer.
+ ///
+ /// From [`InvoiceRequest::payer_metadata`] or [`Refund::payer_metadata`].
+ pub fn payer_metadata(&$self) -> &[u8] {
+ $contents.payer_metadata()
+ }
+
+ /// Features pertaining to requesting an invoice.
+ ///
+ /// From [`InvoiceRequest::invoice_request_features`] or [`Refund::features`].
+ pub fn invoice_request_features(&$self) -> &InvoiceRequestFeatures {
+ &$contents.invoice_request_features()
+ }
+
+ /// The quantity of items requested or refunded for.
+ ///
+ /// From [`InvoiceRequest::quantity`] or [`Refund::quantity`].
+ pub fn quantity(&$self) -> Option<u64> {
+ $contents.quantity()
+ }
+
+ /// A possibly transient pubkey used to sign the invoice request or to send an invoice for a
+ /// refund in case there are no [`message_paths`].
+ ///
+ /// [`message_paths`]: Self::message_paths
+ pub fn payer_id(&$self) -> PublicKey {
+ $contents.payer_id()
+ }
+
+ /// A payer-provided note reflected back in the invoice.
+ ///
+ /// From [`InvoiceRequest::payer_note`] or [`Refund::payer_note`].
+ pub fn payer_note(&$self) -> Option<PrintableString> {
+ $contents.payer_note()
}
/// Paths to the recipient originating from publicly reachable nodes, including information
/// needed for routing payments across them.
///
/// Blinded paths provide recipient privacy by obfuscating its node id. Note, however, that this
- /// privacy is lost if a public node id is used for [`Invoice::signing_pubkey`].
- pub fn payment_paths(&self) -> &[(BlindedPayInfo, BlindedPath)] {
- &self.contents.fields().payment_paths[..]
+ /// privacy is lost if a public node id is used for [`Bolt12Invoice::signing_pubkey`].
+ ///
+ /// This is not exported to bindings users as slices with non-reference types cannot be ABI
+ /// matched in another language.
+ pub fn payment_paths(&$self) -> &[(BlindedPayInfo, BlindedPath)] {
+ $contents.payment_paths()
}
/// Duration since the Unix epoch when the invoice was created.
- pub fn created_at(&self) -> Duration {
- self.contents.fields().created_at
+ pub fn created_at(&$self) -> Duration {
+ $contents.created_at()
}
- /// Duration since [`Invoice::created_at`] when the invoice has expired and therefore should no
- /// longer be paid.
- pub fn relative_expiry(&self) -> Duration {
- self.contents.fields().relative_expiry.unwrap_or(DEFAULT_RELATIVE_EXPIRY)
+ /// Duration since [`Bolt12Invoice::created_at`] when the invoice has expired and therefore
+ /// should no longer be paid.
+ pub fn relative_expiry(&$self) -> Duration {
+ $contents.relative_expiry()
}
/// Whether the invoice has expired.
#[cfg(feature = "std")]
- pub fn is_expired(&self) -> bool {
- let absolute_expiry = self.created_at().checked_add(self.relative_expiry());
- match absolute_expiry {
- Some(seconds_from_epoch) => match SystemTime::UNIX_EPOCH.elapsed() {
- Ok(elapsed) => elapsed > seconds_from_epoch,
- Err(_) => false,
- },
- None => false,
- }
+ pub fn is_expired(&$self) -> bool {
+ $contents.is_expired()
}
/// SHA256 hash of the payment preimage that will be given in return for paying the invoice.
- pub fn payment_hash(&self) -> PaymentHash {
- self.contents.fields().payment_hash
+ pub fn payment_hash(&$self) -> PaymentHash {
+ $contents.payment_hash()
}
/// The minimum amount required for a successful payment of the invoice.
- pub fn amount_msats(&self) -> u64 {
- self.contents.fields().amount_msats
+ pub fn amount_msats(&$self) -> u64 {
+ $contents.amount_msats()
}
/// Fallback addresses for paying the invoice on-chain, in order of most-preferred to
/// least-preferred.
- pub fn fallbacks(&self) -> Vec<Address> {
- let network = match self.network() {
- None => return Vec::new(),
- Some(network) => network,
- };
-
- let to_valid_address = |address: &FallbackAddress| {
- let version = match WitnessVersion::try_from(address.version) {
- Ok(version) => version,
- Err(_) => return None,
- };
-
- let program = &address.program;
- if program.len() < 2 || program.len() > 40 {
- return None;
- }
-
- let address = Address {
- payload: Payload::WitnessProgram {
- version,
- program: address.program.clone(),
- },
- network,
- };
-
- if !address.is_standard() && version == WitnessVersion::V0 {
- return None;
- }
-
- Some(address)
- };
-
- self.contents.fields().fallbacks
- .as_ref()
- .map(|fallbacks| fallbacks.iter().filter_map(to_valid_address).collect())
- .unwrap_or_else(Vec::new)
- }
-
- fn network(&self) -> Option<Network> {
- let chain = self.contents.chain();
- if chain == ChainHash::using_genesis_block(Network::Bitcoin) {
- Some(Network::Bitcoin)
- } else if chain == ChainHash::using_genesis_block(Network::Testnet) {
- Some(Network::Testnet)
- } else if chain == ChainHash::using_genesis_block(Network::Signet) {
- Some(Network::Signet)
- } else if chain == ChainHash::using_genesis_block(Network::Regtest) {
- Some(Network::Regtest)
- } else {
- None
- }
+ pub fn fallbacks(&$self) -> Vec<Address> {
+ $contents.fallbacks()
}
/// Features pertaining to paying an invoice.
- pub fn features(&self) -> &Bolt12InvoiceFeatures {
- &self.contents.fields().features
+ pub fn invoice_features(&$self) -> &Bolt12InvoiceFeatures {
+ $contents.features()
}
/// The public key corresponding to the key used to sign the invoice.
- pub fn signing_pubkey(&self) -> PublicKey {
- self.contents.fields().signing_pubkey
+ pub fn signing_pubkey(&$self) -> PublicKey {
+ $contents.signing_pubkey()
}
+} }
+
+impl UnsignedBolt12Invoice {
+ invoice_accessors!(self, self.contents);
+}
- /// Signature of the invoice verified using [`Invoice::signing_pubkey`].
+impl Bolt12Invoice {
+ invoice_accessors!(self, self.contents);
+
+ /// Signature of the invoice verified using [`Bolt12Invoice::signing_pubkey`].
pub fn signature(&self) -> Signature {
self.signature
}
merkle::message_digest(SIGNATURE_TAG, &self.bytes).as_ref().clone()
}
- /// Verifies that the invoice was for a request or refund created using the given key.
+ /// Verifies that the invoice was for a request or refund created using the given key. Returns
+ /// the associated [`PaymentId`] to use when sending the payment.
pub fn verify<T: secp256k1::Signing>(
&self, key: &ExpandedKey, secp_ctx: &Secp256k1<T>
- ) -> bool {
+ ) -> Result<PaymentId, ()> {
self.contents.verify(TlvStream::new(&self.bytes), key, secp_ctx)
}
}
}
+ fn offer_chains(&self) -> Option<Vec<ChainHash>> {
+ match self {
+ InvoiceContents::ForOffer { invoice_request, .. } =>
+ Some(invoice_request.inner.offer.chains()),
+ InvoiceContents::ForRefund { .. } => None,
+ }
+ }
+
fn chain(&self) -> ChainHash {
match self {
InvoiceContents::ForOffer { invoice_request, .. } => invoice_request.chain(),
}
}
+ fn metadata(&self) -> Option<&Vec<u8>> {
+ match self {
+ InvoiceContents::ForOffer { invoice_request, .. } =>
+ invoice_request.inner.offer.metadata(),
+ InvoiceContents::ForRefund { .. } => None,
+ }
+ }
+
+ fn amount(&self) -> Option<&Amount> {
+ match self {
+ InvoiceContents::ForOffer { invoice_request, .. } =>
+ invoice_request.inner.offer.amount(),
+ InvoiceContents::ForRefund { .. } => None,
+ }
+ }
+
fn description(&self) -> PrintableString {
match self {
InvoiceContents::ForOffer { invoice_request, .. } => {
}
}
+ fn offer_features(&self) -> Option<&OfferFeatures> {
+ match self {
+ InvoiceContents::ForOffer { invoice_request, .. } => {
+ Some(invoice_request.inner.offer.features())
+ },
+ InvoiceContents::ForRefund { .. } => None,
+ }
+ }
+
+ fn absolute_expiry(&self) -> Option<Duration> {
+ match self {
+ InvoiceContents::ForOffer { invoice_request, .. } => {
+ invoice_request.inner.offer.absolute_expiry()
+ },
+ InvoiceContents::ForRefund { refund, .. } => refund.absolute_expiry(),
+ }
+ }
+
+ fn issuer(&self) -> Option<PrintableString> {
+ match self {
+ InvoiceContents::ForOffer { invoice_request, .. } => {
+ invoice_request.inner.offer.issuer()
+ },
+ InvoiceContents::ForRefund { refund, .. } => refund.issuer(),
+ }
+ }
+
+ fn message_paths(&self) -> &[BlindedPath] {
+ match self {
+ InvoiceContents::ForOffer { invoice_request, .. } => {
+ invoice_request.inner.offer.paths()
+ },
+ InvoiceContents::ForRefund { refund, .. } => refund.paths(),
+ }
+ }
+
+ fn supported_quantity(&self) -> Option<Quantity> {
+ match self {
+ InvoiceContents::ForOffer { invoice_request, .. } => {
+ Some(invoice_request.inner.offer.supported_quantity())
+ },
+ InvoiceContents::ForRefund { .. } => None,
+ }
+ }
+
+ fn payer_metadata(&self) -> &[u8] {
+ match self {
+ InvoiceContents::ForOffer { invoice_request, .. } => invoice_request.metadata(),
+ InvoiceContents::ForRefund { refund, .. } => refund.metadata(),
+ }
+ }
+
+ fn invoice_request_features(&self) -> &InvoiceRequestFeatures {
+ match self {
+ InvoiceContents::ForOffer { invoice_request, .. } => invoice_request.features(),
+ InvoiceContents::ForRefund { refund, .. } => refund.features(),
+ }
+ }
+
+ fn quantity(&self) -> Option<u64> {
+ match self {
+ InvoiceContents::ForOffer { invoice_request, .. } => invoice_request.quantity(),
+ InvoiceContents::ForRefund { refund, .. } => refund.quantity(),
+ }
+ }
+
+ fn payer_id(&self) -> PublicKey {
+ match self {
+ InvoiceContents::ForOffer { invoice_request, .. } => invoice_request.payer_id(),
+ InvoiceContents::ForRefund { refund, .. } => refund.payer_id(),
+ }
+ }
+
+ fn payer_note(&self) -> Option<PrintableString> {
+ match self {
+ InvoiceContents::ForOffer { invoice_request, .. } => invoice_request.payer_note(),
+ InvoiceContents::ForRefund { refund, .. } => refund.payer_note(),
+ }
+ }
+
+ fn payment_paths(&self) -> &[(BlindedPayInfo, BlindedPath)] {
+ &self.fields().payment_paths[..]
+ }
+
+ fn created_at(&self) -> Duration {
+ self.fields().created_at
+ }
+
+ fn relative_expiry(&self) -> Duration {
+ self.fields().relative_expiry.unwrap_or(DEFAULT_RELATIVE_EXPIRY)
+ }
+
+ #[cfg(feature = "std")]
+ fn is_expired(&self) -> bool {
+ let absolute_expiry = self.created_at().checked_add(self.relative_expiry());
+ match absolute_expiry {
+ Some(seconds_from_epoch) => match SystemTime::UNIX_EPOCH.elapsed() {
+ Ok(elapsed) => elapsed > seconds_from_epoch,
+ Err(_) => false,
+ },
+ None => false,
+ }
+ }
+
+ fn payment_hash(&self) -> PaymentHash {
+ self.fields().payment_hash
+ }
+
+ fn amount_msats(&self) -> u64 {
+ self.fields().amount_msats
+ }
+
+ fn fallbacks(&self) -> Vec<Address> {
+ let chain = self.chain();
+ let network = if chain == ChainHash::using_genesis_block(Network::Bitcoin) {
+ Network::Bitcoin
+ } else if chain == ChainHash::using_genesis_block(Network::Testnet) {
+ Network::Testnet
+ } else if chain == ChainHash::using_genesis_block(Network::Signet) {
+ Network::Signet
+ } else if chain == ChainHash::using_genesis_block(Network::Regtest) {
+ Network::Regtest
+ } else {
+ return Vec::new()
+ };
+
+ let to_valid_address = |address: &FallbackAddress| {
+ let version = match WitnessVersion::try_from(address.version) {
+ Ok(version) => version,
+ Err(_) => return None,
+ };
+
+ let program = &address.program;
+ if program.len() < 2 || program.len() > 40 {
+ return None;
+ }
+
+ let address = Address {
+ payload: Payload::WitnessProgram {
+ version,
+ program: program.clone(),
+ },
+ network,
+ };
+
+ if !address.is_standard() && version == WitnessVersion::V0 {
+ return None;
+ }
+
+ Some(address)
+ };
+
+ self.fields().fallbacks
+ .as_ref()
+ .map(|fallbacks| fallbacks.iter().filter_map(to_valid_address).collect())
+ .unwrap_or_else(Vec::new)
+ }
+
+ fn features(&self) -> &Bolt12InvoiceFeatures {
+ &self.fields().features
+ }
+
+ fn signing_pubkey(&self) -> PublicKey {
+ self.fields().signing_pubkey
+ }
+
fn fields(&self) -> &InvoiceFields {
match self {
InvoiceContents::ForOffer { fields, .. } => fields,
fn verify<T: secp256k1::Signing>(
&self, tlv_stream: TlvStream<'_>, key: &ExpandedKey, secp_ctx: &Secp256k1<T>
- ) -> bool {
+ ) -> Result<PaymentId, ()> {
let offer_records = tlv_stream.clone().range(OFFER_TYPES);
let invreq_records = tlv_stream.range(INVOICE_REQUEST_TYPES).filter(|record| {
match record.r#type {
},
};
- match signer::verify_metadata(metadata, key, iv_bytes, payer_id, tlv_stream, secp_ctx) {
- Ok(_) => true,
- Err(()) => false,
- }
+ signer::verify_payer_metadata(metadata, key, iv_bytes, payer_id, tlv_stream, secp_ctx)
}
fn derives_keys(&self) -> bool {
}
}
-impl Writeable for Invoice {
+impl Writeable for UnsignedBolt12Invoice {
+ fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
+ WithoutLength(&self.bytes).write(writer)
+ }
+}
+
+impl Writeable for Bolt12Invoice {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
WithoutLength(&self.bytes).write(writer)
}
}
}
-impl TryFrom<Vec<u8>> for Invoice {
- type Error = ParseError;
+impl TryFrom<Vec<u8>> for UnsignedBolt12Invoice {
+ type Error = Bolt12ParseError;
+
+ fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
+ let invoice = ParsedMessage::<PartialInvoiceTlvStream>::try_from(bytes)?;
+ let ParsedMessage { bytes, tlv_stream } = invoice;
+ let (
+ payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
+ ) = tlv_stream;
+ let contents = InvoiceContents::try_from(
+ (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream)
+ )?;
+
+ let tagged_hash = TaggedHash::new(SIGNATURE_TAG, &bytes);
+
+ Ok(UnsignedBolt12Invoice { bytes, contents, tagged_hash })
+ }
+}
+
+impl TryFrom<Vec<u8>> for Bolt12Invoice {
+ type Error = Bolt12ParseError;
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
let parsed_invoice = ParsedMessage::<FullInvoiceTlvStream>::try_from(bytes)?;
- Invoice::try_from(parsed_invoice)
+ Bolt12Invoice::try_from(parsed_invoice)
}
}
InvoiceTlvStreamRef<'a>,
);
-impl TryFrom<ParsedMessage<FullInvoiceTlvStream>> for Invoice {
- type Error = ParseError;
+impl SeekReadable for PartialInvoiceTlvStream {
+ fn read<R: io::Read + io::Seek>(r: &mut R) -> Result<Self, DecodeError> {
+ let payer = SeekReadable::read(r)?;
+ let offer = SeekReadable::read(r)?;
+ let invoice_request = SeekReadable::read(r)?;
+ let invoice = SeekReadable::read(r)?;
+
+ Ok((payer, offer, invoice_request, invoice))
+ }
+}
+
+impl TryFrom<ParsedMessage<FullInvoiceTlvStream>> for Bolt12Invoice {
+ type Error = Bolt12ParseError;
fn try_from(invoice: ParsedMessage<FullInvoiceTlvStream>) -> Result<Self, Self::Error> {
let ParsedMessage { bytes, tlv_stream } = invoice;
)?;
let signature = match signature {
- None => return Err(ParseError::InvalidSemantics(SemanticError::MissingSignature)),
+ None => return Err(Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingSignature)),
Some(signature) => signature,
};
+ let message = TaggedHash::new(SIGNATURE_TAG, &bytes);
let pubkey = contents.fields().signing_pubkey;
- merkle::verify_signature(&signature, SIGNATURE_TAG, &bytes, pubkey)?;
+ merkle::verify_signature(&signature, message, pubkey)?;
- Ok(Invoice { bytes, contents, signature })
+ Ok(Bolt12Invoice { bytes, contents, signature })
}
}
impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {
- type Error = SemanticError;
+ type Error = Bolt12SemanticError;
fn try_from(tlv_stream: PartialInvoiceTlvStream) -> Result<Self, Self::Error> {
let (
) = tlv_stream;
let payment_paths = match (blindedpay, paths) {
- (_, None) => return Err(SemanticError::MissingPaths),
- (None, _) => return Err(SemanticError::InvalidPayInfo),
- (_, Some(paths)) if paths.is_empty() => return Err(SemanticError::MissingPaths),
+ (_, None) => return Err(Bolt12SemanticError::MissingPaths),
+ (None, _) => return Err(Bolt12SemanticError::InvalidPayInfo),
+ (_, Some(paths)) if paths.is_empty() => return Err(Bolt12SemanticError::MissingPaths),
(Some(blindedpay), Some(paths)) if paths.len() != blindedpay.len() => {
- return Err(SemanticError::InvalidPayInfo);
+ return Err(Bolt12SemanticError::InvalidPayInfo);
},
(Some(blindedpay), Some(paths)) => {
blindedpay.into_iter().zip(paths.into_iter()).collect::<Vec<_>>()
};
let created_at = match created_at {
- None => return Err(SemanticError::MissingCreationTime),
+ None => return Err(Bolt12SemanticError::MissingCreationTime),
Some(timestamp) => Duration::from_secs(timestamp),
};
.map(Duration::from_secs);
let payment_hash = match payment_hash {
- None => return Err(SemanticError::MissingPaymentHash),
+ None => return Err(Bolt12SemanticError::MissingPaymentHash),
Some(payment_hash) => payment_hash,
};
let amount_msats = match amount {
- None => return Err(SemanticError::MissingAmount),
+ None => return Err(Bolt12SemanticError::MissingAmount),
Some(amount) => amount,
};
let features = features.unwrap_or_else(Bolt12InvoiceFeatures::empty);
let signing_pubkey = match node_id {
- None => return Err(SemanticError::MissingSigningPubkey),
+ None => return Err(Bolt12SemanticError::MissingSigningPubkey),
Some(node_id) => node_id,
};
match offer_tlv_stream.node_id {
Some(expected_signing_pubkey) => {
if fields.signing_pubkey != expected_signing_pubkey {
- return Err(SemanticError::InvalidSigningPubkey);
+ return Err(Bolt12SemanticError::InvalidSigningPubkey);
}
let invoice_request = InvoiceRequestContents::try_from(
#[cfg(test)]
mod tests {
- use super::{DEFAULT_RELATIVE_EXPIRY, FallbackAddress, FullInvoiceTlvStreamRef, Invoice, InvoiceTlvStreamRef, SIGNATURE_TAG};
+ use super::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, FallbackAddress, FullInvoiceTlvStreamRef, InvoiceTlvStreamRef, SIGNATURE_TAG, UnsignedBolt12Invoice};
+ use bitcoin::blockdata::constants::ChainHash;
use bitcoin::blockdata::script::Script;
use bitcoin::hashes::Hash;
use bitcoin::network::constants::Network;
use core::time::Duration;
use crate::blinded_path::{BlindedHop, BlindedPath};
use crate::sign::KeyMaterial;
- use crate::ln::features::Bolt12InvoiceFeatures;
+ use crate::ln::features::{Bolt12InvoiceFeatures, InvoiceRequestFeatures, OfferFeatures};
use crate::ln::inbound_payment::ExpandedKey;
use crate::ln::msgs::DecodeError;
use crate::offers::invoice_request::InvoiceRequestTlvStreamRef;
- use crate::offers::merkle::{SignError, SignatureTlvStreamRef, self};
- use crate::offers::offer::{OfferBuilder, OfferTlvStreamRef, Quantity};
- use crate::offers::parse::{ParseError, SemanticError};
+ use crate::offers::merkle::{SignError, SignatureTlvStreamRef, TaggedHash, self};
+ use crate::offers::offer::{Amount, OfferBuilder, OfferTlvStreamRef, Quantity};
+ use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError};
use crate::offers::payer::PayerTlvStreamRef;
use crate::offers::refund::RefundBuilder;
use crate::offers::test_utils::*;
let payment_paths = payment_paths();
let payment_hash = payment_hash();
let now = now();
- let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
+ let unsigned_invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
.amount_msats(1000)
.build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
.build().unwrap()
.sign(payer_sign).unwrap()
.respond_with_no_std(payment_paths.clone(), payment_hash, now).unwrap()
- .build().unwrap()
- .sign(recipient_sign).unwrap();
+ .build().unwrap();
+
+ let mut buffer = Vec::new();
+ unsigned_invoice.write(&mut buffer).unwrap();
+
+ assert_eq!(unsigned_invoice.bytes, buffer.as_slice());
+ assert_eq!(unsigned_invoice.payer_metadata(), &[1; 32]);
+ assert_eq!(unsigned_invoice.offer_chains(), Some(vec![ChainHash::using_genesis_block(Network::Bitcoin)]));
+ assert_eq!(unsigned_invoice.metadata(), None);
+ assert_eq!(unsigned_invoice.amount(), Some(&Amount::Bitcoin { amount_msats: 1000 }));
+ assert_eq!(unsigned_invoice.description(), PrintableString("foo"));
+ assert_eq!(unsigned_invoice.offer_features(), Some(&OfferFeatures::empty()));
+ assert_eq!(unsigned_invoice.absolute_expiry(), None);
+ assert_eq!(unsigned_invoice.message_paths(), &[]);
+ assert_eq!(unsigned_invoice.issuer(), None);
+ assert_eq!(unsigned_invoice.supported_quantity(), Some(Quantity::One));
+ assert_eq!(unsigned_invoice.signing_pubkey(), recipient_pubkey());
+ assert_eq!(unsigned_invoice.chain(), ChainHash::using_genesis_block(Network::Bitcoin));
+ assert_eq!(unsigned_invoice.amount_msats(), 1000);
+ assert_eq!(unsigned_invoice.invoice_request_features(), &InvoiceRequestFeatures::empty());
+ assert_eq!(unsigned_invoice.quantity(), None);
+ assert_eq!(unsigned_invoice.payer_id(), payer_pubkey());
+ assert_eq!(unsigned_invoice.payer_note(), None);
+ assert_eq!(unsigned_invoice.payment_paths(), payment_paths.as_slice());
+ assert_eq!(unsigned_invoice.created_at(), now);
+ assert_eq!(unsigned_invoice.relative_expiry(), DEFAULT_RELATIVE_EXPIRY);
+ #[cfg(feature = "std")]
+ assert!(!unsigned_invoice.is_expired());
+ assert_eq!(unsigned_invoice.payment_hash(), payment_hash);
+ assert_eq!(unsigned_invoice.amount_msats(), 1000);
+ assert_eq!(unsigned_invoice.fallbacks(), vec![]);
+ assert_eq!(unsigned_invoice.invoice_features(), &Bolt12InvoiceFeatures::empty());
+ assert_eq!(unsigned_invoice.signing_pubkey(), recipient_pubkey());
+
+ match UnsignedBolt12Invoice::try_from(buffer) {
+ Err(e) => panic!("error parsing unsigned invoice: {:?}", e),
+ Ok(parsed) => {
+ assert_eq!(parsed.bytes, unsigned_invoice.bytes);
+ assert_eq!(parsed.tagged_hash, unsigned_invoice.tagged_hash);
+ },
+ }
+
+ let invoice = unsigned_invoice.sign(recipient_sign).unwrap();
let mut buffer = Vec::new();
invoice.write(&mut buffer).unwrap();
assert_eq!(invoice.bytes, buffer.as_slice());
+ assert_eq!(invoice.payer_metadata(), &[1; 32]);
+ assert_eq!(invoice.offer_chains(), Some(vec![ChainHash::using_genesis_block(Network::Bitcoin)]));
+ assert_eq!(invoice.metadata(), None);
+ assert_eq!(invoice.amount(), Some(&Amount::Bitcoin { amount_msats: 1000 }));
assert_eq!(invoice.description(), PrintableString("foo"));
+ assert_eq!(invoice.offer_features(), Some(&OfferFeatures::empty()));
+ assert_eq!(invoice.absolute_expiry(), None);
+ assert_eq!(invoice.message_paths(), &[]);
+ assert_eq!(invoice.issuer(), None);
+ assert_eq!(invoice.supported_quantity(), Some(Quantity::One));
+ assert_eq!(invoice.signing_pubkey(), recipient_pubkey());
+ assert_eq!(invoice.chain(), ChainHash::using_genesis_block(Network::Bitcoin));
+ assert_eq!(invoice.amount_msats(), 1000);
+ assert_eq!(invoice.invoice_request_features(), &InvoiceRequestFeatures::empty());
+ assert_eq!(invoice.quantity(), None);
+ assert_eq!(invoice.payer_id(), payer_pubkey());
+ assert_eq!(invoice.payer_note(), None);
assert_eq!(invoice.payment_paths(), payment_paths.as_slice());
assert_eq!(invoice.created_at(), now);
assert_eq!(invoice.relative_expiry(), DEFAULT_RELATIVE_EXPIRY);
assert_eq!(invoice.payment_hash(), payment_hash);
assert_eq!(invoice.amount_msats(), 1000);
assert_eq!(invoice.fallbacks(), vec![]);
- assert_eq!(invoice.features(), &Bolt12InvoiceFeatures::empty());
+ assert_eq!(invoice.invoice_features(), &Bolt12InvoiceFeatures::empty());
assert_eq!(invoice.signing_pubkey(), recipient_pubkey());
- assert!(
- merkle::verify_signature(
- &invoice.signature, SIGNATURE_TAG, &invoice.bytes, recipient_pubkey()
- ).is_ok()
- );
+
+ let message = TaggedHash::new(SIGNATURE_TAG, &invoice.bytes);
+ assert!(merkle::verify_signature(&invoice.signature, message, recipient_pubkey()).is_ok());
let digest = Message::from_slice(&invoice.signable_hash()).unwrap();
let pubkey = recipient_pubkey().into();
),
);
- if let Err(e) = Invoice::try_from(buffer) {
+ if let Err(e) = Bolt12Invoice::try_from(buffer) {
panic!("error parsing invoice: {:?}", e);
}
}
invoice.write(&mut buffer).unwrap();
assert_eq!(invoice.bytes, buffer.as_slice());
+ assert_eq!(invoice.payer_metadata(), &[1; 32]);
+ assert_eq!(invoice.offer_chains(), None);
+ assert_eq!(invoice.metadata(), None);
+ assert_eq!(invoice.amount(), None);
assert_eq!(invoice.description(), PrintableString("foo"));
+ assert_eq!(invoice.offer_features(), None);
+ assert_eq!(invoice.absolute_expiry(), None);
+ assert_eq!(invoice.message_paths(), &[]);
+ assert_eq!(invoice.issuer(), None);
+ assert_eq!(invoice.supported_quantity(), None);
+ assert_eq!(invoice.signing_pubkey(), recipient_pubkey());
+ assert_eq!(invoice.chain(), ChainHash::using_genesis_block(Network::Bitcoin));
+ assert_eq!(invoice.amount_msats(), 1000);
+ assert_eq!(invoice.invoice_request_features(), &InvoiceRequestFeatures::empty());
+ assert_eq!(invoice.quantity(), None);
+ assert_eq!(invoice.payer_id(), payer_pubkey());
+ assert_eq!(invoice.payer_note(), None);
assert_eq!(invoice.payment_paths(), payment_paths.as_slice());
assert_eq!(invoice.created_at(), now);
assert_eq!(invoice.relative_expiry(), DEFAULT_RELATIVE_EXPIRY);
assert_eq!(invoice.payment_hash(), payment_hash);
assert_eq!(invoice.amount_msats(), 1000);
assert_eq!(invoice.fallbacks(), vec![]);
- assert_eq!(invoice.features(), &Bolt12InvoiceFeatures::empty());
+ assert_eq!(invoice.invoice_features(), &Bolt12InvoiceFeatures::empty());
assert_eq!(invoice.signing_pubkey(), recipient_pubkey());
- assert!(
- merkle::verify_signature(
- &invoice.signature, SIGNATURE_TAG, &invoice.bytes, recipient_pubkey()
- ).is_ok()
- );
+
+ let message = TaggedHash::new(SIGNATURE_TAG, &invoice.bytes);
+ assert!(merkle::verify_signature(&invoice.signature, message, recipient_pubkey()).is_ok());
assert_eq!(
invoice.as_tlv_stream(),
),
);
- if let Err(e) = Invoice::try_from(buffer) {
+ if let Err(e) = Bolt12Invoice::try_from(buffer) {
panic!("error parsing invoice: {:?}", e);
}
}
.build()
{
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, SemanticError::AlreadyExpired),
+ Err(e) => assert_eq!(e, Bolt12SemanticError::AlreadyExpired),
}
}
.build()
{
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, SemanticError::AlreadyExpired),
+ Err(e) => assert_eq!(e, Bolt12SemanticError::AlreadyExpired),
}
}
.build().unwrap()
.sign(payer_sign).unwrap();
- if let Err(e) = invoice_request
- .verify_and_respond_using_derived_keys_no_std(
- payment_paths(), payment_hash(), now(), &expanded_key, &secp_ctx
- )
- .unwrap()
+ if let Err(e) = invoice_request.clone()
+ .verify(&expanded_key, &secp_ctx).unwrap()
+ .respond_using_derived_keys_no_std(payment_paths(), payment_hash(), now()).unwrap()
.build_and_sign(&secp_ctx)
{
panic!("error building invoice: {:?}", e);
}
let expanded_key = ExpandedKey::new(&KeyMaterial([41; 32]));
- match invoice_request.verify_and_respond_using_derived_keys_no_std(
- payment_paths(), payment_hash(), now(), &expanded_key, &secp_ctx
- ) {
- Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, SemanticError::InvalidMetadata),
- }
+ assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_err());
let desc = "foo".to_string();
let offer = OfferBuilder
::deriving_signing_pubkey(desc, node_id, &expanded_key, &entropy, &secp_ctx)
.amount_msats(1000)
+ // Omit the path so that node_id is used for the signing pubkey instead of deriving
.build().unwrap();
let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
.build().unwrap()
.sign(payer_sign).unwrap();
- match invoice_request.verify_and_respond_using_derived_keys_no_std(
- payment_paths(), payment_hash(), now(), &expanded_key, &secp_ctx
- ) {
+ match invoice_request
+ .verify(&expanded_key, &secp_ctx).unwrap()
+ .respond_using_derived_keys_no_std(payment_paths(), payment_hash(), now())
+ {
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, SemanticError::InvalidMetadata),
+ Err(e) => assert_eq!(e, Bolt12SemanticError::InvalidMetadata),
}
}
.respond_with_no_std(payment_paths(), payment_hash(), now())
{
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, SemanticError::InvalidAmount),
+ Err(e) => assert_eq!(e, Bolt12SemanticError::InvalidAmount),
}
}
.build().unwrap()
.sign(recipient_sign).unwrap();
let (_, _, _, tlv_stream, _) = invoice.as_tlv_stream();
- assert_eq!(invoice.features(), &features);
+ assert_eq!(invoice.invoice_features(), &features);
assert_eq!(tlv_stream.features, Some(&features));
}
let mut buffer = Vec::new();
invoice.write(&mut buffer).unwrap();
- if let Err(e) = Invoice::try_from(buffer) {
+ if let Err(e) = Bolt12Invoice::try_from(buffer) {
panic!("error parsing invoice: {:?}", e);
}
let mut tlv_stream = invoice.as_tlv_stream();
tlv_stream.3.paths = None;
- match Invoice::try_from(tlv_stream.to_bytes()) {
+ match Bolt12Invoice::try_from(tlv_stream.to_bytes()) {
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingPaths)),
+ Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingPaths)),
}
let mut tlv_stream = invoice.as_tlv_stream();
tlv_stream.3.blindedpay = None;
- match Invoice::try_from(tlv_stream.to_bytes()) {
+ match Bolt12Invoice::try_from(tlv_stream.to_bytes()) {
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::InvalidPayInfo)),
+ Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::InvalidPayInfo)),
}
let empty_payment_paths = vec![];
let mut tlv_stream = invoice.as_tlv_stream();
tlv_stream.3.paths = Some(Iterable(empty_payment_paths.iter().map(|(_, path)| path)));
- match Invoice::try_from(tlv_stream.to_bytes()) {
+ match Bolt12Invoice::try_from(tlv_stream.to_bytes()) {
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingPaths)),
+ Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingPaths)),
}
let mut payment_paths = payment_paths();
let mut tlv_stream = invoice.as_tlv_stream();
tlv_stream.3.blindedpay = Some(Iterable(payment_paths.iter().map(|(payinfo, _)| payinfo)));
- match Invoice::try_from(tlv_stream.to_bytes()) {
+ match Bolt12Invoice::try_from(tlv_stream.to_bytes()) {
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::InvalidPayInfo)),
+ Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::InvalidPayInfo)),
}
}
let mut buffer = Vec::new();
invoice.write(&mut buffer).unwrap();
- if let Err(e) = Invoice::try_from(buffer) {
+ if let Err(e) = Bolt12Invoice::try_from(buffer) {
panic!("error parsing invoice: {:?}", e);
}
let mut tlv_stream = invoice.as_tlv_stream();
tlv_stream.3.created_at = None;
- match Invoice::try_from(tlv_stream.to_bytes()) {
+ match Bolt12Invoice::try_from(tlv_stream.to_bytes()) {
Ok(_) => panic!("expected error"),
Err(e) => {
- assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingCreationTime));
+ assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingCreationTime));
},
}
}
let mut buffer = Vec::new();
invoice.write(&mut buffer).unwrap();
- match Invoice::try_from(buffer) {
+ match Bolt12Invoice::try_from(buffer) {
Ok(invoice) => assert_eq!(invoice.relative_expiry(), Duration::from_secs(3600)),
Err(e) => panic!("error parsing invoice: {:?}", e),
}
let mut buffer = Vec::new();
invoice.write(&mut buffer).unwrap();
- if let Err(e) = Invoice::try_from(buffer) {
+ if let Err(e) = Bolt12Invoice::try_from(buffer) {
panic!("error parsing invoice: {:?}", e);
}
let mut tlv_stream = invoice.as_tlv_stream();
tlv_stream.3.payment_hash = None;
- match Invoice::try_from(tlv_stream.to_bytes()) {
+ match Bolt12Invoice::try_from(tlv_stream.to_bytes()) {
Ok(_) => panic!("expected error"),
Err(e) => {
- assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingPaymentHash));
+ assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingPaymentHash));
},
}
}
let mut buffer = Vec::new();
invoice.write(&mut buffer).unwrap();
- if let Err(e) = Invoice::try_from(buffer) {
+ if let Err(e) = Bolt12Invoice::try_from(buffer) {
panic!("error parsing invoice: {:?}", e);
}
let mut tlv_stream = invoice.as_tlv_stream();
tlv_stream.3.amount = None;
- match Invoice::try_from(tlv_stream.to_bytes()) {
+ match Bolt12Invoice::try_from(tlv_stream.to_bytes()) {
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingAmount)),
+ Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingAmount)),
}
}
let mut buffer = Vec::new();
invoice.write(&mut buffer).unwrap();
- match Invoice::try_from(buffer) {
+ match Bolt12Invoice::try_from(buffer) {
Ok(invoice) => {
let mut features = Bolt12InvoiceFeatures::empty();
features.set_basic_mpp_optional();
- assert_eq!(invoice.features(), &features);
+ assert_eq!(invoice.invoice_features(), &features);
},
Err(e) => panic!("error parsing invoice: {:?}", e),
}
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
.build().unwrap()
.sign(payer_sign).unwrap();
- let mut unsigned_invoice = invoice_request
+ let mut invoice_builder = invoice_request
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
.fallback_v0_p2wsh(&script.wscript_hash())
.fallback_v0_p2wpkh(&pubkey.wpubkey_hash().unwrap())
- .fallback_v1_p2tr_tweaked(&tweaked_pubkey)
- .build().unwrap();
+ .fallback_v1_p2tr_tweaked(&tweaked_pubkey);
// Only standard addresses will be included.
- let fallbacks = unsigned_invoice.invoice.fields_mut().fallbacks.as_mut().unwrap();
+ let fallbacks = invoice_builder.invoice.fields_mut().fallbacks.as_mut().unwrap();
// Non-standard addresses
fallbacks.push(FallbackAddress { version: 1, program: vec![0u8; 41] });
fallbacks.push(FallbackAddress { version: 2, program: vec![0u8; 1] });
fallbacks.push(FallbackAddress { version: 1, program: vec![0u8; 33] });
fallbacks.push(FallbackAddress { version: 2, program: vec![0u8; 40] });
- let invoice = unsigned_invoice.sign(recipient_sign).unwrap();
+ let invoice = invoice_builder.build().unwrap().sign(recipient_sign).unwrap();
let mut buffer = Vec::new();
invoice.write(&mut buffer).unwrap();
- match Invoice::try_from(buffer) {
+ match Bolt12Invoice::try_from(buffer) {
Ok(invoice) => {
assert_eq!(
invoice.fallbacks(),
let mut buffer = Vec::new();
invoice.write(&mut buffer).unwrap();
- if let Err(e) = Invoice::try_from(buffer) {
+ if let Err(e) = Bolt12Invoice::try_from(buffer) {
panic!("error parsing invoice: {:?}", e);
}
let mut tlv_stream = invoice.as_tlv_stream();
tlv_stream.3.node_id = None;
- match Invoice::try_from(tlv_stream.to_bytes()) {
+ match Bolt12Invoice::try_from(tlv_stream.to_bytes()) {
Ok(_) => panic!("expected error"),
Err(e) => {
- assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingSigningPubkey));
+ assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingSigningPubkey));
},
}
let mut tlv_stream = invoice.as_tlv_stream();
tlv_stream.3.node_id = Some(&invalid_pubkey);
- match Invoice::try_from(tlv_stream.to_bytes()) {
+ match Bolt12Invoice::try_from(tlv_stream.to_bytes()) {
Ok(_) => panic!("expected error"),
Err(e) => {
- assert_eq!(e, ParseError::InvalidSemantics(SemanticError::InvalidSigningPubkey));
+ assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::InvalidSigningPubkey));
},
}
}
.sign(payer_sign).unwrap()
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
.build().unwrap()
- .invoice
+ .contents
.write(&mut buffer).unwrap();
- match Invoice::try_from(buffer) {
+ match Bolt12Invoice::try_from(buffer) {
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingSignature)),
+ Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingSignature)),
}
}
let mut buffer = Vec::new();
invoice.write(&mut buffer).unwrap();
- match Invoice::try_from(buffer) {
+ match Bolt12Invoice::try_from(buffer) {
Ok(_) => panic!("expected error"),
Err(e) => {
- assert_eq!(e, ParseError::InvalidSignature(secp256k1::Error::InvalidSignature));
+ assert_eq!(e, Bolt12ParseError::InvalidSignature(secp256k1::Error::InvalidSignature));
},
}
}
BigSize(32).write(&mut encoded_invoice).unwrap();
[42u8; 32].write(&mut encoded_invoice).unwrap();
- match Invoice::try_from(encoded_invoice) {
+ match Bolt12Invoice::try_from(encoded_invoice) {
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, ParseError::Decode(DecodeError::InvalidValue)),
+ Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::InvalidValue)),
}
}
}
use crate::io;
use crate::ln::msgs::DecodeError;
-use crate::offers::parse::SemanticError;
+use crate::offers::parse::Bolt12SemanticError;
use crate::util::ser::{HighZeroBytesDroppedBigSize, Readable, WithoutLength, Writeable, Writer};
use crate::util::string::UntrustedString;
use crate::prelude::*;
-/// An error in response to an [`InvoiceRequest`] or an [`Invoice`].
+/// An error in response to an [`InvoiceRequest`] or an [`Bolt12Invoice`].
///
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
-/// [`Invoice`]: crate::offers::invoice::Invoice
+/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
#[derive(Clone, Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub struct InvoiceError {
- /// The field in the [`InvoiceRequest`] or the [`Invoice`] that contained an error.
+ /// The field in the [`InvoiceRequest`] or the [`Bolt12Invoice`] that contained an error.
///
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
- /// [`Invoice`]: crate::offers::invoice::Invoice
+ /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
pub erroneous_field: Option<ErroneousField>,
/// An explanation of the error.
pub message: UntrustedString,
}
-/// The field in the [`InvoiceRequest`] or the [`Invoice`] that contained an error.
+/// The field in the [`InvoiceRequest`] or the [`Bolt12Invoice`] that contained an error.
///
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
-/// [`Invoice`]: crate::offers::invoice::Invoice
+/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
#[derive(Clone, Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub struct ErroneousField {
impl Readable for InvoiceError {
fn read<R: io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
- _init_and_read_tlv_fields!(reader, {
+ _init_and_read_len_prefixed_tlv_fields!(reader, {
(1, erroneous_field, (option, encoding: (u64, HighZeroBytesDroppedBigSize))),
(3, suggested_value, (option, encoding: (Vec<u8>, WithoutLength))),
(5, error, (option, encoding: (UntrustedString, WithoutLength))),
}
}
-impl From<SemanticError> for InvoiceError {
- fn from(error: SemanticError) -> Self {
+impl From<Bolt12SemanticError> for InvoiceError {
+ fn from(error: Bolt12SemanticError) -> Self {
InvoiceError {
erroneous_field: None,
message: UntrustedString(format!("{:?}", error)),
//!
//! An [`InvoiceRequest`] can be built from a parsed [`Offer`] as an "offer to be paid". It is
//! typically constructed by a customer and sent to the merchant who had published the corresponding
-//! offer. The recipient of the request responds with an [`Invoice`].
+//! offer. The recipient of the request responds with a [`Bolt12Invoice`].
//!
//! For an "offer for money" (e.g., refund, ATM withdrawal), where an offer doesn't exist as a
//! precursor, see [`Refund`].
//!
-//! [`Invoice`]: crate::offers::invoice::Invoice
+//! [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
//! [`Refund`]: crate::offers::refund::Refund
//!
//! ```
//! use lightning::offers::offer::Offer;
//! use lightning::util::ser::Writeable;
//!
-//! # fn parse() -> Result<(), lightning::offers::parse::ParseError> {
+//! # fn parse() -> Result<(), lightning::offers::parse::Bolt12ParseError> {
//! let secp_ctx = Secp256k1::new();
//! let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32])?);
//! let pubkey = PublicKey::from(keys);
//! .quantity(5)?
//! .payer_note("foo".to_string())
//! .build()?
-//! .sign::<_, Infallible>(|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys)))
+//! .sign::<_, Infallible>(
+//! |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
+//! )
//! .expect("failed verifying signature")
//! .write(&mut buffer)
//! .unwrap();
use bitcoin::blockdata::constants::ChainHash;
use bitcoin::network::constants::Network;
-use bitcoin::secp256k1::{KeyPair, Message, PublicKey, Secp256k1, self};
+use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, self};
use bitcoin::secp256k1::schnorr::Signature;
-use core::convert::{Infallible, TryFrom};
+use core::convert::{AsRef, Infallible, TryFrom};
use core::ops::Deref;
use crate::sign::EntropySource;
use crate::io;
use crate::blinded_path::BlindedPath;
use crate::ln::PaymentHash;
+use crate::ln::channelmanager::PaymentId;
use crate::ln::features::InvoiceRequestFeatures;
use crate::ln::inbound_payment::{ExpandedKey, IV_LEN, Nonce};
use crate::ln::msgs::DecodeError;
use crate::offers::invoice::{BlindedPayInfo, DerivedSigningPubkey, ExplicitSigningPubkey, InvoiceBuilder};
-use crate::offers::merkle::{SignError, SignatureTlvStream, SignatureTlvStreamRef, self};
+use crate::offers::merkle::{SignError, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, self};
use crate::offers::offer::{Offer, OfferContents, OfferTlvStream, OfferTlvStreamRef};
-use crate::offers::parse::{ParseError, ParsedMessage, SemanticError};
+use crate::offers::parse::{Bolt12ParseError, ParsedMessage, Bolt12SemanticError};
use crate::offers::payer::{PayerContents, PayerTlvStream, PayerTlvStreamRef};
use crate::offers::signer::{Metadata, MetadataMaterial};
use crate::util::ser::{HighZeroBytesDroppedBigSize, SeekReadable, WithoutLength, Writeable, Writer};
use crate::prelude::*;
-const SIGNATURE_TAG: &'static str = concat!("lightning", "invoice_request", "signature");
+/// Tag for the hash function used when signing an [`InvoiceRequest`]'s merkle root.
+pub const SIGNATURE_TAG: &'static str = concat!("lightning", "invoice_request", "signature");
pub(super) const IV_BYTES: &[u8; IV_LEN] = b"LDK Invreq ~~~~~";
}
pub(super) fn deriving_metadata<ES: Deref>(
- offer: &'a Offer, payer_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES
+ offer: &'a Offer, payer_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES,
+ payment_id: PaymentId,
) -> Self where ES::Target: EntropySource {
let nonce = Nonce::from_entropy_source(entropy_source);
- let derivation_material = MetadataMaterial::new(nonce, expanded_key, IV_BYTES);
+ let payment_id = Some(payment_id);
+ let derivation_material = MetadataMaterial::new(nonce, expanded_key, IV_BYTES, payment_id);
let metadata = Metadata::Derived(derivation_material);
Self {
offer,
impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, DerivedPayerId, T> {
pub(super) fn deriving_payer_id<ES: Deref>(
- offer: &'a Offer, expanded_key: &ExpandedKey, entropy_source: ES, secp_ctx: &'b Secp256k1<T>
+ offer: &'a Offer, expanded_key: &ExpandedKey, entropy_source: ES,
+ secp_ctx: &'b Secp256k1<T>, payment_id: PaymentId
) -> Self where ES::Target: EntropySource {
let nonce = Nonce::from_entropy_source(entropy_source);
- let derivation_material = MetadataMaterial::new(nonce, expanded_key, IV_BYTES);
+ let payment_id = Some(payment_id);
+ let derivation_material = MetadataMaterial::new(nonce, expanded_key, IV_BYTES, payment_id);
let metadata = Metadata::DerivedSigningPubkey(derivation_material);
Self {
offer,
/// by the offer.
///
/// Successive calls to this method will override the previous setting.
- pub fn chain(mut self, network: Network) -> Result<Self, SemanticError> {
+ pub fn chain(mut self, network: Network) -> Result<Self, Bolt12SemanticError> {
let chain = ChainHash::using_genesis_block(network);
if !self.offer.supports_chain(chain) {
- return Err(SemanticError::UnsupportedChain);
+ return Err(Bolt12SemanticError::UnsupportedChain);
}
self.invoice_request.chain = Some(chain);
/// Successive calls to this method will override the previous setting.
///
/// [`quantity`]: Self::quantity
- pub fn amount_msats(mut self, amount_msats: u64) -> Result<Self, SemanticError> {
+ pub fn amount_msats(mut self, amount_msats: u64) -> Result<Self, Bolt12SemanticError> {
self.invoice_request.offer.check_amount_msats_for_quantity(
Some(amount_msats), self.invoice_request.quantity
)?;
/// does not conform to [`Offer::is_valid_quantity`].
///
/// Successive calls to this method will override the previous setting.
- pub fn quantity(mut self, quantity: u64) -> Result<Self, SemanticError> {
+ pub fn quantity(mut self, quantity: u64) -> Result<Self, Bolt12SemanticError> {
self.invoice_request.offer.check_quantity(Some(quantity))?;
self.invoice_request.quantity = Some(quantity);
Ok(self)
}
fn build_with_checks(mut self) -> Result<
- (UnsignedInvoiceRequest<'a>, Option<KeyPair>, Option<&'b Secp256k1<T>>),
- SemanticError
+ (UnsignedInvoiceRequest, Option<KeyPair>, Option<&'b Secp256k1<T>>),
+ Bolt12SemanticError
> {
#[cfg(feature = "std")] {
if self.offer.is_expired() {
- return Err(SemanticError::AlreadyExpired);
+ return Err(Bolt12SemanticError::AlreadyExpired);
}
}
let chain = self.invoice_request.chain();
if !self.offer.supports_chain(chain) {
- return Err(SemanticError::UnsupportedChain);
+ return Err(Bolt12SemanticError::UnsupportedChain);
}
if chain == self.offer.implied_chain() {
}
if self.offer.amount().is_none() && self.invoice_request.amount_msats.is_none() {
- return Err(SemanticError::MissingAmount);
+ return Err(Bolt12SemanticError::MissingAmount);
}
self.invoice_request.offer.check_quantity(self.invoice_request.quantity)?;
}
fn build_without_checks(mut self) ->
- (UnsignedInvoiceRequest<'a>, Option<KeyPair>, Option<&'b Secp256k1<T>>)
+ (UnsignedInvoiceRequest, Option<KeyPair>, Option<&'b Secp256k1<T>>)
{
- // Create the metadata for stateless verification of an Invoice.
+ // Create the metadata for stateless verification of a Bolt12Invoice.
let mut keys = None;
let secp_ctx = self.secp_ctx.clone();
if self.invoice_request.payer.0.has_derivation_material() {
let mut tlv_stream = self.invoice_request.as_tlv_stream();
debug_assert!(tlv_stream.2.payer_id.is_none());
tlv_stream.0.metadata = None;
- if !metadata.derives_keys() {
+ if !metadata.derives_payer_keys() {
tlv_stream.2.payer_id = self.payer_id.as_ref();
}
debug_assert!(self.payer_id.is_some());
let payer_id = self.payer_id.unwrap();
- let unsigned_invoice = UnsignedInvoiceRequest {
- offer: self.offer,
- invoice_request: InvoiceRequestContents {
- inner: self.invoice_request,
- payer_id,
- },
+ let invoice_request = InvoiceRequestContents {
+ inner: self.invoice_request,
+ payer_id,
};
+ let unsigned_invoice_request = UnsignedInvoiceRequest::new(self.offer, invoice_request);
- (unsigned_invoice, keys, secp_ctx)
+ (unsigned_invoice_request, keys, secp_ctx)
}
}
impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, ExplicitPayerId, T> {
/// Builds an unsigned [`InvoiceRequest`] after checking for valid semantics. It can be signed
/// by [`UnsignedInvoiceRequest::sign`].
- pub fn build(self) -> Result<UnsignedInvoiceRequest<'a>, SemanticError> {
+ pub fn build(self) -> Result<UnsignedInvoiceRequest, Bolt12SemanticError> {
let (unsigned_invoice_request, keys, _) = self.build_with_checks()?;
debug_assert!(keys.is_none());
Ok(unsigned_invoice_request)
impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, DerivedPayerId, T> {
/// Builds a signed [`InvoiceRequest`] after checking for valid semantics.
- pub fn build_and_sign(self) -> Result<InvoiceRequest, SemanticError> {
+ pub fn build_and_sign(self) -> Result<InvoiceRequest, Bolt12SemanticError> {
let (unsigned_invoice_request, keys, secp_ctx) = self.build_with_checks()?;
debug_assert!(keys.is_some());
let secp_ctx = secp_ctx.unwrap();
let keys = keys.unwrap();
let invoice_request = unsigned_invoice_request
- .sign::<_, Infallible>(|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys)))
+ .sign::<_, Infallible>(
+ |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
+ )
.unwrap();
Ok(invoice_request)
}
self
}
- pub(super) fn build_unchecked(self) -> UnsignedInvoiceRequest<'a> {
+ pub(super) fn build_unchecked(self) -> UnsignedInvoiceRequest {
self.build_without_checks().0
}
}
/// A semantically valid [`InvoiceRequest`] that hasn't been signed.
-pub struct UnsignedInvoiceRequest<'a> {
- offer: &'a Offer,
- invoice_request: InvoiceRequestContents,
+///
+/// # Serialization
+///
+/// This is serialized as a TLV stream, which includes TLV records from the originating message. As
+/// such, it may include unknown, odd TLV records.
+pub struct UnsignedInvoiceRequest {
+ bytes: Vec<u8>,
+ contents: InvoiceRequestContents,
+ tagged_hash: TaggedHash,
}
-impl<'a> UnsignedInvoiceRequest<'a> {
- /// Signs the invoice request using the given function.
- ///
- /// This is not exported to bindings users as functions are not yet mapped.
- pub fn sign<F, E>(self, sign: F) -> Result<InvoiceRequest, SignError<E>>
- where
- F: FnOnce(&Message) -> Result<Signature, E>
- {
+impl UnsignedInvoiceRequest {
+ fn new(offer: &Offer, contents: InvoiceRequestContents) -> Self {
// Use the offer bytes instead of the offer TLV stream as the offer may have contained
// unknown TLV records, which are not stored in `OfferContents`.
let (payer_tlv_stream, _offer_tlv_stream, invoice_request_tlv_stream) =
- self.invoice_request.as_tlv_stream();
- let offer_bytes = WithoutLength(&self.offer.bytes);
+ contents.as_tlv_stream();
+ let offer_bytes = WithoutLength(&offer.bytes);
let unsigned_tlv_stream = (payer_tlv_stream, offer_bytes, invoice_request_tlv_stream);
let mut bytes = Vec::new();
unsigned_tlv_stream.write(&mut bytes).unwrap();
- let pubkey = self.invoice_request.payer_id;
- let signature = merkle::sign_message(sign, SIGNATURE_TAG, &bytes, pubkey)?;
+ let tagged_hash = TaggedHash::new(SIGNATURE_TAG, &bytes);
+
+ Self { bytes, contents, tagged_hash }
+ }
+
+ /// Returns the [`TaggedHash`] of the invoice to sign.
+ pub fn tagged_hash(&self) -> &TaggedHash {
+ &self.tagged_hash
+ }
+
+ /// Signs the [`TaggedHash`] of the invoice request using the given function.
+ ///
+ /// Note: The hash computation may have included unknown, odd TLV records.
+ ///
+ /// This is not exported to bindings users as functions are not yet mapped.
+ pub fn sign<F, E>(mut self, sign: F) -> Result<InvoiceRequest, SignError<E>>
+ where
+ F: FnOnce(&Self) -> Result<Signature, E>
+ {
+ let pubkey = self.contents.payer_id;
+ let signature = merkle::sign_message(sign, &self, pubkey)?;
// Append the signature TLV record to the bytes.
let signature_tlv_stream = SignatureTlvStreamRef {
signature: Some(&signature),
};
- signature_tlv_stream.write(&mut bytes).unwrap();
+ signature_tlv_stream.write(&mut self.bytes).unwrap();
Ok(InvoiceRequest {
- bytes,
- contents: self.invoice_request,
+ bytes: self.bytes,
+ contents: self.contents,
signature,
})
}
}
-/// An `InvoiceRequest` is a request for an [`Invoice`] formulated from an [`Offer`].
+impl AsRef<TaggedHash> for UnsignedInvoiceRequest {
+ fn as_ref(&self) -> &TaggedHash {
+ &self.tagged_hash
+ }
+}
+
+/// An `InvoiceRequest` is a request for a [`Bolt12Invoice`] formulated from an [`Offer`].
///
/// An offer may provide choices such as quantity, amount, chain, features, etc. An invoice request
/// specifies these such that its recipient can send an invoice for payment.
///
-/// [`Invoice`]: crate::offers::invoice::Invoice
+/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
/// [`Offer`]: crate::offers::offer::Offer
#[derive(Clone, Debug)]
#[cfg_attr(test, derive(PartialEq))]
signature: Signature,
}
-/// The contents of an [`InvoiceRequest`], which may be shared with an [`Invoice`].
+/// An [`InvoiceRequest`] that has been verified by [`InvoiceRequest::verify`] and exposes different
+/// ways to respond depending on whether the signing keys were derived.
+#[derive(Clone, Debug)]
+pub struct VerifiedInvoiceRequest {
+ /// The verified request.
+ inner: InvoiceRequest,
+
+ /// Keys used for signing a [`Bolt12Invoice`] if they can be derived.
+ ///
+ /// If `Some`, must call [`respond_using_derived_keys`] when responding. Otherwise, call
+ /// [`respond_with`].
+ ///
+ /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
+ /// [`respond_using_derived_keys`]: Self::respond_using_derived_keys
+ /// [`respond_with`]: Self::respond_with
+ pub keys: Option<KeyPair>,
+}
+
+/// The contents of an [`InvoiceRequest`], which may be shared with an [`Bolt12Invoice`].
///
-/// [`Invoice`]: crate::offers::invoice::Invoice
+/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
#[derive(Clone, Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub(super) struct InvoiceRequestContents {
payer_note: Option<String>,
}
-impl InvoiceRequest {
+macro_rules! invoice_request_accessors { ($self: ident, $contents: expr) => {
/// An unpredictable series of bytes, typically containing information about the derivation of
/// [`payer_id`].
///
/// [`payer_id`]: Self::payer_id
- pub fn metadata(&self) -> &[u8] {
- self.contents.metadata()
+ pub fn payer_metadata(&$self) -> &[u8] {
+ $contents.metadata()
}
/// A chain from [`Offer::chains`] that the offer is valid for.
- pub fn chain(&self) -> ChainHash {
- self.contents.chain()
+ pub fn chain(&$self) -> ChainHash {
+ $contents.chain()
}
/// The amount to pay in msats (i.e., the minimum lightning-payable unit for [`chain`]), which
/// must be greater than or equal to [`Offer::amount`], converted if necessary.
///
/// [`chain`]: Self::chain
- pub fn amount_msats(&self) -> Option<u64> {
- self.contents.inner.amount_msats
+ pub fn amount_msats(&$self) -> Option<u64> {
+ $contents.amount_msats()
}
/// Features pertaining to requesting an invoice.
- pub fn features(&self) -> &InvoiceRequestFeatures {
- &self.contents.inner.features
+ pub fn invoice_request_features(&$self) -> &InvoiceRequestFeatures {
+ &$contents.features()
}
/// The quantity of the offer's item conforming to [`Offer::is_valid_quantity`].
- pub fn quantity(&self) -> Option<u64> {
- self.contents.inner.quantity
+ pub fn quantity(&$self) -> Option<u64> {
+ $contents.quantity()
}
/// A possibly transient pubkey used to sign the invoice request.
- pub fn payer_id(&self) -> PublicKey {
- self.contents.payer_id
+ pub fn payer_id(&$self) -> PublicKey {
+ $contents.payer_id()
}
/// A payer-provided note which will be seen by the recipient and reflected back in the invoice
/// response.
- pub fn payer_note(&self) -> Option<PrintableString> {
- self.contents.inner.payer_note.as_ref()
- .map(|payer_note| PrintableString(payer_note.as_str()))
+ pub fn payer_note(&$self) -> Option<PrintableString> {
+ $contents.payer_note()
}
+} }
+
+impl UnsignedInvoiceRequest {
+ offer_accessors!(self, self.contents.inner.offer);
+ invoice_request_accessors!(self, self.contents);
+}
+
+impl InvoiceRequest {
+ offer_accessors!(self, self.contents.inner.offer);
+ invoice_request_accessors!(self, self.contents);
/// Signature of the invoice request using [`payer_id`].
///
#[cfg(feature = "std")]
pub fn respond_with(
&self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash
- ) -> Result<InvoiceBuilder<ExplicitSigningPubkey>, SemanticError> {
+ ) -> Result<InvoiceBuilder<ExplicitSigningPubkey>, Bolt12SemanticError> {
let created_at = std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH");
/// Creates an [`InvoiceBuilder`] for the request with the given required fields.
///
/// Unless [`InvoiceBuilder::relative_expiry`] is set, the invoice will expire two hours after
- /// `created_at`, which is used to set [`Invoice::created_at`]. Useful for `no-std` builds where
- /// [`std::time::SystemTime`] is not available.
+ /// `created_at`, which is used to set [`Bolt12Invoice::created_at`]. Useful for `no-std` builds
+ /// where [`std::time::SystemTime`] is not available.
///
/// The caller is expected to remember the preimage of `payment_hash` in order to claim a payment
/// for the invoice.
///
/// Errors if the request contains unknown required features.
///
+ /// # Note
+ ///
+ /// If the originating [`Offer`] was created using [`OfferBuilder::deriving_signing_pubkey`],
+ /// then use [`InvoiceRequest::verify`] and [`VerifiedInvoiceRequest`] methods instead.
+ ///
/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
///
- /// [`Invoice::created_at`]: crate::offers::invoice::Invoice::created_at
+ /// [`Bolt12Invoice::created_at`]: crate::offers::invoice::Bolt12Invoice::created_at
+ /// [`OfferBuilder::deriving_signing_pubkey`]: crate::offers::offer::OfferBuilder::deriving_signing_pubkey
pub fn respond_with_no_std(
&self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
created_at: core::time::Duration
- ) -> Result<InvoiceBuilder<ExplicitSigningPubkey>, SemanticError> {
- if self.features().requires_unknown_bits() {
- return Err(SemanticError::UnknownRequiredFeatures);
+ ) -> Result<InvoiceBuilder<ExplicitSigningPubkey>, Bolt12SemanticError> {
+ if self.invoice_request_features().requires_unknown_bits() {
+ return Err(Bolt12SemanticError::UnknownRequiredFeatures);
}
InvoiceBuilder::for_offer(self, payment_paths, created_at, payment_hash)
}
+ /// Verifies that the request was for an offer created using the given key. Returns the verified
+ /// request which contains the derived keys needed to sign a [`Bolt12Invoice`] for the request
+ /// if they could be extracted from the metadata.
+ ///
+ /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
+ pub fn verify<T: secp256k1::Signing>(
+ self, key: &ExpandedKey, secp_ctx: &Secp256k1<T>
+ ) -> Result<VerifiedInvoiceRequest, ()> {
+ let keys = self.contents.inner.offer.verify(&self.bytes, key, secp_ctx)?;
+ Ok(VerifiedInvoiceRequest {
+ inner: self,
+ keys,
+ })
+ }
+
+ #[cfg(test)]
+ fn as_tlv_stream(&self) -> FullInvoiceRequestTlvStreamRef {
+ let (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream) =
+ self.contents.as_tlv_stream();
+ let signature_tlv_stream = SignatureTlvStreamRef {
+ signature: Some(&self.signature),
+ };
+ (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, signature_tlv_stream)
+ }
+}
+
+impl VerifiedInvoiceRequest {
+ offer_accessors!(self, self.inner.contents.inner.offer);
+ invoice_request_accessors!(self, self.inner.contents);
+
+ /// Creates an [`InvoiceBuilder`] for the request with the given required fields and using the
+ /// [`Duration`] since [`std::time::SystemTime::UNIX_EPOCH`] as the creation time.
+ ///
+ /// See [`InvoiceRequest::respond_with_no_std`] for further details.
+ ///
+ /// This is not exported to bindings users as builder patterns don't map outside of move semantics.
+ ///
+ /// [`Duration`]: core::time::Duration
+ #[cfg(feature = "std")]
+ pub fn respond_with(
+ &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash
+ ) -> Result<InvoiceBuilder<ExplicitSigningPubkey>, Bolt12SemanticError> {
+ self.inner.respond_with(payment_paths, payment_hash)
+ }
+
+ /// Creates an [`InvoiceBuilder`] for the request with the given required fields.
+ ///
+ /// See [`InvoiceRequest::respond_with_no_std`] for further details.
+ ///
+ /// This is not exported to bindings users as builder patterns don't map outside of move semantics.
+ pub fn respond_with_no_std(
+ &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
+ created_at: core::time::Duration
+ ) -> Result<InvoiceBuilder<ExplicitSigningPubkey>, Bolt12SemanticError> {
+ self.inner.respond_with_no_std(payment_paths, payment_hash, created_at)
+ }
+
/// Creates an [`InvoiceBuilder`] for the request using the given required fields and that uses
- /// derived signing keys from the originating [`Offer`] to sign the [`Invoice`]. Must use the
- /// same [`ExpandedKey`] as the one used to create the offer.
+ /// derived signing keys from the originating [`Offer`] to sign the [`Bolt12Invoice`]. Must use
+ /// the same [`ExpandedKey`] as the one used to create the offer.
///
/// See [`InvoiceRequest::respond_with`] for further details.
///
/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
///
- /// [`Invoice`]: crate::offers::invoice::Invoice
+ /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
#[cfg(feature = "std")]
- pub fn verify_and_respond_using_derived_keys<T: secp256k1::Signing>(
- &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
- expanded_key: &ExpandedKey, secp_ctx: &Secp256k1<T>
- ) -> Result<InvoiceBuilder<DerivedSigningPubkey>, SemanticError> {
+ pub fn respond_using_derived_keys(
+ &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash
+ ) -> Result<InvoiceBuilder<DerivedSigningPubkey>, Bolt12SemanticError> {
let created_at = std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH");
- self.verify_and_respond_using_derived_keys_no_std(
- payment_paths, payment_hash, created_at, expanded_key, secp_ctx
- )
+ self.respond_using_derived_keys_no_std(payment_paths, payment_hash, created_at)
}
/// Creates an [`InvoiceBuilder`] for the request using the given required fields and that uses
- /// derived signing keys from the originating [`Offer`] to sign the [`Invoice`]. Must use the
- /// same [`ExpandedKey`] as the one used to create the offer.
+ /// derived signing keys from the originating [`Offer`] to sign the [`Bolt12Invoice`]. Must use
+ /// the same [`ExpandedKey`] as the one used to create the offer.
///
/// See [`InvoiceRequest::respond_with_no_std`] for further details.
///
/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
///
- /// [`Invoice`]: crate::offers::invoice::Invoice
- pub fn verify_and_respond_using_derived_keys_no_std<T: secp256k1::Signing>(
+ /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
+ pub fn respond_using_derived_keys_no_std(
&self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
- created_at: core::time::Duration, expanded_key: &ExpandedKey, secp_ctx: &Secp256k1<T>
- ) -> Result<InvoiceBuilder<DerivedSigningPubkey>, SemanticError> {
- if self.features().requires_unknown_bits() {
- return Err(SemanticError::UnknownRequiredFeatures);
+ created_at: core::time::Duration
+ ) -> Result<InvoiceBuilder<DerivedSigningPubkey>, Bolt12SemanticError> {
+ if self.inner.invoice_request_features().requires_unknown_bits() {
+ return Err(Bolt12SemanticError::UnknownRequiredFeatures);
}
- let keys = match self.verify(expanded_key, secp_ctx) {
- Err(()) => return Err(SemanticError::InvalidMetadata),
- Ok(None) => return Err(SemanticError::InvalidMetadata),
- Ok(Some(keys)) => keys,
+ let keys = match self.keys {
+ None => return Err(Bolt12SemanticError::InvalidMetadata),
+ Some(keys) => keys,
};
- InvoiceBuilder::for_offer_using_keys(self, payment_paths, created_at, payment_hash, keys)
- }
-
- /// Verifies that the request was for an offer created using the given key. Returns the derived
- /// keys need to sign an [`Invoice`] for the request if they could be extracted from the
- /// metadata.
- ///
- /// [`Invoice`]: crate::offers::invoice::Invoice
- pub fn verify<T: secp256k1::Signing>(
- &self, key: &ExpandedKey, secp_ctx: &Secp256k1<T>
- ) -> Result<Option<KeyPair>, ()> {
- self.contents.inner.offer.verify(&self.bytes, key, secp_ctx)
- }
-
- #[cfg(test)]
- fn as_tlv_stream(&self) -> FullInvoiceRequestTlvStreamRef {
- let (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream) =
- self.contents.as_tlv_stream();
- let signature_tlv_stream = SignatureTlvStreamRef {
- signature: Some(&self.signature),
- };
- (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, signature_tlv_stream)
+ InvoiceBuilder::for_offer_using_keys(
+ &self.inner, payment_paths, created_at, payment_hash, keys
+ )
}
}
impl InvoiceRequestContents {
- pub fn metadata(&self) -> &[u8] {
+ pub(super) fn metadata(&self) -> &[u8] {
self.inner.metadata()
}
pub(super) fn derives_keys(&self) -> bool {
- self.inner.payer.0.derives_keys()
+ self.inner.payer.0.derives_payer_keys()
}
pub(super) fn chain(&self) -> ChainHash {
self.inner.chain()
}
+ pub(super) fn amount_msats(&self) -> Option<u64> {
+ self.inner.amount_msats
+ }
+
+ pub(super) fn features(&self) -> &InvoiceRequestFeatures {
+ &self.inner.features
+ }
+
+ pub(super) fn quantity(&self) -> Option<u64> {
+ self.inner.quantity
+ }
+
pub(super) fn payer_id(&self) -> PublicKey {
self.payer_id
}
+ pub(super) fn payer_note(&self) -> Option<PrintableString> {
+ self.inner.payer_note.as_ref()
+ .map(|payer_note| PrintableString(payer_note.as_str()))
+ }
+
pub(super) fn as_tlv_stream(&self) -> PartialInvoiceRequestTlvStreamRef {
let (payer, offer, mut invoice_request) = self.inner.as_tlv_stream();
invoice_request.payer_id = Some(&self.payer_id);
}
}
+impl Writeable for UnsignedInvoiceRequest {
+ fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
+ WithoutLength(&self.bytes).write(writer)
+ }
+}
+
impl Writeable for InvoiceRequest {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
WithoutLength(&self.bytes).write(writer)
InvoiceRequestTlvStreamRef<'a>,
);
+impl TryFrom<Vec<u8>> for UnsignedInvoiceRequest {
+ type Error = Bolt12ParseError;
+
+ fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
+ let invoice_request = ParsedMessage::<PartialInvoiceRequestTlvStream>::try_from(bytes)?;
+ let ParsedMessage { bytes, tlv_stream } = invoice_request;
+ let (
+ payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream,
+ ) = tlv_stream;
+ let contents = InvoiceRequestContents::try_from(
+ (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream)
+ )?;
+
+ let tagged_hash = TaggedHash::new(SIGNATURE_TAG, &bytes);
+
+ Ok(UnsignedInvoiceRequest { bytes, contents, tagged_hash })
+ }
+}
+
impl TryFrom<Vec<u8>> for InvoiceRequest {
- type Error = ParseError;
+ type Error = Bolt12ParseError;
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
let invoice_request = ParsedMessage::<FullInvoiceRequestTlvStream>::try_from(bytes)?;
)?;
let signature = match signature {
- None => return Err(ParseError::InvalidSemantics(SemanticError::MissingSignature)),
+ None => return Err(Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingSignature)),
Some(signature) => signature,
};
- merkle::verify_signature(&signature, SIGNATURE_TAG, &bytes, contents.payer_id)?;
+ let message = TaggedHash::new(SIGNATURE_TAG, &bytes);
+ merkle::verify_signature(&signature, message, contents.payer_id)?;
Ok(InvoiceRequest { bytes, contents, signature })
}
}
impl TryFrom<PartialInvoiceRequestTlvStream> for InvoiceRequestContents {
- type Error = SemanticError;
+ type Error = Bolt12SemanticError;
fn try_from(tlv_stream: PartialInvoiceRequestTlvStream) -> Result<Self, Self::Error> {
let (
) = tlv_stream;
let payer = match metadata {
- None => return Err(SemanticError::MissingPayerMetadata),
+ None => return Err(Bolt12SemanticError::MissingPayerMetadata),
Some(metadata) => PayerContents(Metadata::Bytes(metadata)),
};
let offer = OfferContents::try_from(offer_tlv_stream)?;
if !offer.supports_chain(chain.unwrap_or_else(|| offer.implied_chain())) {
- return Err(SemanticError::UnsupportedChain);
+ return Err(Bolt12SemanticError::UnsupportedChain);
}
if offer.amount().is_none() && amount.is_none() {
- return Err(SemanticError::MissingAmount);
+ return Err(Bolt12SemanticError::MissingAmount);
}
offer.check_quantity(quantity)?;
let features = features.unwrap_or_else(InvoiceRequestFeatures::empty);
let payer_id = match payer_id {
- None => return Err(SemanticError::MissingPayerId),
+ None => return Err(Bolt12SemanticError::MissingPayerId),
Some(payer_id) => payer_id,
};
#[cfg(test)]
mod tests {
- use super::{InvoiceRequest, InvoiceRequestTlvStreamRef, SIGNATURE_TAG};
+ use super::{InvoiceRequest, InvoiceRequestTlvStreamRef, SIGNATURE_TAG, UnsignedInvoiceRequest};
use bitcoin::blockdata::constants::ChainHash;
use bitcoin::network::constants::Network;
#[cfg(feature = "std")]
use core::time::Duration;
use crate::sign::KeyMaterial;
- use crate::ln::features::InvoiceRequestFeatures;
+ use crate::ln::channelmanager::PaymentId;
+ use crate::ln::features::{InvoiceRequestFeatures, OfferFeatures};
use crate::ln::inbound_payment::ExpandedKey;
use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
- use crate::offers::invoice::{Invoice, SIGNATURE_TAG as INVOICE_SIGNATURE_TAG};
- use crate::offers::merkle::{SignError, SignatureTlvStreamRef, self};
+ use crate::offers::invoice::{Bolt12Invoice, SIGNATURE_TAG as INVOICE_SIGNATURE_TAG};
+ use crate::offers::merkle::{SignError, SignatureTlvStreamRef, TaggedHash, self};
use crate::offers::offer::{Amount, OfferBuilder, OfferTlvStreamRef, Quantity};
- use crate::offers::parse::{ParseError, SemanticError};
+ use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError};
use crate::offers::payer::PayerTlvStreamRef;
use crate::offers::test_utils::*;
use crate::util::ser::{BigSize, Writeable};
#[test]
fn builds_invoice_request_with_defaults() {
- let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
+ let unsigned_invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
.amount_msats(1000)
.build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
- .build().unwrap()
- .sign(payer_sign).unwrap();
+ .build().unwrap();
+
+ let mut buffer = Vec::new();
+ unsigned_invoice_request.write(&mut buffer).unwrap();
+
+ assert_eq!(unsigned_invoice_request.bytes, buffer.as_slice());
+ assert_eq!(unsigned_invoice_request.payer_metadata(), &[1; 32]);
+ assert_eq!(unsigned_invoice_request.chains(), vec![ChainHash::using_genesis_block(Network::Bitcoin)]);
+ assert_eq!(unsigned_invoice_request.metadata(), None);
+ assert_eq!(unsigned_invoice_request.amount(), Some(&Amount::Bitcoin { amount_msats: 1000 }));
+ assert_eq!(unsigned_invoice_request.description(), PrintableString("foo"));
+ assert_eq!(unsigned_invoice_request.offer_features(), &OfferFeatures::empty());
+ assert_eq!(unsigned_invoice_request.absolute_expiry(), None);
+ assert_eq!(unsigned_invoice_request.paths(), &[]);
+ assert_eq!(unsigned_invoice_request.issuer(), None);
+ assert_eq!(unsigned_invoice_request.supported_quantity(), Quantity::One);
+ assert_eq!(unsigned_invoice_request.signing_pubkey(), recipient_pubkey());
+ assert_eq!(unsigned_invoice_request.chain(), ChainHash::using_genesis_block(Network::Bitcoin));
+ assert_eq!(unsigned_invoice_request.amount_msats(), None);
+ assert_eq!(unsigned_invoice_request.invoice_request_features(), &InvoiceRequestFeatures::empty());
+ assert_eq!(unsigned_invoice_request.quantity(), None);
+ assert_eq!(unsigned_invoice_request.payer_id(), payer_pubkey());
+ assert_eq!(unsigned_invoice_request.payer_note(), None);
+
+ match UnsignedInvoiceRequest::try_from(buffer) {
+ Err(e) => panic!("error parsing unsigned invoice request: {:?}", e),
+ Ok(parsed) => {
+ assert_eq!(parsed.bytes, unsigned_invoice_request.bytes);
+ assert_eq!(parsed.tagged_hash, unsigned_invoice_request.tagged_hash);
+ },
+ }
+
+ let invoice_request = unsigned_invoice_request.sign(payer_sign).unwrap();
let mut buffer = Vec::new();
invoice_request.write(&mut buffer).unwrap();
assert_eq!(invoice_request.bytes, buffer.as_slice());
- assert_eq!(invoice_request.metadata(), &[1; 32]);
+ assert_eq!(invoice_request.payer_metadata(), &[1; 32]);
+ assert_eq!(invoice_request.chains(), vec![ChainHash::using_genesis_block(Network::Bitcoin)]);
+ assert_eq!(invoice_request.metadata(), None);
+ assert_eq!(invoice_request.amount(), Some(&Amount::Bitcoin { amount_msats: 1000 }));
+ assert_eq!(invoice_request.description(), PrintableString("foo"));
+ assert_eq!(invoice_request.offer_features(), &OfferFeatures::empty());
+ assert_eq!(invoice_request.absolute_expiry(), None);
+ assert_eq!(invoice_request.paths(), &[]);
+ assert_eq!(invoice_request.issuer(), None);
+ assert_eq!(invoice_request.supported_quantity(), Quantity::One);
+ assert_eq!(invoice_request.signing_pubkey(), recipient_pubkey());
assert_eq!(invoice_request.chain(), ChainHash::using_genesis_block(Network::Bitcoin));
assert_eq!(invoice_request.amount_msats(), None);
- assert_eq!(invoice_request.features(), &InvoiceRequestFeatures::empty());
+ assert_eq!(invoice_request.invoice_request_features(), &InvoiceRequestFeatures::empty());
assert_eq!(invoice_request.quantity(), None);
assert_eq!(invoice_request.payer_id(), payer_pubkey());
assert_eq!(invoice_request.payer_note(), None);
- assert!(
- merkle::verify_signature(
- &invoice_request.signature, SIGNATURE_TAG, &invoice_request.bytes, payer_pubkey()
- ).is_ok()
- );
+
+ let message = TaggedHash::new(SIGNATURE_TAG, &invoice_request.bytes);
+ assert!(merkle::verify_signature(&invoice_request.signature, message, payer_pubkey()).is_ok());
assert_eq!(
invoice_request.as_tlv_stream(),
.build()
{
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, SemanticError::AlreadyExpired),
+ Err(e) => assert_eq!(e, Bolt12SemanticError::AlreadyExpired),
}
}
let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32]));
let entropy = FixedEntropy {};
let secp_ctx = Secp256k1::new();
+ let payment_id = PaymentId([1; 32]);
let offer = OfferBuilder::new("foo".into(), recipient_pubkey())
.amount_msats(1000)
.build().unwrap();
let invoice_request = offer
- .request_invoice_deriving_metadata(payer_id, &expanded_key, &entropy)
+ .request_invoice_deriving_metadata(payer_id, &expanded_key, &entropy, payment_id)
.unwrap()
.build().unwrap()
.sign(payer_sign).unwrap();
.unwrap()
.build().unwrap()
.sign(recipient_sign).unwrap();
- assert!(invoice.verify(&expanded_key, &secp_ctx));
+ match invoice.verify(&expanded_key, &secp_ctx) {
+ Ok(payment_id) => assert_eq!(payment_id, PaymentId([1; 32])),
+ Err(()) => panic!("verification failed"),
+ }
// Fails verification with altered fields
let (
let mut bytes = Vec::new();
tlv_stream.write(&mut bytes).unwrap();
- let signature = merkle::sign_message(
- recipient_sign, INVOICE_SIGNATURE_TAG, &bytes, recipient_pubkey()
- ).unwrap();
+ let message = TaggedHash::new(INVOICE_SIGNATURE_TAG, &bytes);
+ let signature = merkle::sign_message(recipient_sign, &message, recipient_pubkey()).unwrap();
signature_tlv_stream.signature = Some(&signature);
let mut encoded_invoice = bytes;
signature_tlv_stream.write(&mut encoded_invoice).unwrap();
- let invoice = Invoice::try_from(encoded_invoice).unwrap();
- assert!(!invoice.verify(&expanded_key, &secp_ctx));
+ let invoice = Bolt12Invoice::try_from(encoded_invoice).unwrap();
+ assert!(invoice.verify(&expanded_key, &secp_ctx).is_err());
// Fails verification with altered metadata
let (
let mut bytes = Vec::new();
tlv_stream.write(&mut bytes).unwrap();
- let signature = merkle::sign_message(
- recipient_sign, INVOICE_SIGNATURE_TAG, &bytes, recipient_pubkey()
- ).unwrap();
+ let message = TaggedHash::new(INVOICE_SIGNATURE_TAG, &bytes);
+ let signature = merkle::sign_message(recipient_sign, &message, recipient_pubkey()).unwrap();
signature_tlv_stream.signature = Some(&signature);
let mut encoded_invoice = bytes;
signature_tlv_stream.write(&mut encoded_invoice).unwrap();
- let invoice = Invoice::try_from(encoded_invoice).unwrap();
- assert!(!invoice.verify(&expanded_key, &secp_ctx));
+ let invoice = Bolt12Invoice::try_from(encoded_invoice).unwrap();
+ assert!(invoice.verify(&expanded_key, &secp_ctx).is_err());
}
#[test]
let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32]));
let entropy = FixedEntropy {};
let secp_ctx = Secp256k1::new();
+ let payment_id = PaymentId([1; 32]);
let offer = OfferBuilder::new("foo".into(), recipient_pubkey())
.amount_msats(1000)
.build().unwrap();
let invoice_request = offer
- .request_invoice_deriving_payer_id(&expanded_key, &entropy, &secp_ctx)
+ .request_invoice_deriving_payer_id(&expanded_key, &entropy, &secp_ctx, payment_id)
.unwrap()
.build_and_sign()
.unwrap();
.unwrap()
.build().unwrap()
.sign(recipient_sign).unwrap();
- assert!(invoice.verify(&expanded_key, &secp_ctx));
+ match invoice.verify(&expanded_key, &secp_ctx) {
+ Ok(payment_id) => assert_eq!(payment_id, PaymentId([1; 32])),
+ Err(()) => panic!("verification failed"),
+ }
// Fails verification with altered fields
let (
let mut bytes = Vec::new();
tlv_stream.write(&mut bytes).unwrap();
- let signature = merkle::sign_message(
- recipient_sign, INVOICE_SIGNATURE_TAG, &bytes, recipient_pubkey()
- ).unwrap();
+ let message = TaggedHash::new(INVOICE_SIGNATURE_TAG, &bytes);
+ let signature = merkle::sign_message(recipient_sign, &message, recipient_pubkey()).unwrap();
signature_tlv_stream.signature = Some(&signature);
let mut encoded_invoice = bytes;
signature_tlv_stream.write(&mut encoded_invoice).unwrap();
- let invoice = Invoice::try_from(encoded_invoice).unwrap();
- assert!(!invoice.verify(&expanded_key, &secp_ctx));
+ let invoice = Bolt12Invoice::try_from(encoded_invoice).unwrap();
+ assert!(invoice.verify(&expanded_key, &secp_ctx).is_err());
// Fails verification with altered payer id
let (
let mut bytes = Vec::new();
tlv_stream.write(&mut bytes).unwrap();
- let signature = merkle::sign_message(
- recipient_sign, INVOICE_SIGNATURE_TAG, &bytes, recipient_pubkey()
- ).unwrap();
+ let message = TaggedHash::new(INVOICE_SIGNATURE_TAG, &bytes);
+ let signature = merkle::sign_message(recipient_sign, &message, recipient_pubkey()).unwrap();
signature_tlv_stream.signature = Some(&signature);
let mut encoded_invoice = bytes;
signature_tlv_stream.write(&mut encoded_invoice).unwrap();
- let invoice = Invoice::try_from(encoded_invoice).unwrap();
- assert!(!invoice.verify(&expanded_key, &secp_ctx));
+ let invoice = Bolt12Invoice::try_from(encoded_invoice).unwrap();
+ assert!(invoice.verify(&expanded_key, &secp_ctx).is_err());
}
#[test]
.chain(Network::Bitcoin)
{
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, SemanticError::UnsupportedChain),
+ Err(e) => assert_eq!(e, Bolt12SemanticError::UnsupportedChain),
}
match OfferBuilder::new("foo".into(), recipient_pubkey())
.build()
{
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, SemanticError::UnsupportedChain),
+ Err(e) => assert_eq!(e, Bolt12SemanticError::UnsupportedChain),
}
}
.amount_msats(999)
{
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, SemanticError::InsufficientAmount),
+ Err(e) => assert_eq!(e, Bolt12SemanticError::InsufficientAmount),
}
match OfferBuilder::new("foo".into(), recipient_pubkey())
.amount_msats(1000)
{
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, SemanticError::InsufficientAmount),
+ Err(e) => assert_eq!(e, Bolt12SemanticError::InsufficientAmount),
}
match OfferBuilder::new("foo".into(), recipient_pubkey())
.amount_msats(MAX_VALUE_MSAT + 1)
{
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, SemanticError::InvalidAmount),
+ Err(e) => assert_eq!(e, Bolt12SemanticError::InvalidAmount),
}
match OfferBuilder::new("foo".into(), recipient_pubkey())
.build()
{
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, SemanticError::InsufficientAmount),
+ Err(e) => assert_eq!(e, Bolt12SemanticError::InsufficientAmount),
}
match OfferBuilder::new("foo".into(), recipient_pubkey())
.build()
{
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, SemanticError::MissingAmount),
+ Err(e) => assert_eq!(e, Bolt12SemanticError::MissingAmount),
}
match OfferBuilder::new("foo".into(), recipient_pubkey())
.build()
{
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, SemanticError::InvalidAmount),
+ Err(e) => assert_eq!(e, Bolt12SemanticError::InvalidAmount),
}
}
.build().unwrap()
.sign(payer_sign).unwrap();
let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream();
- assert_eq!(invoice_request.features(), &InvoiceRequestFeatures::unknown());
+ assert_eq!(invoice_request.invoice_request_features(), &InvoiceRequestFeatures::unknown());
assert_eq!(tlv_stream.features, Some(&InvoiceRequestFeatures::unknown()));
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
.build().unwrap()
.sign(payer_sign).unwrap();
let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream();
- assert_eq!(invoice_request.features(), &InvoiceRequestFeatures::empty());
+ assert_eq!(invoice_request.invoice_request_features(), &InvoiceRequestFeatures::empty());
assert_eq!(tlv_stream.features, None);
}
.quantity(2)
{
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, SemanticError::UnexpectedQuantity),
+ Err(e) => assert_eq!(e, Bolt12SemanticError::UnexpectedQuantity),
}
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
.quantity(11)
{
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, SemanticError::InvalidQuantity),
+ Err(e) => assert_eq!(e, Bolt12SemanticError::InvalidQuantity),
}
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
.build()
{
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, SemanticError::MissingQuantity),
+ Err(e) => assert_eq!(e, Bolt12SemanticError::MissingQuantity),
}
match OfferBuilder::new("foo".into(), recipient_pubkey())
.build()
{
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, SemanticError::MissingQuantity),
+ Err(e) => assert_eq!(e, Bolt12SemanticError::MissingQuantity),
}
}
.respond_with_no_std(payment_paths(), payment_hash(), now())
{
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, SemanticError::UnknownRequiredFeatures),
+ Err(e) => assert_eq!(e, Bolt12SemanticError::UnknownRequiredFeatures),
}
}
match InvoiceRequest::try_from(buffer) {
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::UnsupportedChain)),
+ Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::UnsupportedChain)),
}
}
match InvoiceRequest::try_from(buffer) {
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingAmount)),
+ Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingAmount)),
}
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
match InvoiceRequest::try_from(buffer) {
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::InsufficientAmount)),
+ Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::InsufficientAmount)),
}
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
match InvoiceRequest::try_from(buffer) {
Ok(_) => panic!("expected error"),
Err(e) => {
- assert_eq!(e, ParseError::InvalidSemantics(SemanticError::UnsupportedCurrency));
+ assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::UnsupportedCurrency));
},
}
match InvoiceRequest::try_from(buffer) {
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::InvalidAmount)),
+ Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::InvalidAmount)),
}
}
match InvoiceRequest::try_from(buffer) {
Ok(_) => panic!("expected error"),
Err(e) => {
- assert_eq!(e, ParseError::InvalidSemantics(SemanticError::UnexpectedQuantity));
+ assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::UnexpectedQuantity));
},
}
match InvoiceRequest::try_from(buffer) {
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::InvalidQuantity)),
+ Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::InvalidQuantity)),
}
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
match InvoiceRequest::try_from(buffer) {
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingQuantity)),
+ Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingQuantity)),
}
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
match InvoiceRequest::try_from(buffer) {
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingQuantity)),
+ Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingQuantity)),
}
}
.build().unwrap();
let unsigned_invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
.build().unwrap();
- let mut tlv_stream = unsigned_invoice_request.invoice_request.as_tlv_stream();
+ let mut tlv_stream = unsigned_invoice_request.contents.as_tlv_stream();
tlv_stream.0.metadata = None;
let mut buffer = Vec::new();
match InvoiceRequest::try_from(buffer) {
Ok(_) => panic!("expected error"),
Err(e) => {
- assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingPayerMetadata));
+ assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingPayerMetadata));
},
}
}
.build().unwrap();
let unsigned_invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
.build().unwrap();
- let mut tlv_stream = unsigned_invoice_request.invoice_request.as_tlv_stream();
+ let mut tlv_stream = unsigned_invoice_request.contents.as_tlv_stream();
tlv_stream.2.payer_id = None;
let mut buffer = Vec::new();
match InvoiceRequest::try_from(buffer) {
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingPayerId)),
+ Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingPayerId)),
}
}
.build().unwrap();
let unsigned_invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
.build().unwrap();
- let mut tlv_stream = unsigned_invoice_request.invoice_request.as_tlv_stream();
+ let mut tlv_stream = unsigned_invoice_request.contents.as_tlv_stream();
tlv_stream.1.node_id = None;
let mut buffer = Vec::new();
match InvoiceRequest::try_from(buffer) {
Ok(_) => panic!("expected error"),
Err(e) => {
- assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingSigningPubkey));
+ assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingSigningPubkey));
},
}
}
.build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
.build().unwrap()
- .invoice_request
+ .contents
.write(&mut buffer).unwrap();
match InvoiceRequest::try_from(buffer) {
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingSignature)),
+ Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingSignature)),
}
}
match InvoiceRequest::try_from(buffer) {
Ok(_) => panic!("expected error"),
Err(e) => {
- assert_eq!(e, ParseError::InvalidSignature(secp256k1::Error::InvalidSignature));
+ assert_eq!(e, Bolt12ParseError::InvalidSignature(secp256k1::Error::InvalidSignature));
},
}
}
.build().unwrap()
.request_invoice(vec![1; 32], keys.public_key()).unwrap()
.build().unwrap()
- .sign::<_, Infallible>(|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys)))
+ .sign::<_, Infallible>(
+ |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
+ )
.unwrap();
let mut encoded_invoice_request = Vec::new();
match InvoiceRequest::try_from(encoded_invoice_request) {
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, ParseError::Decode(DecodeError::InvalidValue)),
+ Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::InvalidValue)),
}
}
}
use bitcoin::hashes::{Hash, HashEngine, sha256};
use bitcoin::secp256k1::{Message, PublicKey, Secp256k1, self};
use bitcoin::secp256k1::schnorr::Signature;
+use core::convert::AsRef;
use crate::io;
use crate::util::ser::{BigSize, Readable, Writeable, Writer};
(240, signature: Signature),
});
+/// A hash for use in a specific context by tweaking with a context-dependent tag as per [BIP 340]
+/// and computed over the merkle root of a TLV stream to sign as defined in [BOLT 12].
+///
+/// [BIP 340]: https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki
+/// [BOLT 12]: https://github.com/rustyrussell/lightning-rfc/blob/guilt/offers/12-offer-encoding.md#signature-calculation
+#[derive(Debug, PartialEq)]
+pub struct TaggedHash(Message);
+
+impl TaggedHash {
+ /// Creates a tagged hash with the given parameters.
+ ///
+ /// Panics if `tlv_stream` is not a well-formed TLV stream containing at least one TLV record.
+ pub(super) fn new(tag: &str, tlv_stream: &[u8]) -> Self {
+ Self(message_digest(tag, tlv_stream))
+ }
+
+ /// Returns the digest to sign.
+ pub fn as_digest(&self) -> &Message {
+ &self.0
+ }
+}
+
+impl AsRef<TaggedHash> for TaggedHash {
+ fn as_ref(&self) -> &TaggedHash {
+ self
+ }
+}
+
/// Error when signing messages.
#[derive(Debug, PartialEq)]
pub enum SignError<E> {
Verification(secp256k1::Error),
}
-/// Signs a message digest consisting of a tagged hash of the given bytes, checking if it can be
-/// verified with the supplied pubkey.
+/// Signs a [`TaggedHash`] computed over the merkle root of `message`'s TLV stream, checking if it
+/// can be verified with the supplied `pubkey`.
+///
+/// Since `message` is any type that implements [`AsRef<TaggedHash>`], `sign` may be a closure that
+/// takes a message such as [`Bolt12Invoice`] or [`InvoiceRequest`]. This allows further message
+/// verification before signing its [`TaggedHash`].
///
-/// Panics if `bytes` is not a well-formed TLV stream containing at least one TLV record.
-pub(super) fn sign_message<F, E>(
- sign: F, tag: &str, bytes: &[u8], pubkey: PublicKey,
+/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
+/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
+pub(super) fn sign_message<F, E, T>(
+ sign: F, message: &T, pubkey: PublicKey,
) -> Result<Signature, SignError<E>>
where
- F: FnOnce(&Message) -> Result<Signature, E>
+ F: FnOnce(&T) -> Result<Signature, E>,
+ T: AsRef<TaggedHash>,
{
- let digest = message_digest(tag, bytes);
- let signature = sign(&digest).map_err(|e| SignError::Signing(e))?;
+ let signature = sign(message).map_err(|e| SignError::Signing(e))?;
+ let digest = message.as_ref().as_digest();
let pubkey = pubkey.into();
let secp_ctx = Secp256k1::verification_only();
- secp_ctx.verify_schnorr(&signature, &digest, &pubkey).map_err(|e| SignError::Verification(e))?;
+ secp_ctx.verify_schnorr(&signature, digest, &pubkey).map_err(|e| SignError::Verification(e))?;
Ok(signature)
}
-/// Verifies the signature with a pubkey over the given bytes using a tagged hash as the message
+/// Verifies the signature with a pubkey over the given message using a tagged hash as the message
/// digest.
-///
-/// Panics if `bytes` is not a well-formed TLV stream containing at least one TLV record.
pub(super) fn verify_signature(
- signature: &Signature, tag: &str, bytes: &[u8], pubkey: PublicKey,
+ signature: &Signature, message: TaggedHash, pubkey: PublicKey,
) -> Result<(), secp256k1::Error> {
- let digest = message_digest(tag, bytes);
+ let digest = message.as_digest();
let pubkey = pubkey.into();
let secp_ctx = Secp256k1::verification_only();
- secp_ctx.verify_schnorr(signature, &digest, &pubkey)
+ secp_ctx.verify_schnorr(signature, digest, &pubkey)
}
pub(super) fn message_digest(tag: &str, bytes: &[u8]) -> Message {
/// Encoding for a pre-serialized TLV stream that excludes any signature TLV records.
///
/// Panics if the wrapped bytes are not a well-formed TLV stream.
-pub(super) struct WithoutSignatures<'a>(pub &'a Vec<u8>);
+pub(super) struct WithoutSignatures<'a>(pub &'a [u8]);
impl<'a> Writeable for WithoutSignatures<'a> {
#[inline]
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
- let tlv_stream = TlvStream::new(&self.0[..]);
+ let tlv_stream = TlvStream::new(self.0);
for record in tlv_stream.skip_signatures() {
writer.write_all(record.record_bytes)?;
}
.build_unchecked()
.request_invoice(vec![0; 8], payer_keys.public_key()).unwrap()
.build_unchecked()
- .sign::<_, Infallible>(|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &payer_keys)))
+ .sign::<_, Infallible>(
+ |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &payer_keys))
+ )
.unwrap();
assert_eq!(
invoice_request.to_string(),
.build_unchecked()
.request_invoice(vec![0; 8], payer_keys.public_key()).unwrap()
.build_unchecked()
- .sign::<_, Infallible>(|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &payer_keys)))
+ .sign::<_, Infallible>(
+ |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &payer_keys))
+ )
.unwrap();
let mut bytes_without_signature = Vec::new();
.build_unchecked()
.request_invoice(vec![0; 8], payer_keys.public_key()).unwrap()
.build_unchecked()
- .sign::<_, Infallible>(|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &payer_keys)))
+ .sign::<_, Infallible>(
+ |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &payer_keys))
+ )
.unwrap();
let tlv_stream = TlvStream::new(&invoice_request.bytes).range(0..1)
//!
//! Offers are a flexible protocol for Lightning payments.
+#[macro_use]
+pub mod offer;
+
pub mod invoice;
pub mod invoice_error;
pub mod invoice_request;
-mod merkle;
-pub mod offer;
+pub mod merkle;
pub mod parse;
mod payer;
pub mod refund;
-#[allow(unused)]
pub(crate) mod signer;
#[cfg(test)]
-mod test_utils;
+pub(crate) mod test_utils;
//!
//! use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey};
//! use lightning::offers::offer::{Offer, OfferBuilder, Quantity};
-//! use lightning::offers::parse::ParseError;
+//! use lightning::offers::parse::Bolt12ParseError;
//! use lightning::util::ser::{Readable, Writeable};
//!
//! # use lightning::blinded_path::BlindedPath;
//! # fn create_another_blinded_path() -> BlindedPath { unimplemented!() }
//! #
//! # #[cfg(feature = "std")]
-//! # fn build() -> Result<(), ParseError> {
+//! # fn build() -> Result<(), Bolt12ParseError> {
//! let secp_ctx = Secp256k1::new();
//! let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
//! let pubkey = PublicKey::from(keys);
use crate::sign::EntropySource;
use crate::io;
use crate::blinded_path::BlindedPath;
+use crate::ln::channelmanager::PaymentId;
use crate::ln::features::OfferFeatures;
use crate::ln::inbound_payment::{ExpandedKey, IV_LEN, Nonce};
use crate::ln::msgs::MAX_VALUE_MSAT;
use crate::offers::invoice_request::{DerivedPayerId, ExplicitPayerId, InvoiceRequestBuilder};
use crate::offers::merkle::TlvStream;
-use crate::offers::parse::{Bech32Encode, ParseError, ParsedMessage, SemanticError};
+use crate::offers::parse::{Bech32Encode, Bolt12ParseError, Bolt12SemanticError, ParsedMessage};
use crate::offers::signer::{Metadata, MetadataMaterial, self};
use crate::util::ser::{HighZeroBytesDroppedBigSize, WithoutLength, Writeable, Writer};
use crate::util::string::PrintableString;
/// Sets the [`Offer::metadata`] to the given bytes.
///
/// Successive calls to this method will override the previous setting.
- pub fn metadata(mut self, metadata: Vec<u8>) -> Result<Self, SemanticError> {
+ pub fn metadata(mut self, metadata: Vec<u8>) -> Result<Self, Bolt12SemanticError> {
self.offer.metadata = Some(Metadata::Bytes(metadata));
Ok(self)
}
secp_ctx: &'a Secp256k1<T>
) -> Self where ES::Target: EntropySource {
let nonce = Nonce::from_entropy_source(entropy_source);
- let derivation_material = MetadataMaterial::new(nonce, expanded_key, IV_BYTES);
+ let derivation_material = MetadataMaterial::new(nonce, expanded_key, IV_BYTES, None);
let metadata = Metadata::DerivedSigningPubkey(derivation_material);
OfferBuilder {
offer: OfferContents {
}
/// Builds an [`Offer`] from the builder's settings.
- pub fn build(mut self) -> Result<Offer, SemanticError> {
+ pub fn build(mut self) -> Result<Offer, Bolt12SemanticError> {
match self.offer.amount {
Some(Amount::Bitcoin { amount_msats }) => {
if amount_msats > MAX_VALUE_MSAT {
- return Err(SemanticError::InvalidAmount);
+ return Err(Bolt12SemanticError::InvalidAmount);
}
},
- Some(Amount::Currency { .. }) => return Err(SemanticError::UnsupportedCurrency),
+ Some(Amount::Currency { .. }) => return Err(Bolt12SemanticError::UnsupportedCurrency),
None => {},
}
let mut tlv_stream = self.offer.as_tlv_stream();
debug_assert_eq!(tlv_stream.metadata, None);
tlv_stream.metadata = None;
- if metadata.derives_keys() {
+ if metadata.derives_recipient_keys() {
tlv_stream.node_id = None;
}
/// An `Offer` is a potentially long-lived proposal for payment of a good or service.
///
/// An offer is a precursor to an [`InvoiceRequest`]. A merchant publishes an offer from which a
-/// customer may request an [`Invoice`] for a specific quantity and using an amount sufficient to
-/// cover that quantity (i.e., at least `quantity * amount`). See [`Offer::amount`].
+/// customer may request an [`Bolt12Invoice`] for a specific quantity and using an amount sufficient
+/// to cover that quantity (i.e., at least `quantity * amount`). See [`Offer::amount`].
///
/// Offers may be denominated in currency other than bitcoin but are ultimately paid using the
/// latter.
/// Through the use of [`BlindedPath`]s, offers provide recipient privacy.
///
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
-/// [`Invoice`]: crate::offers::invoice::Invoice
+/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
#[derive(Clone, Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub struct Offer {
pub(super) contents: OfferContents,
}
-/// The contents of an [`Offer`], which may be shared with an [`InvoiceRequest`] or an [`Invoice`].
+/// The contents of an [`Offer`], which may be shared with an [`InvoiceRequest`] or a
+/// [`Bolt12Invoice`].
///
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
-/// [`Invoice`]: crate::offers::invoice::Invoice
+/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
#[derive(Clone, Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub(super) struct OfferContents {
signing_pubkey: PublicKey,
}
-impl Offer {
+macro_rules! offer_accessors { ($self: ident, $contents: expr) => {
// TODO: Return a slice once ChainHash has constants.
// - https://github.com/rust-bitcoin/rust-bitcoin/pull/1283
// - https://github.com/rust-bitcoin/rust-bitcoin/pull/1286
/// The chains that may be used when paying a requested invoice (e.g., bitcoin mainnet).
/// Payments must be denominated in units of the minimal lightning-payable unit (e.g., msats)
/// for the selected chain.
- pub fn chains(&self) -> Vec<ChainHash> {
- self.contents.chains()
- }
-
- pub(super) fn implied_chain(&self) -> ChainHash {
- self.contents.implied_chain()
- }
-
- /// Returns whether the given chain is supported by the offer.
- pub fn supports_chain(&self, chain: ChainHash) -> bool {
- self.contents.supports_chain(chain)
+ pub fn chains(&$self) -> Vec<$crate::bitcoin::blockdata::constants::ChainHash> {
+ $contents.chains()
}
// TODO: Link to corresponding method in `InvoiceRequest`.
/// Opaque bytes set by the originator. Useful for authentication and validating fields since it
/// is reflected in `invoice_request` messages along with all the other fields from the `offer`.
- pub fn metadata(&self) -> Option<&Vec<u8>> {
- self.contents.metadata()
+ pub fn metadata(&$self) -> Option<&Vec<u8>> {
+ $contents.metadata()
}
/// The minimum amount required for a successful payment of a single item.
- pub fn amount(&self) -> Option<&Amount> {
- self.contents.amount()
+ pub fn amount(&$self) -> Option<&$crate::offers::offer::Amount> {
+ $contents.amount()
}
/// A complete description of the purpose of the payment. Intended to be displayed to the user
/// but with the caveat that it has not been verified in any way.
- pub fn description(&self) -> PrintableString {
- self.contents.description()
+ pub fn description(&$self) -> $crate::util::string::PrintableString {
+ $contents.description()
}
/// Features pertaining to the offer.
- pub fn features(&self) -> &OfferFeatures {
- &self.contents.features
+ pub fn offer_features(&$self) -> &$crate::ln::features::OfferFeatures {
+ &$contents.features()
}
/// Duration since the Unix epoch when an invoice should no longer be requested.
///
/// If `None`, the offer does not expire.
- pub fn absolute_expiry(&self) -> Option<Duration> {
- self.contents.absolute_expiry
- }
-
- /// Whether the offer has expired.
- #[cfg(feature = "std")]
- pub fn is_expired(&self) -> bool {
- self.contents.is_expired()
+ pub fn absolute_expiry(&$self) -> Option<core::time::Duration> {
+ $contents.absolute_expiry()
}
/// The issuer of the offer, possibly beginning with `user@domain` or `domain`. Intended to be
/// displayed to the user but with the caveat that it has not been verified in any way.
- pub fn issuer(&self) -> Option<PrintableString> {
- self.contents.issuer.as_ref().map(|issuer| PrintableString(issuer.as_str()))
+ pub fn issuer(&$self) -> Option<$crate::util::string::PrintableString> {
+ $contents.issuer()
}
/// Paths to the recipient originating from publicly reachable nodes. Blinded paths provide
/// recipient privacy by obfuscating its node id.
- pub fn paths(&self) -> &[BlindedPath] {
- self.contents.paths.as_ref().map(|paths| paths.as_slice()).unwrap_or(&[])
+ pub fn paths(&$self) -> &[$crate::blinded_path::BlindedPath] {
+ $contents.paths()
}
/// The quantity of items supported.
- pub fn supported_quantity(&self) -> Quantity {
- self.contents.supported_quantity()
+ pub fn supported_quantity(&$self) -> $crate::offers::offer::Quantity {
+ $contents.supported_quantity()
+ }
+
+ /// The public key used by the recipient to sign invoices.
+ pub fn signing_pubkey(&$self) -> $crate::bitcoin::secp256k1::PublicKey {
+ $contents.signing_pubkey()
+ }
+} }
+
+impl Offer {
+ offer_accessors!(self, self.contents);
+
+ pub(super) fn implied_chain(&self) -> ChainHash {
+ self.contents.implied_chain()
+ }
+
+ /// Returns whether the given chain is supported by the offer.
+ pub fn supports_chain(&self, chain: ChainHash) -> bool {
+ self.contents.supports_chain(chain)
+ }
+
+ /// Whether the offer has expired.
+ #[cfg(feature = "std")]
+ pub fn is_expired(&self) -> bool {
+ self.contents.is_expired()
}
/// Returns whether the given quantity is valid for the offer.
self.contents.expects_quantity()
}
- /// The public key used by the recipient to sign invoices.
- pub fn signing_pubkey(&self) -> PublicKey {
- self.contents.signing_pubkey()
- }
-
/// Similar to [`Offer::request_invoice`] except it:
/// - derives the [`InvoiceRequest::payer_id`] such that a different key can be used for each
- /// request, and
- /// - sets the [`InvoiceRequest::metadata`] when [`InvoiceRequestBuilder::build`] is called such
- /// that it can be used by [`Invoice::verify`] to determine if the invoice was requested using
- /// a base [`ExpandedKey`] from which the payer id was derived.
+ /// request,
+ /// - sets [`InvoiceRequest::payer_metadata`] when [`InvoiceRequestBuilder::build`] is called
+ /// such that it can be used by [`Bolt12Invoice::verify`] to determine if the invoice was
+ /// requested using a base [`ExpandedKey`] from which the payer id was derived, and
+ /// - includes the [`PaymentId`] encrypted in [`InvoiceRequest::payer_metadata`] so that it can
+ /// be used when sending the payment for the requested invoice.
///
/// Useful to protect the sender's privacy.
///
/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
///
/// [`InvoiceRequest::payer_id`]: crate::offers::invoice_request::InvoiceRequest::payer_id
- /// [`InvoiceRequest::metadata`]: crate::offers::invoice_request::InvoiceRequest::metadata
- /// [`Invoice::verify`]: crate::offers::invoice::Invoice::verify
+ /// [`InvoiceRequest::payer_metadata`]: crate::offers::invoice_request::InvoiceRequest::payer_metadata
+ /// [`Bolt12Invoice::verify`]: crate::offers::invoice::Bolt12Invoice::verify
/// [`ExpandedKey`]: crate::ln::inbound_payment::ExpandedKey
pub fn request_invoice_deriving_payer_id<'a, 'b, ES: Deref, T: secp256k1::Signing>(
- &'a self, expanded_key: &ExpandedKey, entropy_source: ES, secp_ctx: &'b Secp256k1<T>
- ) -> Result<InvoiceRequestBuilder<'a, 'b, DerivedPayerId, T>, SemanticError>
+ &'a self, expanded_key: &ExpandedKey, entropy_source: ES, secp_ctx: &'b Secp256k1<T>,
+ payment_id: PaymentId
+ ) -> Result<InvoiceRequestBuilder<'a, 'b, DerivedPayerId, T>, Bolt12SemanticError>
where
ES::Target: EntropySource,
{
- if self.features().requires_unknown_bits() {
- return Err(SemanticError::UnknownRequiredFeatures);
+ if self.offer_features().requires_unknown_bits() {
+ return Err(Bolt12SemanticError::UnknownRequiredFeatures);
}
- Ok(InvoiceRequestBuilder::deriving_payer_id(self, expanded_key, entropy_source, secp_ctx))
+ Ok(InvoiceRequestBuilder::deriving_payer_id(
+ self, expanded_key, entropy_source, secp_ctx, payment_id
+ ))
}
/// Similar to [`Offer::request_invoice_deriving_payer_id`] except uses `payer_id` for the
///
/// [`InvoiceRequest::payer_id`]: crate::offers::invoice_request::InvoiceRequest::payer_id
pub fn request_invoice_deriving_metadata<ES: Deref>(
- &self, payer_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES
- ) -> Result<InvoiceRequestBuilder<ExplicitPayerId, secp256k1::SignOnly>, SemanticError>
+ &self, payer_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES,
+ payment_id: PaymentId
+ ) -> Result<InvoiceRequestBuilder<ExplicitPayerId, secp256k1::SignOnly>, Bolt12SemanticError>
where
ES::Target: EntropySource,
{
- if self.features().requires_unknown_bits() {
- return Err(SemanticError::UnknownRequiredFeatures);
+ if self.offer_features().requires_unknown_bits() {
+ return Err(Bolt12SemanticError::UnknownRequiredFeatures);
}
- Ok(InvoiceRequestBuilder::deriving_metadata(self, payer_id, expanded_key, entropy_source))
+ Ok(InvoiceRequestBuilder::deriving_metadata(
+ self, payer_id, expanded_key, entropy_source, payment_id
+ ))
}
/// Creates an [`InvoiceRequestBuilder`] for the offer with the given `metadata` and `payer_id`,
- /// which will be reflected in the `Invoice` response.
+ /// which will be reflected in the `Bolt12Invoice` response.
///
/// The `metadata` is useful for including information about the derivation of `payer_id` such
/// that invoice response handling can be stateless. Also serves as payer-provided entropy while
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
pub fn request_invoice(
&self, metadata: Vec<u8>, payer_id: PublicKey
- ) -> Result<InvoiceRequestBuilder<ExplicitPayerId, secp256k1::SignOnly>, SemanticError> {
- if self.features().requires_unknown_bits() {
- return Err(SemanticError::UnknownRequiredFeatures);
+ ) -> Result<InvoiceRequestBuilder<ExplicitPayerId, secp256k1::SignOnly>, Bolt12SemanticError> {
+ if self.offer_features().requires_unknown_bits() {
+ return Err(Bolt12SemanticError::UnknownRequiredFeatures);
}
Ok(InvoiceRequestBuilder::new(self, metadata, payer_id))
self.metadata.as_ref().and_then(|metadata| metadata.as_bytes())
}
+ pub fn amount(&self) -> Option<&Amount> {
+ self.amount.as_ref()
+ }
+
pub fn description(&self) -> PrintableString {
PrintableString(&self.description)
}
+ pub fn features(&self) -> &OfferFeatures {
+ &self.features
+ }
+
+ pub fn absolute_expiry(&self) -> Option<Duration> {
+ self.absolute_expiry
+ }
+
#[cfg(feature = "std")]
pub(super) fn is_expired(&self) -> bool {
match self.absolute_expiry {
}
}
- pub fn amount(&self) -> Option<&Amount> {
- self.amount.as_ref()
+ pub fn issuer(&self) -> Option<PrintableString> {
+ self.issuer.as_ref().map(|issuer| PrintableString(issuer.as_str()))
+ }
+
+ pub fn paths(&self) -> &[BlindedPath] {
+ self.paths.as_ref().map(|paths| paths.as_slice()).unwrap_or(&[])
}
pub(super) fn check_amount_msats_for_quantity(
&self, amount_msats: Option<u64>, quantity: Option<u64>
- ) -> Result<(), SemanticError> {
+ ) -> Result<(), Bolt12SemanticError> {
let offer_amount_msats = match self.amount {
None => 0,
Some(Amount::Bitcoin { amount_msats }) => amount_msats,
- Some(Amount::Currency { .. }) => return Err(SemanticError::UnsupportedCurrency),
+ Some(Amount::Currency { .. }) => return Err(Bolt12SemanticError::UnsupportedCurrency),
};
if !self.expects_quantity() || quantity.is_some() {
let expected_amount_msats = offer_amount_msats.checked_mul(quantity.unwrap_or(1))
- .ok_or(SemanticError::InvalidAmount)?;
+ .ok_or(Bolt12SemanticError::InvalidAmount)?;
let amount_msats = amount_msats.unwrap_or(expected_amount_msats);
if amount_msats < expected_amount_msats {
- return Err(SemanticError::InsufficientAmount);
+ return Err(Bolt12SemanticError::InsufficientAmount);
}
if amount_msats > MAX_VALUE_MSAT {
- return Err(SemanticError::InvalidAmount);
+ return Err(Bolt12SemanticError::InvalidAmount);
}
}
self.supported_quantity
}
- pub(super) fn check_quantity(&self, quantity: Option<u64>) -> Result<(), SemanticError> {
+ pub(super) fn check_quantity(&self, quantity: Option<u64>) -> Result<(), Bolt12SemanticError> {
let expects_quantity = self.expects_quantity();
match quantity {
- None if expects_quantity => Err(SemanticError::MissingQuantity),
- Some(_) if !expects_quantity => Err(SemanticError::UnexpectedQuantity),
+ None if expects_quantity => Err(Bolt12SemanticError::MissingQuantity),
+ Some(_) if !expects_quantity => Err(Bolt12SemanticError::UnexpectedQuantity),
Some(quantity) if !self.is_valid_quantity(quantity) => {
- Err(SemanticError::InvalidQuantity)
+ Err(Bolt12SemanticError::InvalidQuantity)
},
_ => Ok(()),
}
let tlv_stream = TlvStream::new(bytes).range(OFFER_TYPES).filter(|record| {
match record.r#type {
OFFER_METADATA_TYPE => false,
- OFFER_NODE_ID_TYPE => !self.metadata.as_ref().unwrap().derives_keys(),
+ OFFER_NODE_ID_TYPE => {
+ !self.metadata.as_ref().unwrap().derives_recipient_keys()
+ },
_ => true,
}
});
- signer::verify_metadata(
+ signer::verify_recipient_metadata(
metadata, key, IV_BYTES, self.signing_pubkey(), tlv_stream, secp_ctx
)
},
}
impl FromStr for Offer {
- type Err = ParseError;
+ type Err = Bolt12ParseError;
fn from_str(s: &str) -> Result<Self, <Self as FromStr>::Err> {
Self::from_bech32_str(s)
}
impl TryFrom<Vec<u8>> for Offer {
- type Error = ParseError;
+ type Error = Bolt12ParseError;
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
let offer = ParsedMessage::<OfferTlvStream>::try_from(bytes)?;
}
impl TryFrom<OfferTlvStream> for OfferContents {
- type Error = SemanticError;
+ type Error = Bolt12SemanticError;
fn try_from(tlv_stream: OfferTlvStream) -> Result<Self, Self::Error> {
let OfferTlvStream {
let amount = match (currency, amount) {
(None, None) => None,
(None, Some(amount_msats)) if amount_msats > MAX_VALUE_MSAT => {
- return Err(SemanticError::InvalidAmount);
+ return Err(Bolt12SemanticError::InvalidAmount);
},
(None, Some(amount_msats)) => Some(Amount::Bitcoin { amount_msats }),
- (Some(_), None) => return Err(SemanticError::MissingAmount),
+ (Some(_), None) => return Err(Bolt12SemanticError::MissingAmount),
(Some(iso4217_code), Some(amount)) => Some(Amount::Currency { iso4217_code, amount }),
};
let description = match description {
- None => return Err(SemanticError::MissingDescription),
+ None => return Err(Bolt12SemanticError::MissingDescription),
Some(description) => description,
};
};
let signing_pubkey = match node_id {
- None => return Err(SemanticError::MissingSigningPubkey),
+ None => return Err(Bolt12SemanticError::MissingSigningPubkey),
Some(node_id) => node_id,
};
use crate::ln::features::OfferFeatures;
use crate::ln::inbound_payment::ExpandedKey;
use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
- use crate::offers::parse::{ParseError, SemanticError};
+ use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError};
use crate::offers::test_utils::*;
use crate::util::ser::{BigSize, Writeable};
use crate::util::string::PrintableString;
assert_eq!(offer.metadata(), None);
assert_eq!(offer.amount(), None);
assert_eq!(offer.description(), PrintableString("foo"));
- assert_eq!(offer.features(), &OfferFeatures::empty());
+ assert_eq!(offer.offer_features(), &OfferFeatures::empty());
assert_eq!(offer.absolute_expiry(), None);
#[cfg(feature = "std")]
assert!(!offer.is_expired());
assert_eq!(tlv_stream.currency, Some(b"USD"));
match builder.build() {
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, SemanticError::UnsupportedCurrency),
+ Err(e) => assert_eq!(e, Bolt12SemanticError::UnsupportedCurrency),
}
let offer = OfferBuilder::new("foo".into(), pubkey(42))
let invalid_amount = Amount::Bitcoin { amount_msats: MAX_VALUE_MSAT + 1 };
match OfferBuilder::new("foo".into(), pubkey(42)).amount(invalid_amount).build() {
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, SemanticError::InvalidAmount),
+ Err(e) => assert_eq!(e, Bolt12SemanticError::InvalidAmount),
}
}
.features_unchecked(OfferFeatures::unknown())
.build()
.unwrap();
- assert_eq!(offer.features(), &OfferFeatures::unknown());
+ assert_eq!(offer.offer_features(), &OfferFeatures::unknown());
assert_eq!(offer.as_tlv_stream().features, Some(&OfferFeatures::unknown()));
let offer = OfferBuilder::new("foo".into(), pubkey(42))
.features_unchecked(OfferFeatures::empty())
.build()
.unwrap();
- assert_eq!(offer.features(), &OfferFeatures::empty());
+ assert_eq!(offer.offer_features(), &OfferFeatures::empty());
assert_eq!(offer.as_tlv_stream().features, None);
}
.request_invoice(vec![1; 32], pubkey(43))
{
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, SemanticError::UnknownRequiredFeatures),
+ Err(e) => assert_eq!(e, Bolt12SemanticError::UnknownRequiredFeatures),
}
}
match Offer::try_from(encoded_offer) {
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingAmount)),
+ Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingAmount)),
}
let mut tlv_stream = offer.as_tlv_stream();
match Offer::try_from(encoded_offer) {
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::InvalidAmount)),
+ Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::InvalidAmount)),
}
}
match Offer::try_from(encoded_offer) {
Ok(_) => panic!("expected error"),
Err(e) => {
- assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingDescription));
+ assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingDescription));
},
}
}
match Offer::try_from(encoded_offer) {
Ok(_) => panic!("expected error"),
Err(e) => {
- assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingSigningPubkey));
+ assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingSigningPubkey));
},
}
}
match Offer::try_from(encoded_offer) {
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, ParseError::Decode(DecodeError::InvalidValue)),
+ Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::InvalidValue)),
}
}
}
#[cfg(test)]
mod bech32_tests {
- use super::{Offer, ParseError};
+ use super::{Bolt12ParseError, Offer};
use bitcoin::bech32;
use crate::ln::msgs::DecodeError;
for encoded_offer in &offers {
match encoded_offer.parse::<Offer>() {
Ok(_) => panic!("Valid offer: {}", encoded_offer),
- Err(e) => assert_eq!(e, ParseError::InvalidContinuation),
+ Err(e) => assert_eq!(e, Bolt12ParseError::InvalidContinuation),
}
}
let encoded_offer = "lni1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg";
match encoded_offer.parse::<Offer>() {
Ok(_) => panic!("Valid offer: {}", encoded_offer),
- Err(e) => assert_eq!(e, ParseError::InvalidBech32Hrp),
+ Err(e) => assert_eq!(e, Bolt12ParseError::InvalidBech32Hrp),
}
}
let encoded_offer = "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxo";
match encoded_offer.parse::<Offer>() {
Ok(_) => panic!("Valid offer: {}", encoded_offer),
- Err(e) => assert_eq!(e, ParseError::Bech32(bech32::Error::InvalidChar('o'))),
+ Err(e) => assert_eq!(e, Bolt12ParseError::Bech32(bech32::Error::InvalidChar('o'))),
}
}
let encoded_offer = "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxgqqqqq";
match encoded_offer.parse::<Offer>() {
Ok(_) => panic!("Valid offer: {}", encoded_offer),
- Err(e) => assert_eq!(e, ParseError::Decode(DecodeError::InvalidValue)),
+ Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::InvalidValue)),
}
}
}
use bitcoin::bech32::{FromBase32, ToBase32};
use core::convert::TryFrom;
use core::fmt;
- use super::ParseError;
+ use super::Bolt12ParseError;
use crate::prelude::*;
/// Indicates a message can be encoded using bech32.
- pub trait Bech32Encode: AsRef<[u8]> + TryFrom<Vec<u8>, Error=ParseError> {
+ pub trait Bech32Encode: AsRef<[u8]> + TryFrom<Vec<u8>, Error=Bolt12ParseError> {
/// Human readable part of the message's bech32 encoding.
const BECH32_HRP: &'static str;
/// Parses a bech32-encoded message into a TLV stream.
- fn from_bech32_str(s: &str) -> Result<Self, ParseError> {
+ fn from_bech32_str(s: &str) -> Result<Self, Bolt12ParseError> {
// Offer encoding may be split by '+' followed by optional whitespace.
let encoded = match s.split('+').skip(1).next() {
Some(_) => {
for chunk in s.split('+') {
let chunk = chunk.trim_start();
if chunk.is_empty() || chunk.contains(char::is_whitespace) {
- return Err(ParseError::InvalidContinuation);
+ return Err(Bolt12ParseError::InvalidContinuation);
}
}
let (hrp, data) = bech32::decode_without_checksum(encoded.as_ref())?;
if hrp != Self::BECH32_HRP {
- return Err(ParseError::InvalidBech32Hrp);
+ return Err(Bolt12ParseError::InvalidBech32Hrp);
}
let data = Vec::<u8>::from_base32(&data)?;
}
/// Error when parsing a bech32 encoded message using [`str::parse`].
-///
-/// This is not exported to bindings users as its name conflicts with the BOLT 11 ParseError type.
-#[derive(Debug, PartialEq)]
-pub enum ParseError {
+#[derive(Clone, Debug, PartialEq)]
+pub enum Bolt12ParseError {
/// The bech32 encoding does not conform to the BOLT 12 requirements for continuing messages
/// across multiple parts (i.e., '+' followed by whitespace).
InvalidContinuation,
/// The bech32 decoded string could not be decoded as the expected message type.
Decode(DecodeError),
/// The parsed message has invalid semantics.
- InvalidSemantics(SemanticError),
+ InvalidSemantics(Bolt12SemanticError),
/// The parsed message has an invalid signature.
InvalidSignature(secp256k1::Error),
}
/// Error when interpreting a TLV stream as a specific type.
-///
-/// This is not exported to bindings users as its name conflicts with the BOLT 11 SemanticError type.
-#[derive(Debug, PartialEq)]
-pub enum SemanticError {
+#[derive(Clone, Debug, PartialEq)]
+pub enum Bolt12SemanticError {
/// The current [`std::time::SystemTime`] is past the offer or invoice's expiration.
AlreadyExpired,
/// The provided chain hash does not correspond to a supported chain.
MissingSignature,
}
-impl From<bech32::Error> for ParseError {
+impl From<bech32::Error> for Bolt12ParseError {
fn from(error: bech32::Error) -> Self {
Self::Bech32(error)
}
}
-impl From<DecodeError> for ParseError {
+impl From<DecodeError> for Bolt12ParseError {
fn from(error: DecodeError) -> Self {
Self::Decode(error)
}
}
-impl From<SemanticError> for ParseError {
- fn from(error: SemanticError) -> Self {
+impl From<Bolt12SemanticError> for Bolt12ParseError {
+ fn from(error: Bolt12SemanticError) -> Self {
Self::InvalidSemantics(error)
}
}
-impl From<secp256k1::Error> for ParseError {
+impl From<secp256k1::Error> for Bolt12ParseError {
fn from(error: secp256k1::Error) -> Self {
Self::InvalidSignature(error)
}
#[cfg_attr(test, derive(PartialEq))]
pub(super) struct PayerContents(pub Metadata);
-/// TLV record type for [`InvoiceRequest::metadata`] and [`Refund::metadata`].
+/// TLV record type for [`InvoiceRequest::payer_metadata`] and [`Refund::payer_metadata`].
///
-/// [`InvoiceRequest::metadata`]: crate::offers::invoice_request::InvoiceRequest::metadata
-/// [`Refund::metadata`]: crate::offers::refund::Refund::metadata
+/// [`InvoiceRequest::payer_metadata`]: crate::offers::invoice_request::InvoiceRequest::payer_metadata
+/// [`Refund::payer_metadata`]: crate::offers::refund::Refund::payer_metadata
pub(super) const PAYER_METADATA_TYPE: u64 = 0;
tlv_stream!(PayerTlvStream, PayerTlvStreamRef, 0..1, {
//! Data structures and encoding for refunds.
//!
//! A [`Refund`] is an "offer for money" and is typically constructed by a merchant and presented
-//! directly to the customer. The recipient responds with an [`Invoice`] to be paid.
+//! directly to the customer. The recipient responds with a [`Bolt12Invoice`] to be paid.
//!
//! This is an [`InvoiceRequest`] produced *not* in response to an [`Offer`].
//!
-//! [`Invoice`]: crate::offers::invoice::Invoice
+//! [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
//! [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
//! [`Offer`]: crate::offers::offer::Offer
//!
//!
//! use bitcoin::network::constants::Network;
//! use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey};
-//! use lightning::offers::parse::ParseError;
+//! use lightning::offers::parse::Bolt12ParseError;
//! use lightning::offers::refund::{Refund, RefundBuilder};
//! use lightning::util::ser::{Readable, Writeable};
//!
//! # fn create_another_blinded_path() -> BlindedPath { unimplemented!() }
//! #
//! # #[cfg(feature = "std")]
-//! # fn build() -> Result<(), ParseError> {
+//! # fn build() -> Result<(), Bolt12ParseError> {
//! let secp_ctx = Secp256k1::new();
//! let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
//! let pubkey = PublicKey::from(keys);
use crate::io;
use crate::blinded_path::BlindedPath;
use crate::ln::PaymentHash;
+use crate::ln::channelmanager::PaymentId;
use crate::ln::features::InvoiceRequestFeatures;
use crate::ln::inbound_payment::{ExpandedKey, IV_LEN, Nonce};
use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
use crate::offers::invoice::{BlindedPayInfo, DerivedSigningPubkey, ExplicitSigningPubkey, InvoiceBuilder};
use crate::offers::invoice_request::{InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef};
use crate::offers::offer::{OfferTlvStream, OfferTlvStreamRef};
-use crate::offers::parse::{Bech32Encode, ParseError, ParsedMessage, SemanticError};
+use crate::offers::parse::{Bech32Encode, Bolt12ParseError, Bolt12SemanticError, ParsedMessage};
use crate::offers::payer::{PayerContents, PayerTlvStream, PayerTlvStreamRef};
use crate::offers::signer::{Metadata, MetadataMaterial, self};
use crate::util::ser::{SeekReadable, WithoutLength, Writeable, Writer};
/// Creates a new builder for a refund using the [`Refund::payer_id`] for the public node id to
/// send to if no [`Refund::paths`] are set. Otherwise, it may be a transient pubkey.
///
- /// Additionally, sets the required [`Refund::description`], [`Refund::metadata`], and
+ /// Additionally, sets the required [`Refund::description`], [`Refund::payer_metadata`], and
/// [`Refund::amount_msats`].
pub fn new(
description: String, metadata: Vec<u8>, payer_id: PublicKey, amount_msats: u64
- ) -> Result<Self, SemanticError> {
+ ) -> Result<Self, Bolt12SemanticError> {
if amount_msats > MAX_VALUE_MSAT {
- return Err(SemanticError::InvalidAmount);
+ return Err(Bolt12SemanticError::InvalidAmount);
}
let metadata = Metadata::Bytes(metadata);
/// Also, sets the metadata when [`RefundBuilder::build`] is called such that it can be used to
/// verify that an [`InvoiceRequest`] was produced for the refund given an [`ExpandedKey`].
///
+ /// The `payment_id` is encrypted in the metadata and should be unique. This ensures that only
+ /// one invoice will be paid for the refund and that payments can be uniquely identified.
+ ///
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
/// [`ExpandedKey`]: crate::ln::inbound_payment::ExpandedKey
pub fn deriving_payer_id<ES: Deref>(
description: String, node_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES,
- secp_ctx: &'a Secp256k1<T>, amount_msats: u64
- ) -> Result<Self, SemanticError> where ES::Target: EntropySource {
+ secp_ctx: &'a Secp256k1<T>, amount_msats: u64, payment_id: PaymentId
+ ) -> Result<Self, Bolt12SemanticError> where ES::Target: EntropySource {
if amount_msats > MAX_VALUE_MSAT {
- return Err(SemanticError::InvalidAmount);
+ return Err(Bolt12SemanticError::InvalidAmount);
}
let nonce = Nonce::from_entropy_source(entropy_source);
- let derivation_material = MetadataMaterial::new(nonce, expanded_key, IV_BYTES);
+ let payment_id = Some(payment_id);
+ let derivation_material = MetadataMaterial::new(nonce, expanded_key, IV_BYTES, payment_id);
let metadata = Metadata::DerivedSigningPubkey(derivation_material);
Ok(Self {
refund: RefundContents {
}
/// Sets [`Refund::quantity`] of items. This is purely for informational purposes. It is useful
- /// when the refund pertains to an [`Invoice`] that paid for more than one item from an
+ /// when the refund pertains to a [`Bolt12Invoice`] that paid for more than one item from an
/// [`Offer`] as specified by [`InvoiceRequest::quantity`].
///
/// Successive calls to this method will override the previous setting.
///
- /// [`Invoice`]: crate::offers::invoice::Invoice
+ /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
/// [`InvoiceRequest::quantity`]: crate::offers::invoice_request::InvoiceRequest::quantity
/// [`Offer`]: crate::offers::offer::Offer
pub fn quantity(mut self, quantity: u64) -> Self {
}
/// Builds a [`Refund`] after checking for valid semantics.
- pub fn build(mut self) -> Result<Refund, SemanticError> {
+ pub fn build(mut self) -> Result<Refund, Bolt12SemanticError> {
if self.refund.chain() == self.refund.implied_chain() {
self.refund.chain = None;
}
- // Create the metadata for stateless verification of an Invoice.
+ // Create the metadata for stateless verification of a Bolt12Invoice.
if self.refund.payer.0.has_derivation_material() {
let mut metadata = core::mem::take(&mut self.refund.payer.0);
let mut tlv_stream = self.refund.as_tlv_stream();
tlv_stream.0.metadata = None;
- if metadata.derives_keys() {
+ if metadata.derives_payer_keys() {
tlv_stream.2.payer_id = None;
}
}
}
-/// A `Refund` is a request to send an [`Invoice`] without a preceding [`Offer`].
+/// A `Refund` is a request to send an [`Bolt12Invoice`] without a preceding [`Offer`].
///
/// Typically, after an invoice is paid, the recipient may publish a refund allowing the sender to
/// recoup their funds. A refund may be used more generally as an "offer for money", such as with a
/// bitcoin ATM.
///
-/// [`Invoice`]: crate::offers::invoice::Invoice
+/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
/// [`Offer`]: crate::offers::offer::Offer
#[derive(Clone, Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub(super) contents: RefundContents,
}
-/// The contents of a [`Refund`], which may be shared with an [`Invoice`].
+/// The contents of a [`Refund`], which may be shared with an [`Bolt12Invoice`].
///
-/// [`Invoice`]: crate::offers::invoice::Invoice
+/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
#[derive(Clone, Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub(super) struct RefundContents {
///
/// If `None`, the refund does not expire.
pub fn absolute_expiry(&self) -> Option<Duration> {
- self.contents.absolute_expiry
+ self.contents.absolute_expiry()
}
/// Whether the refund has expired.
/// 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> {
- self.contents.issuer.as_ref().map(|issuer| PrintableString(issuer.as_str()))
+ self.contents.issuer()
}
/// Paths to the sender originating from publicly reachable nodes. Blinded paths provide sender
/// privacy by obfuscating its node id.
pub fn paths(&self) -> &[BlindedPath] {
- self.contents.paths.as_ref().map(|paths| paths.as_slice()).unwrap_or(&[])
+ self.contents.paths()
}
/// An unpredictable series of bytes, typically containing information about the derivation of
/// [`payer_id`].
///
/// [`payer_id`]: Self::payer_id
- pub fn metadata(&self) -> &[u8] {
+ pub fn payer_metadata(&self) -> &[u8] {
self.contents.metadata()
}
/// A chain that the refund is valid for.
pub fn chain(&self) -> ChainHash {
- self.contents.chain.unwrap_or_else(|| self.contents.implied_chain())
+ self.contents.chain()
}
/// The amount to refund in msats (i.e., the minimum lightning-payable unit for [`chain`]).
///
/// [`chain`]: Self::chain
pub fn amount_msats(&self) -> u64 {
- self.contents.amount_msats
+ self.contents.amount_msats()
}
/// Features pertaining to requesting an invoice.
pub fn features(&self) -> &InvoiceRequestFeatures {
- &self.contents.features
+ &self.contents.features()
}
/// The quantity of an item that refund is for.
pub fn quantity(&self) -> Option<u64> {
- self.contents.quantity
+ self.contents.quantity()
}
/// A public node id to send to in the case where there are no [`paths`]. Otherwise, a possibly
///
/// [`paths`]: Self::paths
pub fn payer_id(&self) -> PublicKey {
- self.contents.payer_id
+ self.contents.payer_id()
}
/// Payer provided note to include in the invoice.
pub fn payer_note(&self) -> Option<PrintableString> {
- self.contents.payer_note.as_ref().map(|payer_note| PrintableString(payer_note.as_str()))
+ self.contents.payer_note()
}
/// Creates an [`InvoiceBuilder`] for the refund with the given required fields and using the
pub fn respond_with(
&self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
signing_pubkey: PublicKey,
- ) -> Result<InvoiceBuilder<ExplicitSigningPubkey>, SemanticError> {
+ ) -> Result<InvoiceBuilder<ExplicitSigningPubkey>, Bolt12SemanticError> {
let created_at = std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH");
/// Creates an [`InvoiceBuilder`] for the refund with the given required fields.
///
/// Unless [`InvoiceBuilder::relative_expiry`] is set, the invoice will expire two hours after
- /// `created_at`, which is used to set [`Invoice::created_at`]. Useful for `no-std` builds where
- /// [`std::time::SystemTime`] is not available.
+ /// `created_at`, which is used to set [`Bolt12Invoice::created_at`]. Useful for `no-std` builds
+ /// where [`std::time::SystemTime`] is not available.
///
/// The caller is expected to remember the preimage of `payment_hash` in order to
/// claim a payment for the invoice.
///
/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
///
- /// [`Invoice::created_at`]: crate::offers::invoice::Invoice::created_at
+ /// [`Bolt12Invoice::created_at`]: crate::offers::invoice::Bolt12Invoice::created_at
pub fn respond_with_no_std(
&self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
signing_pubkey: PublicKey, created_at: Duration
- ) -> Result<InvoiceBuilder<ExplicitSigningPubkey>, SemanticError> {
+ ) -> Result<InvoiceBuilder<ExplicitSigningPubkey>, Bolt12SemanticError> {
if self.features().requires_unknown_bits() {
- return Err(SemanticError::UnknownRequiredFeatures);
+ return Err(Bolt12SemanticError::UnknownRequiredFeatures);
}
InvoiceBuilder::for_refund(self, payment_paths, created_at, payment_hash, signing_pubkey)
}
/// Creates an [`InvoiceBuilder`] for the refund using the given required fields and that uses
- /// derived signing keys to sign the [`Invoice`].
+ /// derived signing keys to sign the [`Bolt12Invoice`].
///
/// See [`Refund::respond_with`] for further details.
///
/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
///
- /// [`Invoice`]: crate::offers::invoice::Invoice
+ /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
#[cfg(feature = "std")]
pub fn respond_using_derived_keys<ES: Deref>(
&self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
expanded_key: &ExpandedKey, entropy_source: ES
- ) -> Result<InvoiceBuilder<DerivedSigningPubkey>, SemanticError>
+ ) -> Result<InvoiceBuilder<DerivedSigningPubkey>, Bolt12SemanticError>
where
ES::Target: EntropySource,
{
}
/// Creates an [`InvoiceBuilder`] for the refund using the given required fields and that uses
- /// derived signing keys to sign the [`Invoice`].
+ /// derived signing keys to sign the [`Bolt12Invoice`].
///
/// See [`Refund::respond_with_no_std`] for further details.
///
/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
///
- /// [`Invoice`]: crate::offers::invoice::Invoice
+ /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
pub fn respond_using_derived_keys_no_std<ES: Deref>(
&self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
created_at: core::time::Duration, expanded_key: &ExpandedKey, entropy_source: ES
- ) -> Result<InvoiceBuilder<DerivedSigningPubkey>, SemanticError>
+ ) -> Result<InvoiceBuilder<DerivedSigningPubkey>, Bolt12SemanticError>
where
ES::Target: EntropySource,
{
if self.features().requires_unknown_bits() {
- return Err(SemanticError::UnknownRequiredFeatures);
+ return Err(Bolt12SemanticError::UnknownRequiredFeatures);
}
let nonce = Nonce::from_entropy_source(entropy_source);
PrintableString(&self.description)
}
+ pub fn absolute_expiry(&self) -> Option<Duration> {
+ self.absolute_expiry
+ }
+
#[cfg(feature = "std")]
pub(super) fn is_expired(&self) -> bool {
match self.absolute_expiry {
}
}
+ pub fn issuer(&self) -> Option<PrintableString> {
+ self.issuer.as_ref().map(|issuer| PrintableString(issuer.as_str()))
+ }
+
+ pub fn paths(&self) -> &[BlindedPath] {
+ self.paths.as_ref().map(|paths| paths.as_slice()).unwrap_or(&[])
+ }
+
pub(super) fn metadata(&self) -> &[u8] {
self.payer.0.as_bytes().map(|bytes| bytes.as_slice()).unwrap_or(&[])
}
ChainHash::using_genesis_block(Network::Bitcoin)
}
- pub(super) fn derives_keys(&self) -> bool {
- self.payer.0.derives_keys()
+ pub fn amount_msats(&self) -> u64 {
+ self.amount_msats
+ }
+
+ /// Features pertaining to requesting an invoice.
+ pub fn features(&self) -> &InvoiceRequestFeatures {
+ &self.features
+ }
+
+ /// The quantity of an item that refund is for.
+ pub fn quantity(&self) -> Option<u64> {
+ self.quantity
}
- pub(super) fn payer_id(&self) -> PublicKey {
+ /// A public node id to send to in the case where there are no [`paths`]. Otherwise, a possibly
+ /// transient pubkey.
+ ///
+ /// [`paths`]: Self::paths
+ pub fn payer_id(&self) -> PublicKey {
self.payer_id
}
+ /// Payer provided note to include in the invoice.
+ pub fn payer_note(&self) -> Option<PrintableString> {
+ self.payer_note.as_ref().map(|payer_note| PrintableString(payer_note.as_str()))
+ }
+
+ pub(super) fn derives_keys(&self) -> bool {
+ self.payer.0.derives_payer_keys()
+ }
+
pub(super) fn as_tlv_stream(&self) -> RefundTlvStreamRef {
let payer = PayerTlvStreamRef {
metadata: self.payer.0.as_bytes(),
}
impl FromStr for Refund {
- type Err = ParseError;
+ type Err = Bolt12ParseError;
fn from_str(s: &str) -> Result<Self, <Self as FromStr>::Err> {
Refund::from_bech32_str(s)
}
impl TryFrom<Vec<u8>> for Refund {
- type Error = ParseError;
+ type Error = Bolt12ParseError;
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
let refund = ParsedMessage::<RefundTlvStream>::try_from(bytes)?;
}
impl TryFrom<RefundTlvStream> for RefundContents {
- type Error = SemanticError;
+ type Error = Bolt12SemanticError;
fn try_from(tlv_stream: RefundTlvStream) -> Result<Self, Self::Error> {
let (
) = tlv_stream;
let payer = match payer_metadata {
- None => return Err(SemanticError::MissingPayerMetadata),
+ None => return Err(Bolt12SemanticError::MissingPayerMetadata),
Some(metadata) => PayerContents(Metadata::Bytes(metadata)),
};
if metadata.is_some() {
- return Err(SemanticError::UnexpectedMetadata);
+ return Err(Bolt12SemanticError::UnexpectedMetadata);
}
if chains.is_some() {
- return Err(SemanticError::UnexpectedChain);
+ return Err(Bolt12SemanticError::UnexpectedChain);
}
if currency.is_some() || offer_amount.is_some() {
- return Err(SemanticError::UnexpectedAmount);
+ return Err(Bolt12SemanticError::UnexpectedAmount);
}
let description = match description {
- None => return Err(SemanticError::MissingDescription),
+ None => return Err(Bolt12SemanticError::MissingDescription),
Some(description) => description,
};
if offer_features.is_some() {
- return Err(SemanticError::UnexpectedFeatures);
+ return Err(Bolt12SemanticError::UnexpectedFeatures);
}
let absolute_expiry = absolute_expiry.map(Duration::from_secs);
if quantity_max.is_some() {
- return Err(SemanticError::UnexpectedQuantity);
+ return Err(Bolt12SemanticError::UnexpectedQuantity);
}
if node_id.is_some() {
- return Err(SemanticError::UnexpectedSigningPubkey);
+ return Err(Bolt12SemanticError::UnexpectedSigningPubkey);
}
let amount_msats = match amount {
- None => return Err(SemanticError::MissingAmount),
+ None => return Err(Bolt12SemanticError::MissingAmount),
Some(amount_msats) if amount_msats > MAX_VALUE_MSAT => {
- return Err(SemanticError::InvalidAmount);
+ return Err(Bolt12SemanticError::InvalidAmount);
},
Some(amount_msats) => amount_msats,
};
let features = features.unwrap_or_else(InvoiceRequestFeatures::empty);
let payer_id = match payer_id {
- None => return Err(SemanticError::MissingPayerId),
+ None => return Err(Bolt12SemanticError::MissingPayerId),
Some(payer_id) => payer_id,
};
use core::time::Duration;
use crate::blinded_path::{BlindedHop, BlindedPath};
use crate::sign::KeyMaterial;
+ use crate::ln::channelmanager::PaymentId;
use crate::ln::features::{InvoiceRequestFeatures, OfferFeatures};
use crate::ln::inbound_payment::ExpandedKey;
use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
use crate::offers::invoice_request::InvoiceRequestTlvStreamRef;
use crate::offers::offer::OfferTlvStreamRef;
- use crate::offers::parse::{ParseError, SemanticError};
+ use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError};
use crate::offers::payer::PayerTlvStreamRef;
use crate::offers::test_utils::*;
use crate::util::ser::{BigSize, Writeable};
refund.write(&mut buffer).unwrap();
assert_eq!(refund.bytes, buffer.as_slice());
- assert_eq!(refund.metadata(), &[1; 32]);
+ assert_eq!(refund.payer_metadata(), &[1; 32]);
assert_eq!(refund.description(), PrintableString("foo"));
assert_eq!(refund.absolute_expiry(), None);
#[cfg(feature = "std")]
fn fails_building_refund_with_invalid_amount() {
match RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), MAX_VALUE_MSAT + 1) {
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, SemanticError::InvalidAmount),
+ Err(e) => assert_eq!(e, Bolt12SemanticError::InvalidAmount),
}
}
let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32]));
let entropy = FixedEntropy {};
let secp_ctx = Secp256k1::new();
+ let payment_id = PaymentId([1; 32]);
let refund = RefundBuilder
- ::deriving_payer_id(desc, node_id, &expanded_key, &entropy, &secp_ctx, 1000)
+ ::deriving_payer_id(desc, node_id, &expanded_key, &entropy, &secp_ctx, 1000, payment_id)
.unwrap()
.build().unwrap();
assert_eq!(refund.payer_id(), node_id);
.unwrap()
.build().unwrap()
.sign(recipient_sign).unwrap();
- assert!(invoice.verify(&expanded_key, &secp_ctx));
+ match invoice.verify(&expanded_key, &secp_ctx) {
+ Ok(payment_id) => assert_eq!(payment_id, PaymentId([1; 32])),
+ Err(()) => panic!("verification failed"),
+ }
let mut tlv_stream = refund.as_tlv_stream();
tlv_stream.2.amount = Some(2000);
.unwrap()
.build().unwrap()
.sign(recipient_sign).unwrap();
- assert!(!invoice.verify(&expanded_key, &secp_ctx));
+ assert!(invoice.verify(&expanded_key, &secp_ctx).is_err());
// Fails verification with altered metadata
let mut tlv_stream = refund.as_tlv_stream();
.unwrap()
.build().unwrap()
.sign(recipient_sign).unwrap();
- assert!(!invoice.verify(&expanded_key, &secp_ctx));
+ assert!(invoice.verify(&expanded_key, &secp_ctx).is_err());
}
#[test]
let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32]));
let entropy = FixedEntropy {};
let secp_ctx = Secp256k1::new();
+ let payment_id = PaymentId([1; 32]);
let blinded_path = BlindedPath {
introduction_node_id: pubkey(40),
};
let refund = RefundBuilder
- ::deriving_payer_id(desc, node_id, &expanded_key, &entropy, &secp_ctx, 1000)
+ ::deriving_payer_id(desc, node_id, &expanded_key, &entropy, &secp_ctx, 1000, payment_id)
.unwrap()
.path(blinded_path)
.build().unwrap();
.unwrap()
.build().unwrap()
.sign(recipient_sign).unwrap();
- assert!(invoice.verify(&expanded_key, &secp_ctx));
+ match invoice.verify(&expanded_key, &secp_ctx) {
+ Ok(payment_id) => assert_eq!(payment_id, PaymentId([1; 32])),
+ Err(()) => panic!("verification failed"),
+ }
// Fails verification with altered fields
let mut tlv_stream = refund.as_tlv_stream();
.unwrap()
.build().unwrap()
.sign(recipient_sign).unwrap();
- assert!(!invoice.verify(&expanded_key, &secp_ctx));
+ assert!(invoice.verify(&expanded_key, &secp_ctx).is_err());
// Fails verification with altered payer_id
let mut tlv_stream = refund.as_tlv_stream();
.unwrap()
.build().unwrap()
.sign(recipient_sign).unwrap();
- assert!(!invoice.verify(&expanded_key, &secp_ctx));
+ assert!(invoice.verify(&expanded_key, &secp_ctx).is_err());
}
#[test]
.respond_with_no_std(payment_paths(), payment_hash(), recipient_pubkey(), now())
{
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, SemanticError::UnknownRequiredFeatures),
+ Err(e) => assert_eq!(e, Bolt12SemanticError::UnknownRequiredFeatures),
}
}
match Refund::try_from(tlv_stream.to_bytes()) {
Ok(_) => panic!("expected error"),
Err(e) => {
- assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingPayerMetadata));
+ assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingPayerMetadata));
},
}
}
match Refund::try_from(tlv_stream.to_bytes()) {
Ok(_) => panic!("expected error"),
Err(e) => {
- assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingDescription));
+ assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingDescription));
},
}
}
match Refund::try_from(tlv_stream.to_bytes()) {
Ok(_) => panic!("expected error"),
Err(e) => {
- assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingAmount));
+ assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingAmount));
},
}
match Refund::try_from(tlv_stream.to_bytes()) {
Ok(_) => panic!("expected error"),
Err(e) => {
- assert_eq!(e, ParseError::InvalidSemantics(SemanticError::InvalidAmount));
+ assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::InvalidAmount));
},
}
}
match Refund::try_from(tlv_stream.to_bytes()) {
Ok(_) => panic!("expected error"),
Err(e) => {
- assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingPayerId));
+ assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingPayerId));
},
}
}
match Refund::try_from(tlv_stream.to_bytes()) {
Ok(_) => panic!("expected error"),
Err(e) => {
- assert_eq!(e, ParseError::InvalidSemantics(SemanticError::UnexpectedMetadata));
+ assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::UnexpectedMetadata));
},
}
match Refund::try_from(tlv_stream.to_bytes()) {
Ok(_) => panic!("expected error"),
Err(e) => {
- assert_eq!(e, ParseError::InvalidSemantics(SemanticError::UnexpectedChain));
+ assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::UnexpectedChain));
},
}
match Refund::try_from(tlv_stream.to_bytes()) {
Ok(_) => panic!("expected error"),
Err(e) => {
- assert_eq!(e, ParseError::InvalidSemantics(SemanticError::UnexpectedAmount));
+ assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::UnexpectedAmount));
},
}
match Refund::try_from(tlv_stream.to_bytes()) {
Ok(_) => panic!("expected error"),
Err(e) => {
- assert_eq!(e, ParseError::InvalidSemantics(SemanticError::UnexpectedFeatures));
+ assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::UnexpectedFeatures));
},
}
match Refund::try_from(tlv_stream.to_bytes()) {
Ok(_) => panic!("expected error"),
Err(e) => {
- assert_eq!(e, ParseError::InvalidSemantics(SemanticError::UnexpectedQuantity));
+ assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::UnexpectedQuantity));
},
}
match Refund::try_from(tlv_stream.to_bytes()) {
Ok(_) => panic!("expected error"),
Err(e) => {
- assert_eq!(e, ParseError::InvalidSemantics(SemanticError::UnexpectedSigningPubkey));
+ assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::UnexpectedSigningPubkey));
},
}
}
match Refund::try_from(encoded_refund) {
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, ParseError::Decode(DecodeError::InvalidValue)),
+ Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::InvalidValue)),
}
}
}
use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey, self};
use core::convert::TryFrom;
use core::fmt;
+use crate::ln::channelmanager::PaymentId;
use crate::ln::inbound_payment::{ExpandedKey, IV_LEN, Nonce};
use crate::offers::merkle::TlvRecord;
use crate::util::ser::Writeable;
use crate::prelude::*;
+// Use a different HMAC input for each derivation. Otherwise, an attacker could:
+// - take an Offer that has metadata consisting of a nonce and HMAC
+// - strip off the HMAC and replace the signing_pubkey where the privkey is the HMAC,
+// - generate and sign an invoice using the new signing_pubkey, and
+// - claim they paid it since they would know the preimage of the invoice's payment_hash
const DERIVED_METADATA_HMAC_INPUT: &[u8; 16] = &[1; 16];
const DERIVED_METADATA_AND_KEYS_HMAC_INPUT: &[u8; 16] = &[2; 16];
+// Additional HMAC inputs to distinguish use cases, either Offer or Refund/InvoiceRequest, where
+// metadata for the latter contain an encrypted PaymentId.
+const WITHOUT_ENCRYPTED_PAYMENT_ID_HMAC_INPUT: &[u8; 16] = &[3; 16];
+const WITH_ENCRYPTED_PAYMENT_ID_HMAC_INPUT: &[u8; 16] = &[4; 16];
+
/// Message metadata which possibly is derived from [`MetadataMaterial`] such that it can be
/// verified.
#[derive(Clone)]
}
}
- pub fn derives_keys(&self) -> bool {
+ pub fn derives_payer_keys(&self) -> bool {
+ match self {
+ // Infer whether Metadata::derived_from was called on Metadata::DerivedSigningPubkey to
+ // produce Metadata::Bytes. This is merely to determine which fields should be included
+ // when verifying a message. It doesn't necessarily indicate that keys were in fact
+ // derived, as wouldn't be the case if a Metadata::Bytes with length PaymentId::LENGTH +
+ // Nonce::LENGTH had been set explicitly.
+ Metadata::Bytes(bytes) => bytes.len() == PaymentId::LENGTH + Nonce::LENGTH,
+ Metadata::Derived(_) => false,
+ Metadata::DerivedSigningPubkey(_) => true,
+ }
+ }
+
+ pub fn derives_recipient_keys(&self) -> bool {
match self {
// Infer whether Metadata::derived_from was called on Metadata::DerivedSigningPubkey to
// produce Metadata::Bytes. This is merely to determine which fields should be included
pub(super) struct MetadataMaterial {
nonce: Nonce,
hmac: HmacEngine<Sha256>,
+ // Some for payer metadata and None for offer metadata
+ encrypted_payment_id: Option<[u8; PaymentId::LENGTH]>,
}
impl MetadataMaterial {
- pub fn new(nonce: Nonce, expanded_key: &ExpandedKey, iv_bytes: &[u8; IV_LEN]) -> Self {
+ pub fn new(
+ nonce: Nonce, expanded_key: &ExpandedKey, iv_bytes: &[u8; IV_LEN],
+ payment_id: Option<PaymentId>
+ ) -> Self {
+ // Encrypt payment_id
+ let encrypted_payment_id = payment_id.map(|payment_id| {
+ expanded_key.crypt_for_offer(payment_id.0, nonce)
+ });
+
Self {
nonce,
hmac: expanded_key.hmac_for_offer(nonce, iv_bytes),
+ encrypted_payment_id,
}
}
fn derive_metadata(mut self) -> Vec<u8> {
self.hmac.input(DERIVED_METADATA_HMAC_INPUT);
+ self.maybe_include_encrypted_payment_id();
- let mut bytes = self.nonce.as_slice().to_vec();
+ let mut bytes = self.encrypted_payment_id.map(|id| id.to_vec()).unwrap_or(vec![]);
+ bytes.extend_from_slice(self.nonce.as_slice());
bytes.extend_from_slice(&Hmac::from_engine(self.hmac).into_inner());
bytes
}
mut self, secp_ctx: &Secp256k1<T>
) -> (Vec<u8>, KeyPair) {
self.hmac.input(DERIVED_METADATA_AND_KEYS_HMAC_INPUT);
+ self.maybe_include_encrypted_payment_id();
+
+ let mut bytes = self.encrypted_payment_id.map(|id| id.to_vec()).unwrap_or(vec![]);
+ bytes.extend_from_slice(self.nonce.as_slice());
let hmac = Hmac::from_engine(self.hmac);
let privkey = SecretKey::from_slice(hmac.as_inner()).unwrap();
let keys = KeyPair::from_secret_key(secp_ctx, &privkey);
- (self.nonce.as_slice().to_vec(), keys)
+
+ (bytes, keys)
+ }
+
+ fn maybe_include_encrypted_payment_id(&mut self) {
+ match self.encrypted_payment_id {
+ None => self.hmac.input(WITHOUT_ENCRYPTED_PAYMENT_ID_HMAC_INPUT),
+ Some(encrypted_payment_id) => {
+ self.hmac.input(WITH_ENCRYPTED_PAYMENT_ID_HMAC_INPUT);
+ self.hmac.input(&encrypted_payment_id)
+ },
+ }
}
}
KeyPair::from_secret_key(&secp_ctx, &privkey)
}
+/// Verifies data given in a TLV stream was used to produce the given metadata, consisting of:
+/// - a 256-bit [`PaymentId`],
+/// - a 128-bit [`Nonce`], and possibly
+/// - a [`Sha256`] hash of the nonce and the TLV records using the [`ExpandedKey`].
+///
+/// If the latter is not included in the metadata, the TLV stream is used to check if the given
+/// `signing_pubkey` can be derived from it.
+///
+/// Returns the [`PaymentId`] that should be used for sending the payment.
+pub(super) fn verify_payer_metadata<'a, T: secp256k1::Signing>(
+ metadata: &[u8], expanded_key: &ExpandedKey, iv_bytes: &[u8; IV_LEN],
+ signing_pubkey: PublicKey, tlv_stream: impl core::iter::Iterator<Item = TlvRecord<'a>>,
+ secp_ctx: &Secp256k1<T>
+) -> Result<PaymentId, ()> {
+ if metadata.len() < PaymentId::LENGTH {
+ return Err(());
+ }
+
+ let mut encrypted_payment_id = [0u8; PaymentId::LENGTH];
+ encrypted_payment_id.copy_from_slice(&metadata[..PaymentId::LENGTH]);
+
+ let mut hmac = hmac_for_message(
+ &metadata[PaymentId::LENGTH..], expanded_key, iv_bytes, tlv_stream
+ )?;
+ hmac.input(WITH_ENCRYPTED_PAYMENT_ID_HMAC_INPUT);
+ hmac.input(&encrypted_payment_id);
+
+ verify_metadata(
+ &metadata[PaymentId::LENGTH..], Hmac::from_engine(hmac), signing_pubkey, secp_ctx
+ )?;
+
+ let nonce = Nonce::try_from(&metadata[PaymentId::LENGTH..][..Nonce::LENGTH]).unwrap();
+ let payment_id = expanded_key.crypt_for_offer(encrypted_payment_id, nonce);
+
+ Ok(PaymentId(payment_id))
+}
+
/// Verifies data given in a TLV stream was used to produce the given metadata, consisting of:
/// - a 128-bit [`Nonce`] and possibly
/// - a [`Sha256`] hash of the nonce and the TLV records using the [`ExpandedKey`].
///
/// If the latter is not included in the metadata, the TLV stream is used to check if the given
/// `signing_pubkey` can be derived from it.
-pub(super) fn verify_metadata<'a, T: secp256k1::Signing>(
+///
+/// Returns the [`KeyPair`] for signing the invoice, if it can be derived from the metadata.
+pub(super) fn verify_recipient_metadata<'a, T: secp256k1::Signing>(
metadata: &[u8], expanded_key: &ExpandedKey, iv_bytes: &[u8; IV_LEN],
signing_pubkey: PublicKey, tlv_stream: impl core::iter::Iterator<Item = TlvRecord<'a>>,
secp_ctx: &Secp256k1<T>
) -> Result<Option<KeyPair>, ()> {
- let hmac = hmac_for_message(metadata, expanded_key, iv_bytes, tlv_stream)?;
+ let mut hmac = hmac_for_message(metadata, expanded_key, iv_bytes, tlv_stream)?;
+ hmac.input(WITHOUT_ENCRYPTED_PAYMENT_ID_HMAC_INPUT);
+
+ verify_metadata(metadata, Hmac::from_engine(hmac), signing_pubkey, secp_ctx)
+}
+fn verify_metadata<T: secp256k1::Signing>(
+ metadata: &[u8], hmac: Hmac<Sha256>, signing_pubkey: PublicKey, secp_ctx: &Secp256k1<T>
+) -> Result<Option<KeyPair>, ()> {
if metadata.len() == Nonce::LENGTH {
let derived_keys = KeyPair::from_secret_key(
secp_ctx, &SecretKey::from_slice(hmac.as_inner()).unwrap()
fn hmac_for_message<'a>(
metadata: &[u8], expanded_key: &ExpandedKey, iv_bytes: &[u8; IV_LEN],
tlv_stream: impl core::iter::Iterator<Item = TlvRecord<'a>>
-) -> Result<Hmac<Sha256>, ()> {
+) -> Result<HmacEngine<Sha256>, ()> {
if metadata.len() < Nonce::LENGTH {
return Err(());
}
hmac.input(DERIVED_METADATA_HMAC_INPUT);
}
- Ok(Hmac::from_engine(hmac))
+ Ok(hmac)
}
//! Utilities for testing BOLT 12 Offers interfaces
-use bitcoin::secp256k1::{KeyPair, Message, PublicKey, Secp256k1, SecretKey};
+use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey};
use bitcoin::secp256k1::schnorr::Signature;
-use core::convert::Infallible;
+use core::convert::{AsRef, Infallible};
use core::time::Duration;
use crate::blinded_path::{BlindedHop, BlindedPath};
use crate::sign::EntropySource;
use crate::ln::PaymentHash;
use crate::ln::features::BlindedHopFeatures;
use crate::offers::invoice::BlindedPayInfo;
+use crate::offers::merkle::TaggedHash;
-pub(super) fn payer_keys() -> KeyPair {
+pub(crate) fn payer_keys() -> KeyPair {
let secp_ctx = Secp256k1::new();
KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap())
}
-pub(super) fn payer_sign(digest: &Message) -> Result<Signature, Infallible> {
+pub(crate) fn payer_sign<T: AsRef<TaggedHash>>(message: &T) -> Result<Signature, Infallible> {
let secp_ctx = Secp256k1::new();
let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
- Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys))
+ Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
}
-pub(super) fn payer_pubkey() -> PublicKey {
+pub(crate) fn payer_pubkey() -> PublicKey {
payer_keys().public_key()
}
-pub(super) fn recipient_keys() -> KeyPair {
+pub(crate) fn recipient_keys() -> KeyPair {
let secp_ctx = Secp256k1::new();
KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[43; 32]).unwrap())
}
-pub(super) fn recipient_sign(digest: &Message) -> Result<Signature, Infallible> {
+pub(crate) fn recipient_sign<T: AsRef<TaggedHash>>(message: &T) -> Result<Signature, Infallible> {
let secp_ctx = Secp256k1::new();
let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[43; 32]).unwrap());
- Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys))
+ Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
}
-pub(super) fn recipient_pubkey() -> PublicKey {
+pub(crate) fn recipient_pubkey() -> PublicKey {
recipient_keys().public_key()
}
SecretKey::from_slice(&[byte; 32]).unwrap()
}
-pub(super) fn payment_paths() -> Vec<(BlindedPayInfo, BlindedPath)> {
+pub(crate) fn payment_paths() -> Vec<(BlindedPayInfo, BlindedPath)> {
let paths = vec![
BlindedPath {
introduction_node_id: pubkey(40),
payinfo.into_iter().zip(paths.into_iter()).collect()
}
-pub(super) fn payment_hash() -> PaymentHash {
+pub(crate) fn payment_hash() -> PaymentHash {
PaymentHash([42; 32])
}
-pub(super) fn now() -> Duration {
+pub(crate) fn now() -> Duration {
std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH")
}
-pub(super) struct FixedEntropy;
+pub(crate) struct FixedEntropy;
impl EntropySource for FixedEntropy {
fn get_secure_random_bytes(&self) -> [u8; 32] {
//! Onion message testing and test utilities live here.
use crate::blinded_path::BlindedPath;
-use crate::sign::{NodeSigner, Recipient};
+use crate::events::OnionMessageProvider;
use crate::ln::features::InitFeatures;
use crate::ln::msgs::{self, DecodeError, OnionMessageHandler};
-use super::{CustomOnionMessageContents, CustomOnionMessageHandler, Destination, MessageRouter, OffersMessage, OffersMessageHandler, OnionMessageContents, OnionMessagePath, OnionMessenger, SendError};
-use crate::util::ser::{Writeable, Writer};
+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 bitcoin::network::constants::Network;
-use bitcoin::secp256k1::{PublicKey, Secp256k1};
+use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
use crate::io;
use crate::io_extras::read_to_end;
nodes[num_nodes-1].custom_message_handler.expect_message(TestCustomMessage::Response);
pass_along_path(&nodes);
}
+
+#[test]
+fn spec_test_vector() {
+ let keys_mgrs = vec![
+ (Arc::new(test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet)), // Alice
+ Arc::new(test_utils::TestNodeSigner::new(SecretKey::from_slice(&hex::decode("4141414141414141414141414141414141414141414141414141414141414141").unwrap()).unwrap()))),
+ (Arc::new(test_utils::TestKeysInterface::new(&[1; 32], Network::Testnet)), // Bob
+ Arc::new(test_utils::TestNodeSigner::new(SecretKey::from_slice(&hex::decode("4242424242424242424242424242424242424242424242424242424242424242").unwrap()).unwrap()))),
+ (Arc::new(test_utils::TestKeysInterface::new(&[2; 32], Network::Testnet)), // Carol
+ Arc::new(test_utils::TestNodeSigner::new(SecretKey::from_slice(&hex::decode("4343434343434343434343434343434343434343434343434343434343434343").unwrap()).unwrap()))),
+ (Arc::new(test_utils::TestKeysInterface::new(&[3; 32], Network::Testnet)), // Dave
+ Arc::new(test_utils::TestNodeSigner::new(SecretKey::from_slice(&hex::decode("4444444444444444444444444444444444444444444444444444444444444444").unwrap()).unwrap()))),
+ ];
+ let message_router = Arc::new(TestMessageRouter {});
+ let offers_message_handler = Arc::new(TestOffersMessageHandler {});
+ let custom_message_handler = Arc::new(TestCustomMessageHandler::new());
+ let mut nodes = Vec::new();
+ for (idx, (entropy_source, node_signer)) in keys_mgrs.iter().enumerate() {
+ let logger = Arc::new(test_utils::TestLogger::with_id(format!("node {}", idx)));
+ nodes.push(OnionMessenger::new(
+ entropy_source.clone(), node_signer.clone(), logger.clone(), message_router.clone(),
+ offers_message_handler.clone(), custom_message_handler.clone()
+ ));
+ }
+ for idx in 0..nodes.len() - 1 {
+ let i = idx as usize;
+ let mut features = InitFeatures::empty();
+ features.set_onion_messages_optional();
+ let init_msg = msgs::Init { features, networks: None, remote_network_address: None };
+ nodes[i].peer_connected(
+ &keys_mgrs[i + 1].1.get_node_id(Recipient::Node).unwrap(), &init_msg.clone(), true).unwrap();
+ nodes[i + 1].peer_connected(
+ &keys_mgrs[i].1.get_node_id(Recipient::Node).unwrap(), &init_msg.clone(), false).unwrap();
+ }
+
+ // Hardcode the sender->Alice onion message, because it includes an unknown TLV of type 1, which
+ // LDK doesn't support constructing.
+ let sender_to_alice_packet_bytes = hex::decode("0002531fe6068134503d2723133227c867ac8fa6c83c537e9a44c3c5bdbdcb1fe33793b828776d70aabbd8cef1a5b52d5a397ae1a20f20435ff6057cd8be339d5aee226660ef73b64afa45dbf2e6e8e26eb96a259b2db5aeecda1ce2e768bbc35d389d7f320ca3d2bd14e2689bef2f5ac0307eaaabc1924eb972c1563d4646ae131accd39da766257ed35ea36e4222527d1db4fa7b2000aab9eafcceed45e28b5560312d4e2299bd8d1e7fe27d10925966c28d497aec400b4630485e82efbabc00550996bdad5d6a9a8c75952f126d14ad2cff91e16198691a7ef2937de83209285f1fb90944b4e46bca7c856a9ce3da10cdf2a7d00dc2bf4f114bc4d3ed67b91cbde558ce9af86dc81fbdc37f8e301b29e23c1466659c62bdbf8cff5d4c20f0fb0851ec72f5e9385dd40fdd2e3ed67ca4517117825665e50a3e26f73c66998daf18e418e8aef9ce2d20da33c3629db2933640e03e7b44c2edf49e9b482db7b475cfd4c617ae1d46d5c24d697846f9f08561eac2b065f9b382501f6eabf07343ed6c602f61eab99cdb52adf63fd44a8db2d3016387ea708fc1c08591e19b4d9984ebe31edbd684c2ea86526dd8c7732b1d8d9117511dc1b643976d356258fce8313b1cb92682f41ab72dedd766f06de375f9edacbcd0ca8c99b865ea2b7952318ea1fd20775a28028b5cf59dece5de14f615b8df254eee63493a5111ea987224bea006d8f1b60d565eef06ac0da194dba2a6d02e79b2f2f34e9ca6e1984a507319d86e9d4fcaeea41b4b9144e0b1826304d4cc1da61cfc5f8b9850697df8adc5e9d6f3acb3219b02764b4909f2b2b22e799fd66c383414a84a7d791b899d4aa663770009eb122f90282c8cb9cda16aba6897edcf9b32951d0080c0f52be3ca011fbec3fb16423deb47744645c3b05fdbd932edf54ba6efd26e65340a8e9b1d1216582e1b30d64524f8ca2d6c5ba63a38f7120a3ed71bed8960bcac2feee2dd41c90be48e3c11ec518eb3d872779e4765a6cc28c6b0fa71ab57ced73ae963cc630edae4258cba2bf25821a6ae049fec2fca28b5dd1bb004d92924b65701b06dcf37f0ccd147a13a03f9bc0f98b7d78fe9058089756931e2cd0e0ed92ec6759d07b248069526c67e9e6ce095118fd3501ba0f858ef030b76c6f6beb11a09317b5ad25343f4b31aef02bc555951bc7791c2c289ecf94d5544dcd6ad3021ed8e8e3db34b2a73e1eedb57b578b068a5401836d6e382110b73690a94328c404af25e85a8d6b808893d1b71af6a31fadd8a8cc6e31ecc0d9ff7e6b91fd03c274a5c1f1ccd25b61150220a3fddb04c91012f5f7a83a5c90deb2470089d6e38cd5914b9c946eca6e9d31bbf8667d36cf87effc3f3ff283c21dd4137bd569fe7cf758feac94053e4baf7338bb592c8b7c291667fadf4a9bf9a2a154a18f612cbc7f851b3f8f2070e0a9d180622ee4f8e81b0ab250d504cef24116a3ff188cc829fcd8610b56343569e8dc997629410d1967ca9dd1d27eec5e01e4375aad16c46faba268524b154850d0d6fe3a76af2c6aa3e97647c51036049ac565370028d6a439a2672b6face56e1b171496c0722cfa22d9da631be359661617c5d5a2d286c5e19db9452c1e21a0107b6400debda2decb0c838f342dd017cdb2dccdf1fe97e3df3f881856b546997a3fed9e279c720145101567dd56be21688fed66bf9759e432a9aa89cbbd225d13cdea4ca05f7a45cfb6a682a3d5b1e18f7e6cf934fae5098108bae9058d05c3387a01d8d02a656d2bfff67e9f46b2d8a6aac28129e52efddf6e552214c3f8a45bc7a912cca9a7fec1d7d06412c6972cb9e3dc518983f56530b8bffe7f92c4b6eb47d4aef59fb513c4653a42de61bc17ad7728e7fc7590ff05a9e991de03f023d0aaf8688ed6170def5091c66576a424ac1cb").unwrap();
+ let sender_to_alice_packet_bytes_len = sender_to_alice_packet_bytes.len() as u64;
+ let mut reader = io::Cursor::new(sender_to_alice_packet_bytes);
+ let mut packet_reader = FixedLengthReader::new(&mut reader, sender_to_alice_packet_bytes_len);
+ let sender_to_alice_packet: super::Packet =
+ <super::Packet as LengthReadable>::read(&mut packet_reader).unwrap();
+ let secp_ctx = Secp256k1::new();
+ let sender_to_alice_om = msgs::OnionMessage {
+ blinding_point: PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&hex::decode("6363636363636363636363636363636363636363636363636363636363636363").unwrap()).unwrap()),
+ onion_routing_packet: sender_to_alice_packet,
+ };
+ // The spec test vectors prepend the OM message type (513) to the encoded onion message strings,
+ // which is why the asserted strings differ slightly from the spec.
+ assert_eq!(sender_to_alice_om.encode(), hex::decode("031195a8046dcbb8e17034bca630065e7a0982e4e36f6f7e5a8d4554e4846fcd9905560002531fe6068134503d2723133227c867ac8fa6c83c537e9a44c3c5bdbdcb1fe33793b828776d70aabbd8cef1a5b52d5a397ae1a20f20435ff6057cd8be339d5aee226660ef73b64afa45dbf2e6e8e26eb96a259b2db5aeecda1ce2e768bbc35d389d7f320ca3d2bd14e2689bef2f5ac0307eaaabc1924eb972c1563d4646ae131accd39da766257ed35ea36e4222527d1db4fa7b2000aab9eafcceed45e28b5560312d4e2299bd8d1e7fe27d10925966c28d497aec400b4630485e82efbabc00550996bdad5d6a9a8c75952f126d14ad2cff91e16198691a7ef2937de83209285f1fb90944b4e46bca7c856a9ce3da10cdf2a7d00dc2bf4f114bc4d3ed67b91cbde558ce9af86dc81fbdc37f8e301b29e23c1466659c62bdbf8cff5d4c20f0fb0851ec72f5e9385dd40fdd2e3ed67ca4517117825665e50a3e26f73c66998daf18e418e8aef9ce2d20da33c3629db2933640e03e7b44c2edf49e9b482db7b475cfd4c617ae1d46d5c24d697846f9f08561eac2b065f9b382501f6eabf07343ed6c602f61eab99cdb52adf63fd44a8db2d3016387ea708fc1c08591e19b4d9984ebe31edbd684c2ea86526dd8c7732b1d8d9117511dc1b643976d356258fce8313b1cb92682f41ab72dedd766f06de375f9edacbcd0ca8c99b865ea2b7952318ea1fd20775a28028b5cf59dece5de14f615b8df254eee63493a5111ea987224bea006d8f1b60d565eef06ac0da194dba2a6d02e79b2f2f34e9ca6e1984a507319d86e9d4fcaeea41b4b9144e0b1826304d4cc1da61cfc5f8b9850697df8adc5e9d6f3acb3219b02764b4909f2b2b22e799fd66c383414a84a7d791b899d4aa663770009eb122f90282c8cb9cda16aba6897edcf9b32951d0080c0f52be3ca011fbec3fb16423deb47744645c3b05fdbd932edf54ba6efd26e65340a8e9b1d1216582e1b30d64524f8ca2d6c5ba63a38f7120a3ed71bed8960bcac2feee2dd41c90be48e3c11ec518eb3d872779e4765a6cc28c6b0fa71ab57ced73ae963cc630edae4258cba2bf25821a6ae049fec2fca28b5dd1bb004d92924b65701b06dcf37f0ccd147a13a03f9bc0f98b7d78fe9058089756931e2cd0e0ed92ec6759d07b248069526c67e9e6ce095118fd3501ba0f858ef030b76c6f6beb11a09317b5ad25343f4b31aef02bc555951bc7791c2c289ecf94d5544dcd6ad3021ed8e8e3db34b2a73e1eedb57b578b068a5401836d6e382110b73690a94328c404af25e85a8d6b808893d1b71af6a31fadd8a8cc6e31ecc0d9ff7e6b91fd03c274a5c1f1ccd25b61150220a3fddb04c91012f5f7a83a5c90deb2470089d6e38cd5914b9c946eca6e9d31bbf8667d36cf87effc3f3ff283c21dd4137bd569fe7cf758feac94053e4baf7338bb592c8b7c291667fadf4a9bf9a2a154a18f612cbc7f851b3f8f2070e0a9d180622ee4f8e81b0ab250d504cef24116a3ff188cc829fcd8610b56343569e8dc997629410d1967ca9dd1d27eec5e01e4375aad16c46faba268524b154850d0d6fe3a76af2c6aa3e97647c51036049ac565370028d6a439a2672b6face56e1b171496c0722cfa22d9da631be359661617c5d5a2d286c5e19db9452c1e21a0107b6400debda2decb0c838f342dd017cdb2dccdf1fe97e3df3f881856b546997a3fed9e279c720145101567dd56be21688fed66bf9759e432a9aa89cbbd225d13cdea4ca05f7a45cfb6a682a3d5b1e18f7e6cf934fae5098108bae9058d05c3387a01d8d02a656d2bfff67e9f46b2d8a6aac28129e52efddf6e552214c3f8a45bc7a912cca9a7fec1d7d06412c6972cb9e3dc518983f56530b8bffe7f92c4b6eb47d4aef59fb513c4653a42de61bc17ad7728e7fc7590ff05a9e991de03f023d0aaf8688ed6170def5091c66576a424ac1cb").unwrap());
+ let sender_dummy_node_id = PublicKey::from_slice(&[2; 33]).unwrap();
+ nodes[0].handle_onion_message(&sender_dummy_node_id, &sender_to_alice_om);
+ let alice_to_bob_om = nodes[0].next_onion_message_for_peer(
+ keys_mgrs[1].1.get_node_id(Recipient::Node).unwrap()).unwrap();
+ assert_eq!(alice_to_bob_om.encode(), hex::decode("031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f05560002536d53f93796cad550b6c68662dca41f7e8c221c31022c64dd1a627b2df3982b25eac261e88369cfc66e1e3b6d9829cb3dcd707046e68a7796065202a7904811bf2608c5611cf74c9eb5371c7eb1a4428bb39a041493e2a568ddb0b2482a6cc6711bc6116cef144ebf988073cb18d9dd4ce2d3aa9de91a7dc6d7c6f11a852024626e66b41ba1158055505dff9cb15aa51099f315564d9ee3ed6349665dc3e209eedf9b5805ee4f69d315df44c80e63d0e2efbdab60ec96f44a3447c6a6ddb1efb6aa4e072bde1dab974081646bfddf3b02daa2b83847d74dd336465e76e9b8fecc2b0414045eeedfc39939088a76820177dd1103c99939e659beb07197bab9f714b30ba8dc83738e9a6553a57888aaeda156c68933a2f4ff35e3f81135076b944ed9856acbfee9c61299a5d1763eadd14bf5eaf71304c8e165e590d7ecbcd25f1650bf5b6c2ad1823b2dc9145e168974ecf6a2273c94decff76d94bc6708007a17f22262d63033c184d0166c14f41b225a956271947aae6ce65890ed8f0d09c6ffe05ec02ee8b9de69d7077a0c5adeb813aabcc1ba8975b73ab06ddea5f4db3c23a1de831602de2b83f990d4133871a1a81e53f86393e6a7c3a7b73f0c099fa72afe26c3027bb9412338a19303bd6e6591c04fb4cde9b832b5f41ae199301ea8c303b5cef3aca599454273565de40e1148156d1f97c1aa9e58459ab318304075e034f5b7899c12587b86776a18a1da96b7bcdc22864fccc4c41538ebce92a6f054d53bf46770273a70e75fe0155cd6d2f2e937465b0825ce3123b8c206fac4c30478fa0f08a97ade7216dce11626401374993213636e93545a31f500562130f2feb04089661ad8c34d5a4cbd2e4e426f37cb094c786198a220a2646ecadc38c04c29ee67b19d662c209a7b30bfecc7fe8bf7d274de0605ee5df4db490f6d32234f6af639d3fce38a2801bcf8d51e9c090a6c6932355a83848129a378095b34e71cb8f51152dc035a4fe8e802fec8de221a02ba5afd6765ce570bef912f87357936ea0b90cb2990f56035e89539ec66e8dbd6ed50835158614096990e019c3eba3d7dd6a77147641c6145e8b17552cd5cf7cd163dd40b9eaeba8c78e03a2cd8c0b7997d6f56d35f38983a202b4eb8a54e14945c4de1a6dde46167e11708b7a5ff5cb9c0f7fc12fae49a012aa90bb1995c038130b749c48e6f1ffb732e92086def42af10fbc460d94abeb7b2fa744a5e9a491d62a08452be8cf2fdef573deedc1fe97098bce889f98200b26f9bb99da9aceddda6d793d8e0e44a2601ef4590cfbb5c3d0197aac691e3d31c20fd8e38764962ca34dabeb85df28feabaf6255d4d0df3d814455186a84423182caa87f9673df770432ad8fdfe78d4888632d460d36d2719e8fa8e4b4ca10d817c5d6bc44a8b2affab8c2ba53b8bf4994d63286c2fad6be04c28661162fa1a67065ecda8ba8c13aee4a8039f4f0110e0c0da2366f178d8903e19136dad6df9d8693ce71f3a270f9941de2a93d9b67bc516207ac1687bf6e00b29723c42c7d9c90df9d5e599dbeb7b73add0a6a2b7aba82f98ac93cb6e60494040445229f983a81c34f7f686d166dfc98ec23a6318d4a02a311ac28d655ea4e0f9c3014984f31e621ef003e98c373561d9040893feece2e0fa6cd2dd565e6fbb2773a2407cb2c3273c306cf71f427f2e551c4092e067cf9869f31ac7c6c80dd52d4f85be57a891a41e34be0d564e39b4af6f46b85339254a58b205fb7e10e7d0470ee73622493f28c08962118c23a1198467e72c4ae1cd482144b419247a5895975ea90d135e2a46ef7e5794a1551a447ff0a0d299b66a7f565cd86531f5e7af5408d85d877ce95b1df12b88b7d5954903a5296325ba478ba1e1a9d1f30a2d5052b2e2889bbd64f72c72bc71d8817288a2").unwrap());
+ nodes[1].handle_onion_message(
+ &keys_mgrs[0].1.get_node_id(Recipient::Node).unwrap(), &alice_to_bob_om);
+ let bob_to_carol_om = nodes[1].next_onion_message_for_peer(
+ keys_mgrs[2].1.get_node_id(Recipient::Node).unwrap()).unwrap();
+ assert_eq!(bob_to_carol_om.encode(), hex::decode("02b684babfd400c8dd48b367e9754b8021a3594a34dc94d7101776c7f6a86d0582055600029a77e8523162efa1f4208f4f2050cd5c386ddb6ce6d36235ea569d217ec52209fb85fdf7dbc4786c373eebdba0ddc184cfbe6da624f610e93f62c70f2c56be1090b926359969f040f932c03f53974db5656233bd60af375517d4323002937d784c2c88a564bcefe5c33d3fc21c26d94dfacab85e2e19685fd2ff4c543650958524439b6da68779459aee5ffc9dc543339acec73ff43be4c44ddcbe1c11d50e2411a67056ba9db7939d780f5a86123fdd3abd6f075f7a1d78ab7daf3a82798b7ec1e9f1345bc0d1e935098497067e2ae5a51ece396fcb3bb30871ad73aee51b2418b39f00c8e8e22be4a24f4b624e09cb0414dd46239de31c7be035f71e8da4f5a94d15b44061f46414d3f355069b5c5b874ba56704eb126148a22ec873407fe118972127e63ff80e682e410f297f23841777cec0517e933eaf49d7e34bd203266b42081b3a5193b51ccd34b41342bc67cf73523b741f5c012ba2572e9dda15fbe131a6ac2ff24dc2a7622d58b9f3553092cfae7fae3c8864d95f97aa49ec8edeff5d9f5782471160ee412d82ff6767030fc63eec6a93219a108cd41433834b26676a39846a944998796c79cd1cc460531b8ded659cedfd8aecefd91944f00476f1496daafb4ea6af3feacac1390ea510709783c2aa81a29de27f8959f6284f4684102b17815667cbb0645396ac7d542b878d90c42a1f7f00c4c4eedb2a22a219f38afadb4f1f562b6e000a94e75cc38f535b43a3c0384ccef127fde254a9033a317701c710b2b881065723486e3f4d3eea5e12f374a41565fe43fa137c1a252c2153dde055bb343344c65ad0529010ece29bbd405effbebfe3ba21382b94a60ac1a5ffa03f521792a67b30773cb42e862a8a02a8bbd41b842e115969c87d1ff1f8c7b5726b9f20772dd57fe6e4ea41f959a2a673ffad8e2f2a472c4c8564f3a5a47568dd75294b1c7180c500f7392a7da231b1fe9e525ea2d7251afe9ca52a17fe54a116cb57baca4f55b9b6de915924d644cba9dade4ccc01939d7935749c008bafc6d3ad01cd72341ce5ddf7a5d7d21cf0465ab7a3233433aef21f9acf2bfcdc5a8cc003adc4d82ac9d72b36eb74e05c9aa6ccf439ac92e6b84a3191f0764dd2a2e0b4cc3baa08782b232ad6ecd3ca6029bc08cc094aef3aebddcaddc30070cb6023a689641de86cfc6341c8817215a4650f844cd2ca60f2f10c6e44cfc5f23912684d4457bf4f599879d30b79bf12ef1ab8d34dddc15672b82e56169d4c770f0a2a7a960b1e8790773f5ff7fce92219808f16d061cc85e053971213676d28fb48925e9232b66533dbd938458eb2cc8358159df7a2a2e4cf87500ede2afb8ce963a845b98978edf26a6948d4932a6b95d022004556d25515fe158092ce9a913b4b4a493281393ca731e8d8e5a3449b9d888fc4e73ffcbb9c6d6d66e88e03cf6e81a0496ede6e4e4172b08c000601993af38f80c7f68c9d5fff9e0e215cff088285bf039ca731744efcb7825a272ca724517736b4890f47e306b200aa2543c363e2c9090bcf3cf56b5b86868a62471c7123a41740392fc1d5ab28da18dca66618e9af7b42b62b23aba907779e73ca03ec60e6ab9e0484b9cae6578e0fddb6386cb3468506bf6420298bf4a690947ab582255551d82487f271101c72e19e54872ab47eae144db66bc2f8194a666a5daec08d12822cb83a61946234f2dfdbd6ca7d8763e6818adee7b401fcdb1ac42f9df1ac5cc5ac131f2869013c8d6cd29d4c4e3d05bccd34ca83366d616296acf854fa05149bfd763a25b9938e96826a037fdcb85545439c76df6beed3bdbd01458f9cf984997cc4f0a7ac3cc3f5e1eeb59c09cadcf5a537f16e444149c8f17d4bdaef16c9fbabc5ef06eb0f0bf3a07a1beddfeacdaf1df5582d6dbd6bb808d6ab31bc22e5d7").unwrap());
+ nodes[2].handle_onion_message(
+ &keys_mgrs[1].1.get_node_id(Recipient::Node).unwrap(), &bob_to_carol_om);
+ let carol_to_dave_om = nodes[2].next_onion_message_for_peer(
+ keys_mgrs[3].1.get_node_id(Recipient::Node).unwrap()).unwrap();
+ assert_eq!(carol_to_dave_om.encode(), hex::decode("025aaca62db7ce6b46386206ef9930daa32e979a35cb185a41cb951aa7d254b03c055600025550b2910294fa73bda99b9de9c851be9cbb481e23194a1743033630efba546b86e7d838d0f6e9cc0ed088dbf6889f0dceca3bfc745bd77d013a31311fa932a8bf1d28387d9ff521eabc651dee8f861fed609a68551145a451f017ec44978addeee97a423c08445531da488fd1ddc998e9cdbfcea59517b53fbf1833f0bbe6188dba6ca773a247220ec934010daca9cc185e1ceb136803469baac799e27a0d82abe53dc48a06a55d1f643885cc7894677dd20a4e4152577d1ba74b870b9279f065f9b340cedb3ca13b7df218e853e10ccd1b59c42a2acf93f489e170ee4373d30ab158b60fc20d3ba73a1f8c750951d69fb5b9321b968ddc8114936412346aff802df65516e1c09c51ef19849ff36c0199fd88c8bec301a30fef0c7cb497901c038611303f64e4174b5daf42832aa5586b84d2c9b95f382f4269a5d1bd4be898618dc78dfd451170f72ca16decac5b03e60702112e439cadd104fb3bbb3d5023c9b80823fdcd0a212a7e1aaa6eeb027adc7f8b3723031d135a09a979a4802788bb7861c6cc85501fb91137768b70aeab309b27b885686604ffc387004ac4f8c44b101c39bc0597ef7fd957f53fc5051f534b10eb3852100962b5e58254e5558689913c26ad6072ea41f5c5db10077cfc91101d4ae393be274c74297da5cc381cd88d54753aaa7df74b2f9da8d88a72bc9218fcd1f19e4ff4aace182312b9509c5175b6988f044c5756d232af02a451a02ca752f3c52747773acff6fd07d2032e6ce562a2c42105d106eba02d0b1904182cdc8c74875b082d4989d3a7e9f0e73de7c75d357f4af976c28c0b206c5e8123fc2391d078592d0d5ff686fd245c0a2de2e535b7cca99c0a37d432a8657393a9e3ca53eec1692159046ba52cb9bc97107349d8673f74cbc97e231f1108005c8d03e24ca813cea2294b39a7a493bcc062708f1f6cf0074e387e7d50e0666ce784ef4d31cb860f6cad767438d9ea5156ff0ae86e029e0247bf94df75ee0cda4f2006061455cb2eaff513d558863ae334cef7a3d45f55e7cc13153c6719e9901c1d4db6c03f643b69ea4860690305651794284d9e61eb848ccdf5a77794d376f0af62e46d4835acce6fd9eef5df73ebb8ea3bb48629766967f446e744ecc57ff3642c4aa1ccee9a2f72d5caa75fa05787d08b79408fce792485fdecdc25df34820fb061275d70b84ece540b0fc47b2453612be34f2b78133a64e812598fbe225fd85415f8ffe5340ce955b5fd9d67dd88c1c531dde298ed25f96df271558c812c26fa386966c76f03a6ebccbca49ac955916929bd42e134f982dde03f924c464be5fd1ba44f8dc4c3cbc8162755fd1d8f7dc044b15b1a796c53df7d8769bb167b2045b49cc71e08908796c92c16a235717cabc4bb9f60f8f66ff4fff1f9836388a99583acebdff4a7fb20f48eedcd1f4bdcc06ec8b48e35307df51d9bc81d38a94992dd135b30079e1f592da6e98dff496cb1a7776460a26b06395b176f585636ebdf7eab692b227a31d6979f5a6141292698e91346b6c806b90c7c6971e481559cae92ee8f4136f2226861f5c39ddd29bbdb118a35dece03f49a96804caea79a3dacfbf09d65f2611b5622de51d98e18151acb3bb84c09caaa0cc80edfa743a4679f37d6167618ce99e73362fa6f213409931762618a61f1738c071bba5afc1db24fe94afb70c40d731908ab9a505f76f57a7d40e708fd3df0efc5b7cbb2a7b75cd23449e09684a2f0e2bfa0d6176c35f96fe94d92fc9fa4103972781f81cb6e8df7dbeb0fc529c600d768bed3f08828b773d284f69e9a203459d88c12d6df7a75be2455fec128f07a497a2b2bf626cc6272d0419ca663e9dc66b8224227eb796f0246dcae9c5b0b6cfdbbd40c3245a610481c92047c968c9fc92c04b89cc41a0c15355a8f").unwrap());
+ // Dave handles the onion message but he'll log that he errored while decoding the hop data
+ // because he sees it as an empty onion message (the only contents of the sender's OM is "hello"
+ // with TLV type 1, which Dave ignores because (1) it's odd and he can't understand it and (2) LDK
+ // only attempts to parse custom OM TLVs with type > 64).
+ nodes[3].handle_onion_message(
+ &keys_mgrs[2].1.get_node_id(Recipient::Node).unwrap(), &carol_to_dave_om);
+}
use bitcoin::hashes::sha256::Hash as Sha256;
use bitcoin::secp256k1::{self, PublicKey, Scalar, Secp256k1, SecretKey};
-use crate::blinded_path::{BlindedPath, ForwardTlvs, ReceiveTlvs, utils};
+use crate::blinded_path::BlindedPath;
+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;
use crate::ln::features::{InitFeatures, NodeFeatures};
&self, path: OnionMessagePath, message: OnionMessageContents<T>,
reply_path: Option<BlindedPath>
) -> Result<(), SendError> {
+ let (introduction_node_id, onion_msg) = Self::create_onion_message(
+ &self.entropy_source,
+ &self.node_signer,
+ &self.secp_ctx,
+ path,
+ message,
+ 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) {
+ hash_map::Entry::Vacant(_) => Err(SendError::InvalidFirstHop),
+ hash_map::Entry::Occupied(mut e) => {
+ e.get_mut().push_back(onion_msg);
+ Ok(())
+ }
+ }
+ }
+
+ /// Create an onion message with contents `message` to the destination of `path`.
+ /// Returns (introduction_node_id, onion_msg)
+ pub fn create_onion_message<T: CustomOnionMessageContents>(
+ 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> {
let OnionMessagePath { intermediate_nodes, mut destination } = path;
if let Destination::BlindedPath(BlindedPath { ref blinded_hops, .. }) = destination {
if blinded_hops.len() < 2 {
// advance the blinded path by 1 hop so the second hop is the new introduction node.
if intermediate_nodes.len() == 0 {
if let Destination::BlindedPath(ref mut blinded_path) = destination {
- let our_node_id = self.node_signer.get_node_id(Recipient::Node)
+ let our_node_id = node_signer.get_node_id(Recipient::Node)
.map_err(|()| SendError::GetNodeIdFailed)?;
if blinded_path.introduction_node_id == our_node_id {
- blinded_path.advance_message_path_by_one(&self.node_signer, &self.secp_ctx)
+ advance_path_by_one(blinded_path, node_signer, &secp_ctx)
.map_err(|()| SendError::BlindedPathAdvanceFailed)?;
}
}
}
- let blinding_secret_bytes = self.entropy_source.get_secure_random_bytes();
+ 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(&self.secp_ctx, &blinding_secret))
+ (intermediate_nodes[0], PublicKey::from_secret_key(&secp_ctx, &blinding_secret))
} else {
match destination {
- Destination::Node(pk) => (pk, PublicKey::from_secret_key(&self.secp_ctx, &blinding_secret)),
+ Destination::Node(pk) => (pk, PublicKey::from_secret_key(&secp_ctx, &blinding_secret)),
Destination::BlindedPath(BlindedPath { introduction_node_id, blinding_point, .. }) =>
(introduction_node_id, blinding_point),
}
};
let (packet_payloads, packet_keys) = packet_payloads_and_keys(
- &self.secp_ctx, &intermediate_nodes, destination, message, reply_path, &blinding_secret)
+ &secp_ctx, &intermediate_nodes, destination, message, reply_path, &blinding_secret)
.map_err(|e| SendError::Secp256k1(e))?;
- let prng_seed = self.entropy_source.get_secure_random_bytes();
+ 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)?;
- 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) {
- hash_map::Entry::Vacant(_) => Err(SendError::InvalidFirstHop),
- hash_map::Entry::Occupied(mut e) => {
- e.get_mut().push_back(msgs::OnionMessage { blinding_point, onion_routing_packet });
- Ok(())
- }
- }
+ Ok((introduction_node_id, msgs::OnionMessage {
+ blinding_point,
+ onion_routing_packet
+ }))
}
fn respond_with_onion_message<T: CustomOnionMessageContents>(
// 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_packet_pubkey(&self.secp_ctx, msg.onion_routing_packet.public_key, &onion_decode_ss) {
+ 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);
blinding_point: match next_blinding_override {
Some(blinding_point) => blinding_point,
None => {
- let blinding_factor = {
- let mut sha = Sha256::engine();
- sha.input(&msg.blinding_point.serialize()[..]);
- sha.input(control_tlvs_ss.as_ref());
- Sha256::from_engine(sha).into_inner()
- };
- let next_blinding_point = msg.blinding_point;
- match next_blinding_point.mul_tweak(&self.secp_ctx, &Scalar::from_be_bytes(blinding_factor).unwrap()) {
+ 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,
};
let mut blinded_path_idx = 0;
let mut prev_control_tlvs_ss = None;
let mut final_control_tlvs = None;
- utils::construct_keys_callback(secp_ctx, unblinded_path, Some(destination), session_priv, |_, onion_packet_ss, ephemeral_pubkey, control_tlvs_ss, unblinded_pk_opt, enc_payload_opt| {
- if num_unblinded_hops != 0 && unblinded_path_idx < num_unblinded_hops {
- if let Some(ss) = prev_control_tlvs_ss.take() {
- payloads.push((Payload::Forward(ForwardControlTlvs::Unblinded(
- ForwardTlvs {
- next_node_id: unblinded_pk_opt.unwrap(),
- next_blinding_override: None,
- }
- )), ss));
+ utils::construct_keys_callback(secp_ctx, unblinded_path.iter(), Some(destination), session_priv,
+ |_, onion_packet_ss, ephemeral_pubkey, control_tlvs_ss, unblinded_pk_opt, enc_payload_opt| {
+ if num_unblinded_hops != 0 && unblinded_path_idx < num_unblinded_hops {
+ if let Some(ss) = prev_control_tlvs_ss.take() {
+ payloads.push((Payload::Forward(ForwardControlTlvs::Unblinded(
+ ForwardTlvs {
+ next_node_id: unblinded_pk_opt.unwrap(),
+ next_blinding_override: None,
+ }
+ )), ss));
+ }
+ prev_control_tlvs_ss = Some(control_tlvs_ss);
+ unblinded_path_idx += 1;
+ } else if let Some((intro_node_id, blinding_pt)) = intro_node_id_blinding_pt.take() {
+ if let Some(control_tlvs_ss) = prev_control_tlvs_ss.take() {
+ payloads.push((Payload::Forward(ForwardControlTlvs::Unblinded(ForwardTlvs {
+ next_node_id: intro_node_id,
+ next_blinding_override: Some(blinding_pt),
+ })), control_tlvs_ss));
+ }
}
- prev_control_tlvs_ss = Some(control_tlvs_ss);
- unblinded_path_idx += 1;
- } else if let Some((intro_node_id, blinding_pt)) = intro_node_id_blinding_pt.take() {
- if let Some(control_tlvs_ss) = prev_control_tlvs_ss.take() {
- payloads.push((Payload::Forward(ForwardControlTlvs::Unblinded(ForwardTlvs {
- next_node_id: intro_node_id,
- next_blinding_override: Some(blinding_pt),
- })), control_tlvs_ss));
+ if blinded_path_idx < num_blinded_hops.saturating_sub(1) && enc_payload_opt.is_some() {
+ payloads.push((Payload::Forward(ForwardControlTlvs::Blinded(enc_payload_opt.unwrap())),
+ control_tlvs_ss));
+ blinded_path_idx += 1;
+ } else if let Some(encrypted_payload) = enc_payload_opt {
+ final_control_tlvs = Some(ReceiveControlTlvs::Blinded(encrypted_payload));
+ prev_control_tlvs_ss = Some(control_tlvs_ss);
}
- }
- if blinded_path_idx < num_blinded_hops.saturating_sub(1) && enc_payload_opt.is_some() {
- payloads.push((Payload::Forward(ForwardControlTlvs::Blinded(enc_payload_opt.unwrap())),
- control_tlvs_ss));
- blinded_path_idx += 1;
- } else if let Some(encrypted_payload) = enc_payload_opt {
- final_control_tlvs = Some(ReceiveControlTlvs::Blinded(encrypted_payload));
- prev_control_tlvs_ss = Some(control_tlvs_ss);
- }
- let (rho, mu) = onion_utils::gen_rho_mu_from_shared_secret(onion_packet_ss.as_ref());
- onion_packet_keys.push(onion_utils::OnionKeys {
- #[cfg(test)]
- shared_secret: onion_packet_ss,
- #[cfg(test)]
- blinding_factor: [0; 32],
- ephemeral_pubkey,
- rho,
- mu,
- });
- })?;
+ let (rho, mu) = onion_utils::gen_rho_mu_from_shared_secret(onion_packet_ss.as_ref());
+ onion_packet_keys.push(onion_utils::OnionKeys {
+ #[cfg(test)]
+ shared_secret: onion_packet_ss,
+ #[cfg(test)]
+ blinding_factor: [0; 32],
+ ephemeral_pubkey,
+ rho,
+ mu,
+ });
+ }
+ )?;
if let Some(control_tlvs) = final_control_tlvs {
payloads.push((Payload::Receive {
// 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::offers::{OffersMessage, OffersMessageHandler};
-pub(crate) use self::packet::{ControlTlvs, Packet};
+pub use self::packet::Packet;
+pub(crate) use self::packet::ControlTlvs;
use crate::ln::msgs::DecodeError;
use crate::offers::invoice_error::InvoiceError;
use crate::offers::invoice_request::InvoiceRequest;
-use crate::offers::invoice::Invoice;
-use crate::offers::parse::ParseError;
+use crate::offers::invoice::Bolt12Invoice;
+use crate::offers::parse::Bolt12ParseError;
use crate::util::logger::Logger;
use crate::util::ser::{Readable, ReadableArgs, Writeable, Writer};
///
/// [`OnionMessage`]: crate::ln::msgs::OnionMessage
pub trait OffersMessageHandler {
- /// Handles the given message by either responding with an [`Invoice`], sending a payment, or
- /// replying with an error.
+ /// Handles the given message by either responding with an [`Bolt12Invoice`], sending a payment,
+ /// or replying with an error.
fn handle_message(&self, message: OffersMessage) -> Option<OffersMessage>;
}
/// Possible BOLT 12 Offers messages sent and received via an [`OnionMessage`].
///
/// [`OnionMessage`]: crate::ln::msgs::OnionMessage
-#[derive(Debug)]
+#[derive(Clone, Debug)]
pub enum OffersMessage {
- /// A request for an [`Invoice`] for a particular [`Offer`].
+ /// A request for a [`Bolt12Invoice`] for a particular [`Offer`].
///
/// [`Offer`]: crate::offers::offer::Offer
InvoiceRequest(InvoiceRequest),
- /// An [`Invoice`] sent in response to an [`InvoiceRequest`] or a [`Refund`].
+ /// A [`Bolt12Invoice`] sent in response to an [`InvoiceRequest`] or a [`Refund`].
///
/// [`Refund`]: crate::offers::refund::Refund
- Invoice(Invoice),
+ Invoice(Bolt12Invoice),
/// An error from handling an [`OffersMessage`].
InvoiceError(InvoiceError),
}
}
- fn parse(tlv_type: u64, bytes: Vec<u8>) -> Result<Self, ParseError> {
+ 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)?)),
- INVOICE_TLV_TYPE => Ok(Self::Invoice(Invoice::try_from(bytes)?)),
- _ => Err(ParseError::Decode(DecodeError::InvalidValue)),
+ INVOICE_TLV_TYPE => Ok(Self::Invoice(Bolt12Invoice::try_from(bytes)?)),
+ _ => Err(Bolt12ParseError::Decode(DecodeError::InvalidValue)),
}
}
}
match Self::parse(tlv_type, bytes) {
Ok(message) => Ok(message),
- Err(ParseError::Decode(e)) => Err(e),
- Err(ParseError::InvalidSemantics(e)) => {
+ Err(Bolt12ParseError::Decode(e)) => Err(e),
+ Err(Bolt12ParseError::InvalidSemantics(e)) => {
log_trace!(logger, "Invalid semantics for TLV type {}: {:?}", tlv_type, e);
Err(DecodeError::InvalidValue)
},
- Err(ParseError::InvalidSignature(e)) => {
+ Err(Bolt12ParseError::InvalidSignature(e)) => {
log_trace!(logger, "Invalid signature for TLV type {}: {:?}", tlv_type, e);
Err(DecodeError::InvalidValue)
},
use bitcoin::secp256k1::PublicKey;
use bitcoin::secp256k1::ecdh::SharedSecret;
-use crate::blinded_path::{BlindedPath, ForwardTlvs, ReceiveTlvs};
+use crate::blinded_path::BlindedPath;
+use crate::blinded_path::message::{ForwardTlvs, ReceiveTlvs};
+use crate::blinded_path::utils::Padding;
use crate::ln::msgs::DecodeError;
use crate::ln::onion_utils;
use super::messenger::CustomOnionMessageHandler;
pub(super) const SMALL_PACKET_HOP_DATA_LEN: usize = 1300;
pub(super) const BIG_PACKET_HOP_DATA_LEN: usize = 32768;
+/// Packet of hop data for next peer
#[derive(Clone, Debug, PartialEq, Eq)]
-pub(crate) struct Packet {
- pub(super) version: u8,
- pub(super) public_key: PublicKey,
+pub struct Packet {
+ /// Bolt 04 version number
+ pub version: u8,
+ /// A random sepc256k1 point, used to build the ECDH shared secret to decrypt hop_data
+ pub public_key: PublicKey,
+ /// Encrypted payload for the next hop
+ //
// Unlike the onion packets used for payments, onion message packets can have payloads greater
// than 1300 bytes.
// TODO: if 1300 ends up being the most common size, optimize this to be:
// enum { ThirteenHundred([u8; 1300]), VarLen(Vec<u8>) }
- pub(super) hop_data: Vec<u8>,
- pub(super) hmac: [u8; 32],
+ pub hop_data: Vec<u8>,
+ /// HMAC to verify the integrity of hop_data
+ pub hmac: [u8; 32],
}
impl onion_utils::Packet for Packet {
Blinded(Vec<u8>),
/// If we're constructing an onion message hop through an intermediate unblinded node, we'll need
/// to construct the intermediate hop's control TLVs in their unblinded state to avoid encoding
- /// them into an intermediate Vec. See [`crate::blinded_path::ForwardTlvs`] for more info.
+ /// them into an intermediate Vec. See [`crate::blinded_path::message::ForwardTlvs`] for more
+ /// info.
Unblinded(ForwardTlvs),
}
pub(super) enum ReceiveControlTlvs {
/// See [`ForwardControlTlvs::Blinded`].
Blinded(Vec<u8>),
- /// See [`ForwardControlTlvs::Unblinded`] and [`crate::blinded_path::ReceiveTlvs`].
+ /// See [`ForwardControlTlvs::Unblinded`] and [`crate::blinded_path::message::ReceiveTlvs`].
Unblinded(ReceiveTlvs),
}
match &self.0 {
Payload::Forward(ForwardControlTlvs::Blinded(encrypted_bytes)) => {
_encode_varint_length_prefixed_tlv!(w, {
- (4, *encrypted_bytes, vec_type)
+ (4, *encrypted_bytes, required_vec)
})
},
Payload::Receive {
} => {
_encode_varint_length_prefixed_tlv!(w, {
(2, reply_path, option),
- (4, *encrypted_bytes, vec_type),
+ (4, *encrypted_bytes, required_vec),
(message.tlv_type(), message, required)
})
},
}
/// When reading a packet off the wire, we don't know a priori whether the packet is to be forwarded
-/// or received. Thus we read a ControlTlvs rather than reading a ForwardControlTlvs or
-/// ReceiveControlTlvs directly.
+/// or received. Thus we read a `ControlTlvs` rather than reading a [`ForwardTlvs`] or
+/// [`ReceiveTlvs`] directly. Also useful on the encoding side to keep forward and receive TLVs in
+/// the same iterator.
pub(crate) enum ControlTlvs {
/// This onion message is intended to be forwarded.
Forward(ForwardTlvs),
}
impl Readable for ControlTlvs {
- fn read<R: Read>(mut r: &mut R) -> Result<Self, DecodeError> {
- let mut _padding: Option<Padding> = None;
- let mut _short_channel_id: Option<u64> = None;
- let mut next_node_id: Option<PublicKey> = None;
- let mut path_id: Option<[u8; 32]> = None;
- let mut next_blinding_override: Option<PublicKey> = None;
- decode_tlv_stream!(&mut r, {
+ fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
+ _init_and_read_tlv_stream!(r, {
(1, _padding, option),
(2, _short_channel_id, option),
(4, next_node_id, option),
(6, path_id, option),
(8, next_blinding_override, option),
});
+ let _padding: Option<Padding> = _padding;
+ let _short_channel_id: Option<u64> = _short_channel_id;
let valid_fwd_fmt = next_node_id.is_some() && path_id.is_none();
let valid_recv_fmt = next_node_id.is_none() && next_blinding_override.is_none();
}
}
-/// Reads padding to the end, ignoring what's read.
-pub(crate) struct Padding {}
-impl Readable for Padding {
- #[inline]
- fn read<R: Read>(reader: &mut R) -> Result<Self, DecodeError> {
- loop {
- let mut buf = [0; 8192];
- if reader.read(&mut buf[..])? == 0 { break; }
+impl Writeable for ControlTlvs {
+ fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
+ match self {
+ Self::Forward(tlvs) => tlvs.write(w),
+ Self::Receive(tlvs) => tlvs.write(w),
}
- Ok(Self {})
}
}
//! The [`NetworkGraph`] stores the network gossip and [`P2PGossipSync`] fetches it from peers
use bitcoin::secp256k1::constants::PUBLIC_KEY_SIZE;
-use bitcoin::secp256k1::PublicKey;
+use bitcoin::secp256k1::{PublicKey, Verification};
use bitcoin::secp256k1::Secp256k1;
use bitcoin::secp256k1;
use bitcoin::blockdata::constants::genesis_block;
use crate::events::{MessageSendEvent, MessageSendEventsProvider};
+use crate::ln::ChannelId;
use crate::ln::features::{ChannelFeatures, NodeFeatures, InitFeatures};
-use crate::ln::msgs::{DecodeError, ErrorAction, Init, LightningError, RoutingMessageHandler, NetAddress, MAX_VALUE_MSAT};
+use crate::ln::msgs::{DecodeError, ErrorAction, Init, LightningError, RoutingMessageHandler, SocketAddress, MAX_VALUE_MSAT};
use crate::ln::msgs::{ChannelAnnouncement, ChannelUpdate, NodeAnnouncement, GossipTimestampFilter};
use crate::ln::msgs::{QueryChannelRange, ReplyChannelRange, QueryShortChannelIds, ReplyShortChannelIdsEnd};
use crate::ln::msgs;
impl fmt::Debug for NodeId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- write!(f, "NodeId({})", log_bytes!(self.0))
+ write!(f, "NodeId({})", crate::util::logger::DebugBytes(&self.0))
}
}
impl fmt::Display for NodeId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- write!(f, "{}", log_bytes!(self.0))
+ crate::util::logger::DebugBytes(&self.0).fmt(f)
}
}
where U::Target: UtxoLookup, L::Target: Logger
{
network_graph: G,
- utxo_lookup: Option<U>,
+ utxo_lookup: RwLock<Option<U>>,
#[cfg(feature = "std")]
full_syncs_requested: AtomicUsize,
pending_events: Mutex<Vec<MessageSendEvent>>,
network_graph,
#[cfg(feature = "std")]
full_syncs_requested: AtomicUsize::new(0),
- utxo_lookup,
+ utxo_lookup: RwLock::new(utxo_lookup),
pending_events: Mutex::new(vec![]),
logger,
}
/// Adds a provider used to check new announcements. Does not affect
/// existing announcements unless they are updated.
/// Add, update or remove the provider would replace the current one.
- pub fn add_utxo_lookup(&mut self, utxo_lookup: Option<U>) {
- self.utxo_lookup = utxo_lookup;
+ pub fn add_utxo_lookup(&self, utxo_lookup: Option<U>) {
+ *self.utxo_lookup.write().unwrap() = utxo_lookup;
}
/// Gets a reference to the underlying [`NetworkGraph`] which was provided in
err: format!("Invalid signature on {} message", $msg_type),
action: ErrorAction::SendWarningMessage {
msg: msgs::WarningMessage {
- channel_id: [0; 32],
+ channel_id: ChannelId::new_zero(),
data: format!("Invalid signature on {} message", $msg_type),
},
log_level: Level::Trace,
err: format!("Invalid public key on {} message", $msg_type),
action: ErrorAction::SendWarningMessage {
msg: msgs::WarningMessage {
- channel_id: [0; 32],
+ channel_id: ChannelId::new_zero(),
data: format!("Invalid public key on {} message", $msg_type),
},
log_level: Level::Trace
}
}
+/// Verifies the signature of a [`NodeAnnouncement`].
+///
+/// Returns an error if it is invalid.
+pub fn verify_node_announcement<C: Verification>(msg: &NodeAnnouncement, secp_ctx: &Secp256k1<C>) -> Result<(), LightningError> {
+ let msg_hash = hash_to_message!(&Sha256dHash::hash(&msg.contents.encode()[..])[..]);
+ secp_verify_sig!(secp_ctx, &msg_hash, &msg.signature, &get_pubkey_from_node_id!(msg.contents.node_id, "node_announcement"), "node_announcement");
+
+ Ok(())
+}
+
+/// Verifies all signatures included in a [`ChannelAnnouncement`].
+///
+/// Returns an error if one of the signatures is invalid.
+pub fn verify_channel_announcement<C: Verification>(msg: &ChannelAnnouncement, secp_ctx: &Secp256k1<C>) -> Result<(), LightningError> {
+ let msg_hash = hash_to_message!(&Sha256dHash::hash(&msg.contents.encode()[..])[..]);
+ secp_verify_sig!(secp_ctx, &msg_hash, &msg.node_signature_1, &get_pubkey_from_node_id!(msg.contents.node_id_1, "channel_announcement"), "channel_announcement");
+ secp_verify_sig!(secp_ctx, &msg_hash, &msg.node_signature_2, &get_pubkey_from_node_id!(msg.contents.node_id_2, "channel_announcement"), "channel_announcement");
+ secp_verify_sig!(secp_ctx, &msg_hash, &msg.bitcoin_signature_1, &get_pubkey_from_node_id!(msg.contents.bitcoin_key_1, "channel_announcement"), "channel_announcement");
+ secp_verify_sig!(secp_ctx, &msg_hash, &msg.bitcoin_signature_2, &get_pubkey_from_node_id!(msg.contents.bitcoin_key_2, "channel_announcement"), "channel_announcement");
+
+ Ok(())
+}
+
impl<G: Deref<Target=NetworkGraph<L>>, U: Deref, L: Deref> RoutingMessageHandler for P2PGossipSync<G, U, L>
where U::Target: UtxoLookup, L::Target: Logger
{
}
fn handle_channel_announcement(&self, msg: &msgs::ChannelAnnouncement) -> Result<bool, LightningError> {
- self.network_graph.update_channel_from_announcement(msg, &self.utxo_lookup)?;
+ self.network_graph.update_channel_from_announcement(msg, &*self.utxo_lookup.read().unwrap())?;
Ok(msg.contents.excess_data.len() <= MAX_EXCESS_BYTES_FOR_RELAY)
}
impl fmt::Display for ChannelInfo {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "features: {}, node_one: {}, one_to_two: {:?}, node_two: {}, two_to_one: {:?}",
- log_bytes!(self.features.encode()), log_bytes!(self.node_one.as_slice()), self.one_to_two, log_bytes!(self.node_two.as_slice()), self.two_to_one)?;
+ log_bytes!(self.features.encode()), &self.node_one, self.one_to_two, &self.node_two, self.two_to_one)?;
Ok(())
}
}
impl NodeAnnouncementInfo {
/// Internet-level addresses via which one can connect to the node
- pub fn addresses(&self) -> &[NetAddress] {
+ pub fn addresses(&self) -> &[SocketAddress] {
self.announcement_message.as_ref()
.map(|msg| msg.contents.addresses.as_slice())
.unwrap_or_default()
impl Writeable for NodeAnnouncementInfo {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
- let empty_addresses = Vec::<NetAddress>::new();
+ let empty_addresses = Vec::<SocketAddress>::new();
write_tlv_fields!(writer, {
(0, self.features, required),
(2, self.last_update, required),
(4, self.rgb, required),
(6, self.alias, required),
(8, self.announcement_message, option),
- (10, empty_addresses, vec_type), // Versions prior to 0.0.115 require this field
+ (10, empty_addresses, required_vec), // Versions prior to 0.0.115 require this field
});
Ok(())
}
}
impl Readable for NodeAnnouncementInfo {
- fn read<R: io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
- _init_and_read_tlv_fields!(reader, {
+ fn read<R: io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
+ _init_and_read_len_prefixed_tlv_fields!(reader, {
(0, features, required),
(2, last_update, required),
(4, rgb, required),
(6, alias, required),
(8, announcement_message, option),
- (10, _addresses, vec_type), // deprecated, not used anymore
+ (10, _addresses, optional_vec), // deprecated, not used anymore
});
- let _: Option<Vec<NetAddress>> = _addresses;
+ let _: Option<Vec<SocketAddress>> = _addresses;
Ok(Self { features: features.0.unwrap(), last_update: last_update.0.unwrap(), rgb: rgb.0.unwrap(),
alias: alias.0.unwrap(), announcement_message })
- }
+ }
}
/// A user-defined name for a node, which may be used when displaying the node in a graph.
write_tlv_fields!(writer, {
// Note that older versions of LDK wrote the lowest inbound fees here at type 0
(2, self.announcement_info, option),
- (4, self.channels, vec_type),
+ (4, self.channels, required_vec),
});
Ok(())
}
}
// A wrapper allowing for the optional deserialization of `NodeAnnouncementInfo`. Utilizing this is
-// necessary to maintain compatibility with previous serializations of `NetAddress` that have an
+// necessary to maintain compatibility with previous serializations of `SocketAddress` that have an
// invalid hostname set. We ignore and eat all errors until we are either able to read a
// `NodeAnnouncementInfo` or hit a `ShortRead`, i.e., read the TLV field to the end.
struct NodeAnnouncementInfoDeserWrapper(NodeAnnouncementInfo);
// with zero inbound fees, causing that heuristic to provide little gain. Worse, because it
// requires additional complexity and lookups during routing, it ends up being a
// performance loss. Thus, we simply ignore the old field here and no longer track it.
- let mut _lowest_inbound_channel_fees: Option<RoutingFees> = None;
- let mut announcement_info_wrap: Option<NodeAnnouncementInfoDeserWrapper> = None;
- _init_tlv_field_var!(channels, vec_type);
-
- read_tlv_fields!(reader, {
+ _init_and_read_len_prefixed_tlv_fields!(reader, {
(0, _lowest_inbound_channel_fees, option),
(2, announcement_info_wrap, upgradable_option),
- (4, channels, vec_type),
+ (4, channels, required_vec),
});
+ let _: Option<RoutingFees> = _lowest_inbound_channel_fees;
+ let announcement_info_wrap: Option<NodeAnnouncementInfoDeserWrapper> = announcement_info_wrap;
Ok(NodeInfo {
announcement_info: announcement_info_wrap.map(|w| w.0),
- channels: _init_tlv_based_struct_field!(channels, vec_type),
+ channels,
})
}
}
}
writeln!(f, "[Nodes]")?;
for (&node_id, val) in self.nodes.read().unwrap().unordered_iter() {
- writeln!(f, " {}: {}", log_bytes!(node_id.as_slice()), val)?;
+ writeln!(f, " {}: {}", &node_id, val)?;
}
Ok(())
}
/// 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.
pub fn update_node_from_announcement(&self, msg: &msgs::NodeAnnouncement) -> Result<(), LightningError> {
- let msg_hash = hash_to_message!(&Sha256dHash::hash(&msg.contents.encode()[..])[..]);
- secp_verify_sig!(self.secp_ctx, &msg_hash, &msg.signature, &get_pubkey_from_node_id!(msg.contents.node_id, "node_announcement"), "node_announcement");
+ verify_node_announcement(msg, &self.secp_ctx)?;
self.update_node_from_announcement_intern(&msg.contents, Some(&msg))
}
where
U::Target: UtxoLookup,
{
- let msg_hash = hash_to_message!(&Sha256dHash::hash(&msg.contents.encode()[..])[..]);
- secp_verify_sig!(self.secp_ctx, &msg_hash, &msg.node_signature_1, &get_pubkey_from_node_id!(msg.contents.node_id_1, "channel_announcement"), "channel_announcement");
- secp_verify_sig!(self.secp_ctx, &msg_hash, &msg.node_signature_2, &get_pubkey_from_node_id!(msg.contents.node_id_2, "channel_announcement"), "channel_announcement");
- secp_verify_sig!(self.secp_ctx, &msg_hash, &msg.bitcoin_signature_1, &get_pubkey_from_node_id!(msg.contents.bitcoin_key_1, "channel_announcement"), "channel_announcement");
- secp_verify_sig!(self.secp_ctx, &msg_hash, &msg.bitcoin_signature_2, &get_pubkey_from_node_id!(msg.contents.bitcoin_key_2, "channel_announcement"), "channel_announcement");
+ verify_channel_announcement(msg, &self.secp_ctx)?;
self.update_channel_from_unsigned_announcement_intern(&msg.contents, Some(msg), utxo_lookup)
}
let node_id_a = channel_info.node_one.clone();
let node_id_b = channel_info.node_two.clone();
+ log_gossip!(self.logger, "Adding channel {} between nodes {} and {}", short_channel_id, node_id_a, node_id_b);
+
match channels.entry(short_channel_id) {
IndexedMapEntry::Occupied(mut entry) => {
//TODO: because asking the blockchain if short_channel_id is valid is only optional
let mut scids_to_remove = Vec::new();
for (scid, info) in channels.unordered_iter_mut() {
if info.one_to_two.is_some() && info.one_to_two.as_ref().unwrap().last_update < min_time_unix {
+ log_gossip!(self.logger, "Removing directional update one_to_two (0) for channel {} due to its timestamp {} being below {}",
+ scid, info.one_to_two.as_ref().unwrap().last_update, min_time_unix);
info.one_to_two = None;
}
if info.two_to_one.is_some() && info.two_to_one.as_ref().unwrap().last_update < min_time_unix {
+ log_gossip!(self.logger, "Removing directional update two_to_one (1) for channel {} due to its timestamp {} being below {}",
+ scid, info.two_to_one.as_ref().unwrap().last_update, min_time_unix);
info.two_to_one = None;
}
if info.one_to_two.is_none() || info.two_to_one.is_none() {
// We check the announcement_received_time here to ensure we don't drop
// announcements that we just received and are just waiting for our peer to send a
// channel_update for.
- if info.announcement_received_time < min_time_unix as u64 {
+ let announcement_received_timestamp = info.announcement_received_time;
+ if announcement_received_timestamp < min_time_unix as u64 {
+ log_gossip!(self.logger, "Removing channel {} because both directional updates are missing and its announcement timestamp {} being below {}",
+ scid, announcement_received_timestamp, min_time_unix);
scids_to_remove.push(*scid);
}
}
}
}
+ log_gossip!(self.logger, "Updating channel {} in direction {} with timestamp {}", msg.short_channel_id, msg.flags & 1, msg.timestamp);
+
let mut channels = self.channels.write().unwrap();
match channels.get_mut(&msg.short_channel_id) {
None => {
/// Get network addresses by node id.
/// Returns None if the requested node is completely unknown,
/// or if node announcement for the node was never received.
- pub fn get_addresses(&self, pubkey: &PublicKey) -> Option<Vec<NetAddress>> {
+ pub fn get_addresses(&self, pubkey: &PublicKey) -> Option<Vec<SocketAddress>> {
self.nodes.get(&NodeId::from_pubkey(&pubkey))
.and_then(|node| node.announcement_info.as_ref().map(|ann| ann.addresses().to_vec()))
}
// This serialized info has an address field but no announcement_message, therefore the addresses returned by our function will still be empty
assert!(ann_info_with_addresses.addresses().is_empty());
}
+
+ #[test]
+ fn test_node_id_display() {
+ let node_id = NodeId([42; 33]);
+ assert_eq!(format!("{}", &node_id), "2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a");
+ }
}
#[cfg(ldk_bench)]
use crate::blinded_path::{BlindedHop, BlindedPath};
use crate::ln::PaymentHash;
use crate::ln::channelmanager::{ChannelDetails, PaymentId};
-use crate::ln::features::{Bolt12InvoiceFeatures, ChannelFeatures, InvoiceFeatures, NodeFeatures};
+use crate::ln::features::{Bolt11InvoiceFeatures, Bolt12InvoiceFeatures, ChannelFeatures, NodeFeatures};
use crate::ln::msgs::{DecodeError, ErrorAction, LightningError, MAX_VALUE_MSAT};
-use crate::offers::invoice::{BlindedPayInfo, Invoice as Bolt12Invoice};
+use crate::offers::invoice::{BlindedPayInfo, Bolt12Invoice};
use crate::routing::gossip::{DirectedChannelInfo, EffectiveCapacity, ReadOnlyNetworkGraph, NetworkGraph, NodeId, RoutingFees};
-use crate::routing::scoring::{ChannelUsage, LockableScore, Score};
+use crate::routing::scoring::{ChannelUsage, LockableScore, ScoreLookUp};
use crate::util::ser::{Writeable, Readable, ReadableArgs, Writer};
use crate::util::logger::{Level, Logger};
use crate::util::chacha20::ChaCha20;
use crate::io;
use crate::prelude::*;
-use crate::sync::{Mutex};
+use crate::sync::Mutex;
use alloc::collections::BinaryHeap;
use core::{cmp, fmt};
-use core::ops::{Deref, DerefMut};
+use core::ops::Deref;
/// A [`Router`] implemented using [`find_route`].
-pub struct DefaultRouter<G: Deref<Target = NetworkGraph<L>>, L: Deref, S: Deref, SP: Sized, Sc: Score<ScoreParams = SP>> where
+pub struct DefaultRouter<G: Deref<Target = NetworkGraph<L>>, L: Deref, S: Deref, SP: Sized, Sc: ScoreLookUp<ScoreParams = SP>> where
L::Target: Logger,
- S::Target: for <'a> LockableScore<'a, Score = Sc>,
+ S::Target: for <'a> LockableScore<'a, ScoreLookUp = Sc>,
{
network_graph: G,
logger: L,
score_params: SP
}
-impl<G: Deref<Target = NetworkGraph<L>>, L: Deref, S: Deref, SP: Sized, Sc: Score<ScoreParams = SP>> DefaultRouter<G, L, S, SP, Sc> where
+impl<G: Deref<Target = NetworkGraph<L>>, L: Deref, S: Deref, SP: Sized, Sc: ScoreLookUp<ScoreParams = SP>> DefaultRouter<G, L, S, SP, Sc> where
L::Target: Logger,
- S::Target: for <'a> LockableScore<'a, Score = Sc>,
+ S::Target: for <'a> LockableScore<'a, ScoreLookUp = Sc>,
{
/// Creates a new router.
pub fn new(network_graph: G, logger: L, random_seed_bytes: [u8; 32], scorer: S, score_params: SP) -> Self {
}
}
-impl< G: Deref<Target = NetworkGraph<L>>, L: Deref, S: Deref, SP: Sized, Sc: Score<ScoreParams = SP>> Router for DefaultRouter<G, L, S, SP, Sc> where
+impl< G: Deref<Target = NetworkGraph<L>>, L: Deref, S: Deref, SP: Sized, Sc: ScoreLookUp<ScoreParams = SP>> Router for DefaultRouter<G, L, S, SP, Sc> where
L::Target: Logger,
- S::Target: for <'a> LockableScore<'a, Score = Sc>,
+ S::Target: for <'a> LockableScore<'a, ScoreLookUp = Sc>,
{
fn find_route(
&self,
payer: &PublicKey,
params: &RouteParameters,
first_hops: Option<&[&ChannelDetails]>,
- inflight_htlcs: &InFlightHtlcs
+ inflight_htlcs: InFlightHtlcs
) -> Result<Route, LightningError> {
let random_seed_bytes = {
let mut locked_random_seed_bytes = self.random_seed_bytes.lock().unwrap();
};
find_route(
payer, params, &self.network_graph, first_hops, &*self.logger,
- &ScorerAccountingForInFlightHtlcs::new(self.scorer.lock().deref_mut(), inflight_htlcs),
+ &ScorerAccountingForInFlightHtlcs::new(self.scorer.read_lock(), &inflight_htlcs),
&self.score_params,
&random_seed_bytes
)
/// A trait defining behavior for routing a payment.
pub trait Router {
- /// Finds a [`Route`] between `payer` and `payee` for a payment with the given values.
+ /// 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`]
+ /// and [`RouteParameters::final_value_msat`], respectively.
fn find_route(
&self, payer: &PublicKey, route_params: &RouteParameters,
- first_hops: Option<&[&ChannelDetails]>, inflight_htlcs: &InFlightHtlcs
+ first_hops: Option<&[&ChannelDetails]>, inflight_htlcs: InFlightHtlcs
) -> Result<Route, LightningError>;
- /// Finds a [`Route`] between `payer` and `payee` for a payment with the given values. Includes
- /// `PaymentHash` and `PaymentId` to be able to correlate the request with a specific payment.
+ /// 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`]
+ /// and [`RouteParameters::final_value_msat`], respectively.
+ ///
+ /// Includes a [`PaymentHash`] and a [`PaymentId`] to be able to correlate the request with a specific
+ /// payment.
fn find_route_with_id(
&self, payer: &PublicKey, route_params: &RouteParameters,
- first_hops: Option<&[&ChannelDetails]>, inflight_htlcs: &InFlightHtlcs,
+ first_hops: Option<&[&ChannelDetails]>, inflight_htlcs: InFlightHtlcs,
_payment_hash: PaymentHash, _payment_id: PaymentId
) -> Result<Route, LightningError> {
self.find_route(payer, route_params, first_hops, inflight_htlcs)
}
}
-/// [`Score`] implementation that factors in in-flight HTLC liquidity.
+/// [`ScoreLookUp`] implementation that factors in in-flight HTLC liquidity.
///
-/// Useful for custom [`Router`] implementations to wrap their [`Score`] on-the-fly when calling
+/// Useful for custom [`Router`] implementations to wrap their [`ScoreLookUp`] on-the-fly when calling
/// [`find_route`].
///
-/// [`Score`]: crate::routing::scoring::Score
-pub struct ScorerAccountingForInFlightHtlcs<'a, S: Score<ScoreParams = SP>, SP: Sized> {
- scorer: &'a mut S,
+/// [`ScoreLookUp`]: crate::routing::scoring::ScoreLookUp
+pub struct ScorerAccountingForInFlightHtlcs<'a, SP: Sized, Sc: 'a + ScoreLookUp<ScoreParams = SP>, S: Deref<Target = Sc>> {
+ scorer: S,
// Maps a channel's short channel id and its direction to the liquidity used up.
inflight_htlcs: &'a InFlightHtlcs,
}
-
-impl<'a, S: Score<ScoreParams = SP>, SP: Sized> ScorerAccountingForInFlightHtlcs<'a, S, SP> {
+impl<'a, SP: Sized, Sc: ScoreLookUp<ScoreParams = SP>, S: Deref<Target = Sc>> ScorerAccountingForInFlightHtlcs<'a, SP, Sc, S> {
/// Initialize a new `ScorerAccountingForInFlightHtlcs`.
- pub fn new(scorer: &'a mut S, inflight_htlcs: &'a InFlightHtlcs) -> Self {
+ pub fn new(scorer: S, inflight_htlcs: &'a InFlightHtlcs) -> Self {
ScorerAccountingForInFlightHtlcs {
scorer,
inflight_htlcs
}
#[cfg(c_bindings)]
-impl<'a, S: Score<ScoreParams = SP>, SP: Sized> Writeable for ScorerAccountingForInFlightHtlcs<'a, S, SP> {
+impl<'a, SP: Sized, Sc: ScoreLookUp<ScoreParams = SP>, S: Deref<Target = Sc>> Writeable for ScorerAccountingForInFlightHtlcs<'a, SP, Sc, S> {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> { self.scorer.write(writer) }
}
-impl<'a, S: Score<ScoreParams = SP>, SP: Sized> Score for ScorerAccountingForInFlightHtlcs<'a, S, SP> {
- type ScoreParams = S::ScoreParams;
+impl<'a, SP: Sized, Sc: 'a + ScoreLookUp<ScoreParams = SP>, S: Deref<Target = Sc>> ScoreLookUp for ScorerAccountingForInFlightHtlcs<'a, SP, Sc, S> {
+ type ScoreParams = Sc::ScoreParams;
fn channel_penalty_msat(&self, short_channel_id: u64, source: &NodeId, target: &NodeId, usage: ChannelUsage, score_params: &Self::ScoreParams) -> u64 {
if let Some(used_liquidity) = self.inflight_htlcs.used_liquidity_msat(
source, target, short_channel_id
self.scorer.channel_penalty_msat(short_channel_id, source, target, usage, score_params)
}
}
-
- fn payment_path_failed(&mut self, path: &Path, short_channel_id: u64) {
- self.scorer.payment_path_failed(path, short_channel_id)
- }
-
- fn payment_path_successful(&mut self, path: &Path) {
- self.scorer.payment_path_successful(path)
- }
-
- fn probe_failed(&mut self, path: &Path, short_channel_id: u64) {
- self.scorer.probe_failed(path, short_channel_id)
- }
-
- fn probe_successful(&mut self, path: &Path) {
- self.scorer.probe_successful(path)
- }
}
/// A data structure for tracking in-flight HTLCs. May be used during pathfinding to account for
}
}
+ /// Adds a known HTLC given the public key of the HTLC source, target, and short channel
+ /// id.
+ pub fn add_inflight_htlc(&mut self, source: &NodeId, target: &NodeId, channel_scid: u64, used_msat: u64){
+ self.0
+ .entry((channel_scid, source < target))
+ .and_modify(|used_liquidity_msat| *used_liquidity_msat += used_msat)
+ .or_insert(used_msat);
+ }
+
/// Returns liquidity in msat given the public key of the HTLC source, target, and short channel
/// id.
pub fn used_liquidity_msat(&self, source: &NodeId, target: &NodeId, channel_scid: u64) -> Option<u64> {
///
/// [`BlindedPath`]: crate::blinded_path::BlindedPath
pub cltv_expiry_delta: u32,
+ /// Indicates whether this hop is possibly announced in the public network graph.
+ ///
+ /// Will be `true` if there is a possibility that the channel is publicly known, i.e., if we
+ /// either know for sure it's announced in the public graph, or if any public channels exist
+ /// for which the given `short_channel_id` could be an alias for. Will be `false` if we believe
+ /// the channel to be unannounced.
+ ///
+ /// Will be `true` for objects serialized with LDK version 0.0.116 and before.
+ pub maybe_announced_channel: bool,
}
impl_writeable_tlv_based!(RouteHop, {
(0, pubkey, required),
+ (1, maybe_announced_channel, (default_value, true)),
(2, node_features, required),
(4, short_channel_id, required),
(6, channel_features, required),
});
/// The blinded portion of a [`Path`], if we're routing to a recipient who provided blinded paths in
-/// their BOLT12 [`Invoice`].
+/// their [`Bolt12Invoice`].
///
-/// [`Invoice`]: crate::offers::invoice::Invoice
+/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct BlindedTail {
/// The hops of the [`BlindedPath`] provided by the recipient.
}
impl_writeable_tlv_based!(BlindedTail, {
- (0, hops, vec_type),
+ (0, hops, required_vec),
(2, blinding_point, required),
(4, excess_final_cltv_expiry_delta, required),
(6, final_value_msat, required),
/// A route directs a payment from the sender (us) to the recipient. If the recipient supports MPP,
/// it can take multiple paths. Each path is composed of one or more hops through the network.
-#[derive(Clone, Hash, PartialEq, Eq)]
+#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct Route {
/// The list of [`Path`]s taken for a single (potentially-)multi-part payment. If no
/// [`BlindedTail`]s are present, then the pubkey of the last [`RouteHop`] in each path must be
/// the same.
pub paths: Vec<Path>,
- /// The `payment_params` parameter passed to [`find_route`].
- /// This is used by `ChannelManager` to track information which may be required for retries,
- /// provided back to you via [`Event::PaymentPathFailed`].
+ /// The `route_params` parameter passed to [`find_route`].
+ ///
+ /// This is used by `ChannelManager` to track information which may be required for retries.
///
- /// [`Event::PaymentPathFailed`]: crate::events::Event::PaymentPathFailed
- pub payment_params: Option<PaymentParameters>,
+ /// Will be `None` for objects serialized with LDK versions prior to 0.0.117.
+ pub route_params: Option<RouteParameters>,
}
impl Route {
/// Returns the total amount of fees paid on this [`Route`].
///
- /// This doesn't include any extra payment made to the recipient, which can happen in excess of
- /// the amount passed to [`find_route`]'s `params.final_value_msat`.
+ /// For objects serialized with LDK 0.0.117 and after, this includes any extra payment made to
+ /// the recipient, which can happen in excess of the amount passed to [`find_route`] via
+ /// [`RouteParameters::final_value_msat`], if we had to reach the [`htlc_minimum_msat`] limits.
+ ///
+ /// [`htlc_minimum_msat`]: https://github.com/lightning/bolts/blob/master/07-routing-gossip.md#the-channel_update-message
pub fn get_total_fees(&self) -> u64 {
- self.paths.iter().map(|path| path.fee_msat()).sum()
+ let overpaid_value_msat = self.route_params.as_ref()
+ .map_or(0, |p| self.get_total_amount().saturating_sub(p.final_value_msat));
+ overpaid_value_msat + self.paths.iter().map(|path| path.fee_msat()).sum::<u64>()
}
- /// Returns the total amount paid on this [`Route`], excluding the fees. Might be more than
- /// requested if we had to reach htlc_minimum_msat.
+ /// Returns the total amount paid on this [`Route`], excluding the fees.
+ ///
+ /// Might be more than requested as part of the given [`RouteParameters::final_value_msat`] if
+ /// we had to reach the [`htlc_minimum_msat`] limits.
+ ///
+ /// [`htlc_minimum_msat`]: https://github.com/lightning/bolts/blob/master/07-routing-gossip.md#the-channel_update-message
pub fn get_total_amount(&self) -> u64 {
self.paths.iter().map(|path| path.final_value_msat()).sum()
}
}
+impl fmt::Display for Route {
+ fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ log_route!(self).fmt(f)
+ }
+}
+
const SERIALIZATION_VERSION: u8 = 1;
const MIN_SERIALIZATION_VERSION: u8 = 1;
}
}
write_tlv_fields!(writer, {
- (1, self.payment_params, option),
+ // For compatibility with LDK versions prior to 0.0.117, we take the individual
+ // RouteParameters' fields and reconstruct them on read.
+ (1, self.route_params.as_ref().map(|p| &p.payment_params), option),
(2, blinded_tails, optional_vec),
+ (3, self.route_params.as_ref().map(|p| p.final_value_msat), option),
+ (5, self.route_params.as_ref().map(|p| p.max_total_routing_fee_msat), option),
});
Ok(())
}
cmp::min(min_final_cltv_expiry_delta, hops.last().unwrap().cltv_expiry_delta);
paths.push(Path { hops, blinded_tail: None });
}
- _init_and_read_tlv_fields!(reader, {
+ _init_and_read_len_prefixed_tlv_fields!(reader, {
(1, payment_params, (option: ReadableArgs, min_final_cltv_expiry_delta)),
(2, blinded_tails, optional_vec),
+ (3, final_value_msat, option),
+ (5, max_total_routing_fee_msat, option)
});
let blinded_tails = blinded_tails.unwrap_or(Vec::new());
if blinded_tails.len() != 0 {
path.blinded_tail = blinded_tail_opt;
}
}
- Ok(Route { paths, payment_params })
+
+ // If we previously wrote the corresponding fields, reconstruct RouteParameters.
+ let route_params = match (payment_params, final_value_msat) {
+ (Some(payment_params), Some(final_value_msat)) => {
+ Some(RouteParameters { payment_params, final_value_msat, max_total_routing_fee_msat })
+ }
+ _ => None,
+ };
+
+ Ok(Route { paths, route_params })
}
}
/// Parameters needed to find a [`Route`].
///
-/// Passed to [`find_route`] and [`build_route_from_hops`], but also provided in
-/// [`Event::PaymentPathFailed`].
-///
-/// [`Event::PaymentPathFailed`]: crate::events::Event::PaymentPathFailed
-#[derive(Clone, Debug, PartialEq, Eq)]
+/// Passed to [`find_route`] and [`build_route_from_hops`].
+#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct RouteParameters {
/// The parameters of the failed payment path.
pub payment_params: PaymentParameters,
/// The amount in msats sent on the failed payment path.
pub final_value_msat: u64,
+
+ /// The maximum total fees, in millisatoshi, that may accrue during route finding.
+ ///
+ /// This limit also applies to the total fees that may arise while retrying failed payment
+ /// paths.
+ ///
+ /// Note that values below a few sats may result in some paths being spuriously ignored.
+ pub max_total_routing_fee_msat: Option<u64>,
+}
+
+impl RouteParameters {
+ /// Constructs [`RouteParameters`] from the given [`PaymentParameters`] and a payment amount.
+ ///
+ /// [`Self::max_total_routing_fee_msat`] defaults to 1% of the payment amount + 50 sats
+ pub fn from_payment_params_and_value(payment_params: PaymentParameters, final_value_msat: u64) -> Self {
+ Self { payment_params, final_value_msat, max_total_routing_fee_msat: Some(final_value_msat / 100 + 50_000) }
+ }
}
impl Writeable for RouteParameters {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
write_tlv_fields!(writer, {
(0, self.payment_params, required),
+ (1, self.max_total_routing_fee_msat, option),
(2, self.final_value_msat, required),
// LDK versions prior to 0.0.114 had the `final_cltv_expiry_delta` parameter in
// `RouteParameters` directly. For compatibility, we write it here.
impl Readable for RouteParameters {
fn read<R: io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
- _init_and_read_tlv_fields!(reader, {
+ _init_and_read_len_prefixed_tlv_fields!(reader, {
(0, payment_params, (required: ReadableArgs, 0)),
+ (1, max_total_routing_fee_msat, option),
(2, final_value_msat, required),
(4, final_cltv_delta, option),
});
Ok(Self {
payment_params,
final_value_msat: final_value_msat.0.unwrap(),
+ max_total_routing_fee_msat,
})
}
}
(1, self.max_total_cltv_expiry_delta, required),
(2, self.payee.features(), option),
(3, self.max_path_count, required),
- (4, *clear_hints, vec_type),
+ (4, *clear_hints, required_vec),
(5, self.max_channel_saturation_power_of_half, required),
(6, self.expiry_time, option),
- (7, self.previously_failed_channels, vec_type),
+ (7, self.previously_failed_channels, required_vec),
(8, *blinded_hints, optional_vec),
(9, self.payee.final_cltv_expiry_delta(), option),
});
impl ReadableArgs<u32> for PaymentParameters {
fn read<R: io::Read>(reader: &mut R, default_final_cltv_expiry_delta: u32) -> Result<Self, DecodeError> {
- _init_and_read_tlv_fields!(reader, {
+ _init_and_read_len_prefixed_tlv_fields!(reader, {
(0, payee_pubkey, option),
(1, max_total_cltv_expiry_delta, (default_value, DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA)),
(2, features, (option: ReadableArgs, payee_pubkey.is_some())),
(3, max_path_count, (default_value, DEFAULT_MAX_PATH_COUNT)),
- (4, route_hints, vec_type),
+ (4, clear_route_hints, required_vec),
(5, max_channel_saturation_power_of_half, (default_value, DEFAULT_MAX_CHANNEL_SATURATION_POW_HALF)),
(6, expiry_time, option),
- (7, previously_failed_channels, vec_type),
+ (7, previously_failed_channels, optional_vec),
(8, blinded_route_hints, optional_vec),
(9, final_cltv_expiry_delta, (default_value, default_final_cltv_expiry_delta)),
});
- let clear_route_hints = route_hints.unwrap_or(vec![]);
let blinded_route_hints = blinded_route_hints.unwrap_or(vec![]);
let payee = if blinded_route_hints.len() != 0 {
if clear_route_hints.len() != 0 || payee_pubkey.is_some() { return Err(DecodeError::InvalidValue) }
/// [`RecipientOnionFields::secret_only`]: crate::ln::channelmanager::RecipientOnionFields::secret_only
pub fn for_keysend(payee_pubkey: PublicKey, final_cltv_expiry_delta: u32, allow_mpp: bool) -> Self {
Self::from_node_id(payee_pubkey, final_cltv_expiry_delta)
- .with_bolt11_features(InvoiceFeatures::for_keysend(allow_mpp))
+ .with_bolt11_features(Bolt11InvoiceFeatures::for_keysend(allow_mpp))
.expect("PaymentParameters::from_node_id should always initialize the payee as unblinded")
}
/// [`PaymentParameters::expiry_time`].
pub fn from_bolt12_invoice(invoice: &Bolt12Invoice) -> Self {
Self::blinded(invoice.payment_paths().to_vec())
- .with_bolt12_features(invoice.features().clone()).unwrap()
+ .with_bolt12_features(invoice.invoice_features().clone()).unwrap()
.with_expiry_time(invoice.created_at().as_secs().saturating_add(invoice.relative_expiry().as_secs()))
}
- fn blinded(blinded_route_hints: Vec<(BlindedPayInfo, BlindedPath)>) -> Self {
+ /// Creates parameters for paying to a blinded payee from the provided blinded route hints.
+ pub fn blinded(blinded_route_hints: Vec<(BlindedPayInfo, BlindedPath)>) -> Self {
Self {
payee: Payee::Blinded { route_hints: blinded_route_hints, features: None },
expiry_time: None,
/// [`PaymentParameters::from_bolt12_invoice`].
///
/// This is not exported to bindings users since bindings don't support move semantics
- pub fn with_bolt11_features(self, features: InvoiceFeatures) -> Result<Self, ()> {
+ pub fn with_bolt11_features(self, features: Bolt11InvoiceFeatures) -> Result<Self, ()> {
match self.payee {
Payee::Blinded { .. } => Err(()),
Payee::Clear { route_hints, node_id, final_cltv_expiry_delta, .. } =>
/// does not contain any features.
///
/// [`for_keysend`]: PaymentParameters::for_keysend
- features: Option<InvoiceFeatures>,
+ features: Option<Bolt11InvoiceFeatures>,
/// The minimum CLTV delta at the end of the route. This value must not be zero.
final_cltv_expiry_delta: u32,
},
}
enum FeaturesRef<'a> {
- Bolt11(&'a InvoiceFeatures),
+ Bolt11(&'a Bolt11InvoiceFeatures),
Bolt12(&'a Bolt12InvoiceFeatures),
}
enum Features {
- Bolt11(InvoiceFeatures),
+ Bolt11(Bolt11InvoiceFeatures),
Bolt12(Bolt12InvoiceFeatures),
}
_ => None,
}
}
- fn bolt11(self) -> Option<InvoiceFeatures> {
+ fn bolt11(self) -> Option<Bolt11InvoiceFeatures> {
match self {
Self::Bolt11(f) => Some(f),
_ => None,
// Note that this function is not aware of the available_liquidity limit, and thus does not
// support increasing the value being transferred beyond what was selected during the initial
// routing passes.
- fn update_value_and_recompute_fees(&mut self, value_msat: u64) {
+ //
+ // Returns the amount that this path contributes to the total payment value, which may be greater
+ // than `value_msat` if we had to overpay to meet the final node's `htlc_minimum_msat`.
+ fn update_value_and_recompute_fees(&mut self, value_msat: u64) -> u64 {
+ let mut extra_contribution_msat = 0;
let mut total_fee_paid_msat = 0 as u64;
for i in (0..self.hops.len()).rev() {
let last_hop = i == self.hops.len() - 1;
let cur_hop = &mut self.hops.get_mut(i).unwrap().0;
cur_hop.next_hops_fee_msat = total_fee_paid_msat;
+ cur_hop.path_penalty_msat += extra_contribution_msat;
// Overpay in fees if we can't save these funds due to htlc_minimum_msat.
// We try to account for htlc_minimum_msat in scoring (add_entry!), so that nodes don't
// set it too high just to maliciously take more fees by exploiting this
// Also, this can't be exploited more heavily than *announce a free path and fail
// all payments*.
cur_hop_transferred_amount_msat += extra_fees_msat;
- total_fee_paid_msat += extra_fees_msat;
- cur_hop_fees_msat += extra_fees_msat;
+
+ // We remember and return the extra fees on the final hop to allow accounting for
+ // them in the path's value contribution.
+ if last_hop {
+ extra_contribution_msat = extra_fees_msat;
+ } else {
+ total_fee_paid_msat += extra_fees_msat;
+ cur_hop_fees_msat += extra_fees_msat;
+ }
}
if last_hop {
}
}
}
+ value_msat + extra_contribution_msat
}
}
" and blinding point ".fmt(f)?;
hint.1.blinding_point.fmt(f)
},
+ CandidateRouteHop::FirstHop { .. } => {
+ "first hop with SCID ".fmt(f)?;
+ self.0.short_channel_id().unwrap().fmt(f)
+ },
+ CandidateRouteHop::PrivateHop { .. } => {
+ "route hint with SCID ".fmt(f)?;
+ self.0.short_channel_id().unwrap().fmt(f)
+ },
_ => {
"SCID ".fmt(f)?;
self.0.short_channel_id().unwrap().fmt(f)
/// Finds a route from us (payer) to the given target node (payee).
///
-/// If the payee provided features in their invoice, they should be provided via `params.payee`.
+/// If the payee provided features in their invoice, they should be provided via the `payee` field
+/// in the given [`RouteParameters::payment_params`].
/// Without this, MPP will only be used if the payee's features are available in the network graph.
///
-/// Private routing paths between a public node and the target may be included in `params.payee`.
+/// Private routing paths between a public node and the target may be included in the `payee` field
+/// of [`RouteParameters::payment_params`].
///
/// If some channels aren't announced, it may be useful to fill in `first_hops` with the results
/// from [`ChannelManager::list_usable_channels`]. If it is filled in, the view of these channels
/// However, the enabled/disabled bit on such channels as well as the `htlc_minimum_msat` /
/// `htlc_maximum_msat` *are* checked as they may change based on the receiving node.
///
-/// # Note
-///
-/// May be used to re-compute a [`Route`] when handling a [`Event::PaymentPathFailed`]. Any
-/// adjustments to the [`NetworkGraph`] and channel scores should be made prior to calling this
-/// function.
-///
/// # Panics
///
-/// Panics if first_hops contains channels without short_channel_ids;
+/// Panics if first_hops contains channels without `short_channel_id`s;
/// [`ChannelManager::list_usable_channels`] will never include such channels.
///
/// [`ChannelManager::list_usable_channels`]: crate::ln::channelmanager::ChannelManager::list_usable_channels
/// [`Event::PaymentPathFailed`]: crate::events::Event::PaymentPathFailed
/// [`NetworkGraph`]: crate::routing::gossip::NetworkGraph
-pub fn find_route<L: Deref, GL: Deref, S: Score>(
+pub fn find_route<L: Deref, GL: Deref, S: ScoreLookUp>(
our_node_pubkey: &PublicKey, route_params: &RouteParameters,
network_graph: &NetworkGraph<GL>, first_hops: Option<&[&ChannelDetails]>, logger: L,
scorer: &S, score_params: &S::ScoreParams, random_seed_bytes: &[u8; 32]
) -> Result<Route, LightningError>
where L::Target: Logger, GL::Target: Logger {
let graph_lock = network_graph.read_only();
- let mut route = get_route(our_node_pubkey, &route_params.payment_params, &graph_lock, first_hops,
- route_params.final_value_msat, logger, scorer, score_params,
- random_seed_bytes)?;
+ let mut route = get_route(our_node_pubkey, &route_params, &graph_lock, first_hops, logger,
+ scorer, score_params, random_seed_bytes)?;
add_random_cltv_offset(&mut route, &route_params.payment_params, &graph_lock, random_seed_bytes);
Ok(route)
}
-pub(crate) fn get_route<L: Deref, S: Score>(
- our_node_pubkey: &PublicKey, payment_params: &PaymentParameters, network_graph: &ReadOnlyNetworkGraph,
- first_hops: Option<&[&ChannelDetails]>, final_value_msat: u64, logger: L, scorer: &S, score_params: &S::ScoreParams,
+pub(crate) fn get_route<L: Deref, S: ScoreLookUp>(
+ our_node_pubkey: &PublicKey, route_params: &RouteParameters, network_graph: &ReadOnlyNetworkGraph,
+ first_hops: Option<&[&ChannelDetails]>, logger: L, scorer: &S, score_params: &S::ScoreParams,
_random_seed_bytes: &[u8; 32]
) -> Result<Route, LightningError>
where L::Target: Logger {
+
+ let payment_params = &route_params.payment_params;
+ let final_value_msat = route_params.final_value_msat;
// If we're routing to a blinded recipient, we won't have their node id. Therefore, keep the
// unblinded payee id as an option. We also need a non-optional "payee id" for path construction,
// so use a dummy id for this in the blinded case.
if payee_node_id_opt.map_or(false, |payee| payee == our_node_id) {
return Err(LightningError{err: "Cannot generate a route to ourselves".to_owned(), action: ErrorAction::IgnoreError});
}
+ if our_node_id == maybe_dummy_payee_node_id {
+ return Err(LightningError{err: "Invalid origin node id provided, use a different one".to_owned(), action: ErrorAction::IgnoreError});
+ }
if final_value_msat > MAX_VALUE_MSAT {
return Err(LightningError{err: "Cannot generate a route of more value than all existing satoshis".to_owned(), action: ErrorAction::IgnoreError});
log_trace!(logger, "Building path from {} to payer {} for value {} msat.",
LoggedPayeePubkey(payment_params.payee.node_id()), our_node_pubkey, final_value_msat);
+ // Remember how many candidates we ignored to allow for some logging afterwards.
+ let mut num_ignored_value_contribution = 0;
+ let mut num_ignored_path_length_limit = 0;
+ let mut num_ignored_cltv_delta_limit = 0;
+ let mut num_ignored_previously_failed = 0;
+ let mut num_ignored_total_fee_limit = 0;
+
macro_rules! add_entry {
// Adds entry which goes from $src_node_id to $dest_node_id over the $candidate hop.
// $next_hops_fee_msat represents the fees paid for using all the channels *after* this one,
// since that value has to be transferred over this channel.
- // Returns whether this channel caused an update to `targets`.
+ // Returns the contribution amount of $candidate if the channel caused an update to `targets`.
( $candidate: expr, $src_node_id: expr, $dest_node_id: expr, $next_hops_fee_msat: expr,
$next_hops_value_contribution: expr, $next_hops_path_htlc_minimum_msat: expr,
$next_hops_path_penalty_msat: expr, $next_hops_cltv_delta: expr, $next_hops_path_length: expr ) => { {
// We "return" whether we updated the path at the end, and how much we can route via
// this channel, via this:
- let mut did_add_update_path_to_src_node = None;
+ let mut hop_contribution_amt_msat = None;
// Channels to self should not be used. This is more of belt-and-suspenders, because in
// practice these cases should be caught earlier:
// - for regular channels at channel announcement (TODO)
let payment_failed_on_this_channel = scid_opt.map_or(false,
|scid| payment_params.previously_failed_channels.contains(&scid));
+ let should_log_candidate = match $candidate {
+ CandidateRouteHop::FirstHop { .. } => true,
+ CandidateRouteHop::PrivateHop { .. } => true,
+ CandidateRouteHop::Blinded { .. } => true,
+ CandidateRouteHop::OneHopBlinded { .. } => true,
+ _ => false,
+ };
+
// If HTLC minimum is larger than the amount we're going to transfer, we shouldn't
// bother considering this channel. If retrying with recommended_value_msat may
// allow us to hit the HTLC minimum limit, set htlc_minimum_limit so that we go
// around again with a higher amount.
- if !contributes_sufficient_value || exceeds_max_path_length ||
- exceeds_cltv_delta_limit || payment_failed_on_this_channel {
- // Path isn't useful, ignore it and move on.
+ if !contributes_sufficient_value {
+ if should_log_candidate {
+ log_trace!(logger, "Ignoring {} due to insufficient value contribution.", LoggedCandidateHop(&$candidate));
+ }
+ num_ignored_value_contribution += 1;
+ } else if exceeds_max_path_length {
+ if should_log_candidate {
+ log_trace!(logger, "Ignoring {} due to exceeding maximum path length limit.", LoggedCandidateHop(&$candidate));
+ }
+ num_ignored_path_length_limit += 1;
+ } else if exceeds_cltv_delta_limit {
+ if should_log_candidate {
+ log_trace!(logger, "Ignoring {} due to exceeding CLTV delta limit.", LoggedCandidateHop(&$candidate));
+ }
+ num_ignored_cltv_delta_limit += 1;
+ } else if payment_failed_on_this_channel {
+ if should_log_candidate {
+ log_trace!(logger, "Ignoring {} due to a failed previous payment attempt.", LoggedCandidateHop(&$candidate));
+ }
+ num_ignored_previously_failed += 1;
} else if may_overpay_to_meet_path_minimum_msat {
hit_minimum_limit = true;
} else if over_path_minimum_msat {
// might violate htlc_minimum_msat on the hops which are next along the
// payment path (upstream to the payee). To avoid that, we recompute
// path fees knowing the final path contribution after constructing it.
- let path_htlc_minimum_msat = cmp::max(
- compute_fees_saturating($next_hops_path_htlc_minimum_msat, $candidate.fees())
- .saturating_add($next_hops_path_htlc_minimum_msat),
- $candidate.htlc_minimum_msat());
+ let curr_min = cmp::max(
+ $next_hops_path_htlc_minimum_msat, $candidate.htlc_minimum_msat()
+ );
+ let path_htlc_minimum_msat = compute_fees_saturating(curr_min, $candidate.fees())
+ .saturating_add(curr_min);
let hm_entry = dist.entry($src_node_id);
let old_entry = hm_entry.or_insert_with(|| {
// If there was previously no known way to access the source node
total_fee_msat = total_fee_msat.saturating_add(hop_use_fee_msat);
}
- let channel_usage = ChannelUsage {
- amount_msat: amount_to_transfer_over_msat,
- inflight_htlc_msat: used_liquidity_msat,
- effective_capacity,
- };
- let channel_penalty_msat = scid_opt.map_or(0,
- |scid| scorer.channel_penalty_msat(scid, &$src_node_id, &$dest_node_id,
- channel_usage, score_params));
- let path_penalty_msat = $next_hops_path_penalty_msat
- .saturating_add(channel_penalty_msat);
- let new_graph_node = RouteGraphNode {
- node_id: $src_node_id,
- lowest_fee_to_node: total_fee_msat,
- total_cltv_delta: hop_total_cltv_delta,
- value_contribution_msat,
- path_htlc_minimum_msat,
- path_penalty_msat,
- path_length_to_node,
- };
-
- // Update the way of reaching $src_node_id with the given short_channel_id (from $dest_node_id),
- // if this way is cheaper than the already known
- // (considering the cost to "reach" this channel from the route destination,
- // the cost of using this channel,
- // and the cost of routing to the source node of this channel).
- // Also, consider that htlc_minimum_msat_difference, because we might end up
- // paying it. Consider the following exploit:
- // we use 2 paths to transfer 1.5 BTC. One of them is 0-fee normal 1 BTC path,
- // and for the other one we picked a 1sat-fee path with htlc_minimum_msat of
- // 1 BTC. Now, since the latter is more expensive, we gonna try to cut it
- // by 0.5 BTC, but then match htlc_minimum_msat by paying a fee of 0.5 BTC
- // to this channel.
- // Ideally the scoring could be smarter (e.g. 0.5*htlc_minimum_msat here),
- // but it may require additional tracking - we don't want to double-count
- // the fees included in $next_hops_path_htlc_minimum_msat, but also
- // can't use something that may decrease on future hops.
- let old_cost = cmp::max(old_entry.total_fee_msat, old_entry.path_htlc_minimum_msat)
- .saturating_add(old_entry.path_penalty_msat);
- let new_cost = cmp::max(total_fee_msat, path_htlc_minimum_msat)
- .saturating_add(path_penalty_msat);
-
- if !old_entry.was_processed && new_cost < old_cost {
- targets.push(new_graph_node);
- old_entry.next_hops_fee_msat = $next_hops_fee_msat;
- old_entry.hop_use_fee_msat = hop_use_fee_msat;
- old_entry.total_fee_msat = total_fee_msat;
- old_entry.node_id = $dest_node_id.clone();
- old_entry.candidate = $candidate.clone();
- old_entry.fee_msat = 0; // This value will be later filled with hop_use_fee_msat of the following channel
- old_entry.path_htlc_minimum_msat = path_htlc_minimum_msat;
- old_entry.path_penalty_msat = path_penalty_msat;
- #[cfg(all(not(ldk_bench), any(test, fuzzing)))]
- {
- old_entry.value_contribution_msat = value_contribution_msat;
+ // Ignore hops if augmenting the current path to them would put us over `max_total_routing_fee_msat`
+ let max_total_routing_fee_msat = route_params.max_total_routing_fee_msat.unwrap_or(u64::max_value());
+ if total_fee_msat > max_total_routing_fee_msat {
+ if should_log_candidate {
+ log_trace!(logger, "Ignoring {} due to exceeding max total routing fee limit.", LoggedCandidateHop(&$candidate));
}
- did_add_update_path_to_src_node = Some(value_contribution_msat);
- } else if old_entry.was_processed && new_cost < old_cost {
- #[cfg(all(not(ldk_bench), any(test, fuzzing)))]
- {
- // If we're skipping processing a node which was previously
- // processed even though we found another path to it with a
- // cheaper fee, check that it was because the second path we
- // found (which we are processing now) has a lower value
- // contribution due to an HTLC minimum limit.
- //
- // e.g. take a graph with two paths from node 1 to node 2, one
- // through channel A, and one through channel B. Channel A and
- // B are both in the to-process heap, with their scores set by
- // a higher htlc_minimum than fee.
- // Channel A is processed first, and the channels onwards from
- // node 1 are added to the to-process heap. Thereafter, we pop
- // Channel B off of the heap, note that it has a much more
- // restrictive htlc_maximum_msat, and recalculate the fees for
- // all of node 1's channels using the new, reduced, amount.
- //
- // This would be bogus - we'd be selecting a higher-fee path
- // with a lower htlc_maximum_msat instead of the one we'd
- // already decided to use.
- debug_assert!(path_htlc_minimum_msat < old_entry.path_htlc_minimum_msat);
- debug_assert!(
- value_contribution_msat + path_penalty_msat <
- old_entry.value_contribution_msat + old_entry.path_penalty_msat
- );
+ num_ignored_total_fee_limit += 1;
+ } else {
+ let channel_usage = ChannelUsage {
+ amount_msat: amount_to_transfer_over_msat,
+ inflight_htlc_msat: used_liquidity_msat,
+ effective_capacity,
+ };
+ let channel_penalty_msat = scid_opt.map_or(0,
+ |scid| scorer.channel_penalty_msat(scid, &$src_node_id, &$dest_node_id,
+ channel_usage, score_params));
+ let path_penalty_msat = $next_hops_path_penalty_msat
+ .saturating_add(channel_penalty_msat);
+ let new_graph_node = RouteGraphNode {
+ node_id: $src_node_id,
+ lowest_fee_to_node: total_fee_msat,
+ total_cltv_delta: hop_total_cltv_delta,
+ value_contribution_msat,
+ path_htlc_minimum_msat,
+ path_penalty_msat,
+ path_length_to_node,
+ };
+
+ // Update the way of reaching $src_node_id with the given short_channel_id (from $dest_node_id),
+ // if this way is cheaper than the already known
+ // (considering the cost to "reach" this channel from the route destination,
+ // the cost of using this channel,
+ // and the cost of routing to the source node of this channel).
+ // Also, consider that htlc_minimum_msat_difference, because we might end up
+ // paying it. Consider the following exploit:
+ // we use 2 paths to transfer 1.5 BTC. One of them is 0-fee normal 1 BTC path,
+ // and for the other one we picked a 1sat-fee path with htlc_minimum_msat of
+ // 1 BTC. Now, since the latter is more expensive, we gonna try to cut it
+ // by 0.5 BTC, but then match htlc_minimum_msat by paying a fee of 0.5 BTC
+ // to this channel.
+ // Ideally the scoring could be smarter (e.g. 0.5*htlc_minimum_msat here),
+ // but it may require additional tracking - we don't want to double-count
+ // the fees included in $next_hops_path_htlc_minimum_msat, but also
+ // can't use something that may decrease on future hops.
+ let old_cost = cmp::max(old_entry.total_fee_msat, old_entry.path_htlc_minimum_msat)
+ .saturating_add(old_entry.path_penalty_msat);
+ let new_cost = cmp::max(total_fee_msat, path_htlc_minimum_msat)
+ .saturating_add(path_penalty_msat);
+
+ if !old_entry.was_processed && new_cost < old_cost {
+ targets.push(new_graph_node);
+ old_entry.next_hops_fee_msat = $next_hops_fee_msat;
+ old_entry.hop_use_fee_msat = hop_use_fee_msat;
+ old_entry.total_fee_msat = total_fee_msat;
+ old_entry.node_id = $dest_node_id.clone();
+ old_entry.candidate = $candidate.clone();
+ old_entry.fee_msat = 0; // This value will be later filled with hop_use_fee_msat of the following channel
+ old_entry.path_htlc_minimum_msat = path_htlc_minimum_msat;
+ old_entry.path_penalty_msat = path_penalty_msat;
+ #[cfg(all(not(ldk_bench), any(test, fuzzing)))]
+ {
+ old_entry.value_contribution_msat = value_contribution_msat;
+ }
+ hop_contribution_amt_msat = Some(value_contribution_msat);
+ } else if old_entry.was_processed && new_cost < old_cost {
+ #[cfg(all(not(ldk_bench), any(test, fuzzing)))]
+ {
+ // If we're skipping processing a node which was previously
+ // processed even though we found another path to it with a
+ // cheaper fee, check that it was because the second path we
+ // found (which we are processing now) has a lower value
+ // contribution due to an HTLC minimum limit.
+ //
+ // e.g. take a graph with two paths from node 1 to node 2, one
+ // through channel A, and one through channel B. Channel A and
+ // B are both in the to-process heap, with their scores set by
+ // a higher htlc_minimum than fee.
+ // Channel A is processed first, and the channels onwards from
+ // node 1 are added to the to-process heap. Thereafter, we pop
+ // Channel B off of the heap, note that it has a much more
+ // restrictive htlc_maximum_msat, and recalculate the fees for
+ // all of node 1's channels using the new, reduced, amount.
+ //
+ // This would be bogus - we'd be selecting a higher-fee path
+ // with a lower htlc_maximum_msat instead of the one we'd
+ // already decided to use.
+ debug_assert!(path_htlc_minimum_msat < old_entry.path_htlc_minimum_msat);
+ debug_assert!(
+ value_contribution_msat + path_penalty_msat <
+ old_entry.value_contribution_msat + old_entry.path_penalty_msat
+ );
+ }
}
}
}
}
}
}
- did_add_update_path_to_src_node
+ hop_contribution_amt_msat
} }
}
// in the regular network graph.
first_hop_targets.get(&intro_node_id).is_some() ||
network_nodes.get(&intro_node_id).is_some();
- if !have_intro_node_in_graph { continue }
+ if !have_intro_node_in_graph || our_node_id == intro_node_id { continue }
let candidate = if hint.1.blinded_hops.len() == 1 {
CandidateRouteHop::OneHopBlinded { hint, hint_idx }
} else { CandidateRouteHop::Blinded { hint, hint_idx } };
our_node_pubkey);
for details in first_channels {
let first_hop_candidate = CandidateRouteHop::FirstHop { details };
- add_entry!(first_hop_candidate, our_node_id, intro_node_id, 0, path_contribution_msat, 0,
- 0_u64, 0, 0);
+ let blinded_path_fee = match compute_fees(path_contribution_msat, candidate.fees()) {
+ Some(fee) => fee,
+ None => continue
+ };
+ let path_min = candidate.htlc_minimum_msat().saturating_add(
+ compute_fees_saturating(candidate.htlc_minimum_msat(), candidate.fees()));
+ add_entry!(first_hop_candidate, our_node_id, intro_node_id, blinded_path_fee,
+ path_contribution_msat, path_min, 0_u64, candidate.cltv_expiry_delta(),
+ candidate.blinded_path().map_or(1, |bp| bp.blinded_hops.len() as u8));
}
}
}
.map_or(None, |inc| inc.checked_add(aggregate_next_hops_fee_msat));
aggregate_next_hops_fee_msat = if let Some(val) = hops_fee { val } else { break; };
- let hop_htlc_minimum_msat = candidate.htlc_minimum_msat();
- let hop_htlc_minimum_msat_inc = if let Some(val) = compute_fees(aggregate_next_hops_path_htlc_minimum_msat, hop.fees) { val } else { break; };
- let hops_path_htlc_minimum = aggregate_next_hops_path_htlc_minimum_msat
- .checked_add(hop_htlc_minimum_msat_inc);
- aggregate_next_hops_path_htlc_minimum_msat = if let Some(val) = hops_path_htlc_minimum { cmp::max(hop_htlc_minimum_msat, val) } else { break; };
+ // The next channel will need to relay this channel's min_htlc *plus* the fees taken by
+ // this route hint's source node to forward said min over this channel.
+ aggregate_next_hops_path_htlc_minimum_msat = {
+ let curr_htlc_min = cmp::max(
+ candidate.htlc_minimum_msat(), aggregate_next_hops_path_htlc_minimum_msat
+ );
+ let curr_htlc_min_fee = if let Some(val) = compute_fees(curr_htlc_min, hop.fees) { val } else { break };
+ if let Some(min) = curr_htlc_min.checked_add(curr_htlc_min_fee) { min } else { break }
+ };
if idx == route.0.len() - 1 {
// The last hop in this iterator is the first hop in
// recompute the fees again, so that if that's the case, we match the currently
// underpaid htlc_minimum_msat with fees.
debug_assert_eq!(payment_path.get_value_msat(), value_contribution_msat);
- value_contribution_msat = cmp::min(value_contribution_msat, final_value_msat);
- payment_path.update_value_and_recompute_fees(value_contribution_msat);
+ let desired_value_contribution = cmp::min(value_contribution_msat, final_value_msat);
+ value_contribution_msat = payment_path.update_value_and_recompute_fees(desired_value_contribution);
// Since a path allows to transfer as much value as
// the smallest channel it has ("bottleneck"), we should recompute
// Decrease the available liquidity of a hop in the middle of the path.
let victim_candidate = &payment_path.hops[(payment_path.hops.len()) / 2].0.candidate;
let exhausted = u64::max_value();
- log_trace!(logger, "Disabling route candidate {} for future path building iterations to
- avoid duplicates.", LoggedCandidateHop(victim_candidate));
+ log_trace!(logger,
+ "Disabling route candidate {} for future path building iterations to avoid duplicates.",
+ LoggedCandidateHop(victim_candidate));
*used_liquidities.entry(victim_candidate.id(false)).or_default() = exhausted;
*used_liquidities.entry(victim_candidate.id(true)).or_default() = exhausted;
}
}
}
+ let num_ignored_total = num_ignored_value_contribution + num_ignored_path_length_limit +
+ num_ignored_cltv_delta_limit + num_ignored_previously_failed + num_ignored_total_fee_limit;
+ if num_ignored_total > 0 {
+ log_trace!(logger, "Ignored {} candidate hops due to insufficient value contribution, {} due to path length limit, {} due to CLTV delta limit, {} due to previous payment failure, {} due to maximum total fee limit. Total: {} ignored candidates.", num_ignored_value_contribution, num_ignored_path_length_limit, num_ignored_cltv_delta_limit, num_ignored_previously_failed, num_ignored_total_fee_limit, num_ignored_total);
+ }
+
// Step (5).
if payment_paths.len() == 0 {
return Err(LightningError{err: "Failed to find a path to the given destination".to_owned(), action: ErrorAction::IgnoreError});
let mut paths = Vec::new();
for payment_path in selected_route {
let mut hops = Vec::with_capacity(payment_path.hops.len());
+ let mut prev_hop_node_id = our_node_id;
for (hop, node_features) in payment_path.hops.iter()
.filter(|(h, _)| h.candidate.short_channel_id().is_some())
{
+ let maybe_announced_channel = if let CandidateRouteHop::PublicHop { .. } = hop.candidate {
+ // If we sourced the hop from the graph we're sure the target node is announced.
+ true
+ } else if let CandidateRouteHop::FirstHop { details } = hop.candidate {
+ // If this is a first hop we also know if it's announced.
+ details.is_public
+ } else {
+ // If we sourced it any other way, we double-check the network graph to see if
+ // there are announced channels between the endpoints. If so, the hop might be
+ // referring to any of the announced channels, as its `short_channel_id` might be
+ // an alias, in which case we don't take any chances here.
+ network_graph.node(&hop.node_id).map_or(false, |hop_node|
+ hop_node.channels.iter().any(|scid| network_graph.channel(*scid)
+ .map_or(false, |c| c.as_directed_from(&prev_hop_node_id).is_some()))
+ )
+ };
+
hops.push(RouteHop {
pubkey: PublicKey::from_slice(hop.node_id.as_slice()).map_err(|_| LightningError{err: format!("Public key {:?} is invalid", &hop.node_id), action: ErrorAction::IgnoreAndLog(Level::Trace)})?,
node_features: node_features.clone(),
channel_features: hop.candidate.features(),
fee_msat: hop.fee_msat,
cltv_expiry_delta: hop.candidate.cltv_expiry_delta(),
+ maybe_announced_channel,
});
+
+ prev_hop_node_id = hop.node_id;
}
let mut final_cltv_delta = final_cltv_expiry_delta;
let blinded_tail = payment_path.hops.last().and_then(|(h, _)| {
// Make sure we would never create a route with more paths than we allow.
debug_assert!(paths.len() <= payment_params.max_path_count.into());
+ // Make sure we would never create a route whose total fees exceed max_total_routing_fee_msat.
+ if let Some(max_total_routing_fee_msat) = route_params.max_total_routing_fee_msat {
+ if paths.iter().map(|p| p.fee_msat()).sum::<u64>() > max_total_routing_fee_msat {
+ return Err(LightningError{err: format!("Failed to find route that adheres to the maximum total fee limit of {}msat",
+ max_total_routing_fee_msat), action: ErrorAction::IgnoreError});
+ }
+ }
+
if let Some(node_features) = payment_params.payee.node_features() {
for path in paths.iter_mut() {
path.hops.last_mut().unwrap().node_features = node_features.clone();
}
}
- let route = Route { paths, payment_params: Some(payment_params.clone()) };
+ let route = Route { paths, route_params: Some(route_params.clone()) };
log_info!(logger, "Got route: {}", log_route!(route));
Ok(route)
}
) -> Result<Route, LightningError>
where L::Target: Logger, GL::Target: Logger {
let graph_lock = network_graph.read_only();
- let mut route = build_route_from_hops_internal(
- our_node_pubkey, hops, &route_params.payment_params, &graph_lock,
- route_params.final_value_msat, logger, random_seed_bytes)?;
+ let mut route = build_route_from_hops_internal(our_node_pubkey, hops, &route_params,
+ &graph_lock, logger, random_seed_bytes)?;
add_random_cltv_offset(&mut route, &route_params.payment_params, &graph_lock, random_seed_bytes);
Ok(route)
}
fn build_route_from_hops_internal<L: Deref>(
- our_node_pubkey: &PublicKey, hops: &[PublicKey], payment_params: &PaymentParameters,
- network_graph: &ReadOnlyNetworkGraph, final_value_msat: u64, logger: L,
- random_seed_bytes: &[u8; 32]
+ our_node_pubkey: &PublicKey, hops: &[PublicKey], route_params: &RouteParameters,
+ network_graph: &ReadOnlyNetworkGraph, logger: L, random_seed_bytes: &[u8; 32],
) -> Result<Route, LightningError> where L::Target: Logger {
struct HopScorer {
hop_ids: [Option<NodeId>; MAX_PATH_LENGTH_ESTIMATE as usize],
}
- impl Score for HopScorer {
+ impl ScoreLookUp for HopScorer {
type ScoreParams = ();
fn channel_penalty_msat(&self, _short_channel_id: u64, source: &NodeId, target: &NodeId,
_usage: ChannelUsage, _score_params: &Self::ScoreParams) -> u64
}
u64::max_value()
}
-
- fn payment_path_failed(&mut self, _path: &Path, _short_channel_id: u64) {}
-
- fn payment_path_successful(&mut self, _path: &Path) {}
-
- fn probe_failed(&mut self, _path: &Path, _short_channel_id: u64) {}
-
- fn probe_successful(&mut self, _path: &Path) {}
}
impl<'a> Writeable for HopScorer {
let scorer = HopScorer { our_node_id, hop_ids };
- get_route(our_node_pubkey, payment_params, network_graph, None, final_value_msat,
- logger, &scorer, &(), random_seed_bytes)
+ get_route(our_node_pubkey, route_params, network_graph, None, logger, &scorer, &Default::default(), random_seed_bytes)
}
#[cfg(test)]
use crate::routing::utxo::UtxoResult;
use crate::routing::router::{get_route, build_route_from_hops_internal, add_random_cltv_offset, default_node_features,
BlindedTail, InFlightHtlcs, Path, PaymentParameters, Route, RouteHint, RouteHintHop, RouteHop, RoutingFees,
- DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA, MAX_PATH_LENGTH_ESTIMATE};
- use crate::routing::scoring::{ChannelUsage, FixedPenaltyScorer, Score, ProbabilisticScorer, ProbabilisticScoringFeeParameters, ProbabilisticScoringDecayParameters};
+ DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA, MAX_PATH_LENGTH_ESTIMATE, RouteParameters};
+ use crate::routing::scoring::{ChannelUsage, FixedPenaltyScorer, ScoreLookUp, ProbabilisticScorer, ProbabilisticScoringFeeParameters, ProbabilisticScoringDecayParameters};
use crate::routing::test_utils::{add_channel, add_or_update_node, build_graph, build_line_graph, id_to_feature_flags, get_nodes, update_channel};
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::msgs::{ErrorAction, LightningError, UnsignedChannelUpdate, MAX_VALUE_MSAT};
use crate::ln::channelmanager;
fn get_channel_details(short_channel_id: Option<u64>, node_id: PublicKey,
features: InitFeatures, outbound_capacity_msat: u64) -> channelmanager::ChannelDetails {
channelmanager::ChannelDetails {
- channel_id: [0; 32],
+ channel_id: ChannelId::new_zero(),
counterparty: channelmanager::ChannelCounterparty {
features,
node_id,
inbound_scid_alias: None,
channel_value_satoshis: 0,
user_channel_id: 0,
- balance_msat: 0,
outbound_capacity_msat,
next_outbound_htlc_limit_msat: outbound_capacity_msat,
next_outbound_htlc_minimum_msat: 0,
// Simple route to 2 via 1
- if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 0, Arc::clone(&logger), &scorer, &(), &random_seed_bytes) {
- assert_eq!(err, "Cannot send a payment of 0 msat");
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 0);
+ if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id,
+ &route_params, &network_graph.read_only(), None, Arc::clone(&logger), &scorer,
+ &Default::default(), &random_seed_bytes) {
+ assert_eq!(err, "Cannot send a payment of 0 msat");
} else { panic!(); }
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].hops.len(), 2);
assert_eq!(route.paths[0].hops[0].pubkey, nodes[1]);
let our_chans = vec![get_channel_details(Some(2), our_id, InitFeatures::from_le_bytes(vec![0b11]), 100000)];
- if let Err(LightningError{err, action: ErrorAction::IgnoreError}) =
- get_route(&our_id, &payment_params, &network_graph.read_only(), Some(&our_chans.iter().collect::<Vec<_>>()), 100, Arc::clone(&logger), &scorer, &(), &random_seed_bytes) {
- assert_eq!(err, "First hop cannot have our_node_pubkey as a destination.");
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
+ if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id,
+ &route_params, &network_graph.read_only(), Some(&our_chans.iter().collect::<Vec<_>>()),
+ Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes) {
+ assert_eq!(err, "First hop cannot have our_node_pubkey as a destination.");
} else { panic!(); }
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].hops.len(), 2);
}
});
// Not possible to send 199_999_999, because the minimum on channel=2 is 200_000_000.
- if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 199_999_999, Arc::clone(&logger), &scorer, &(), &random_seed_bytes) {
- assert_eq!(err, "Failed to find a path to the given destination");
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params, 199_999_999);
+ if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id,
+ &route_params, &network_graph.read_only(), None, Arc::clone(&logger), &scorer,
+ &Default::default(), &random_seed_bytes) {
+ assert_eq!(err, "Failed to find a path to the given destination");
} else { panic!(); }
// Lift the restriction on the first hop.
});
// A payment above the minimum should pass
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 199_999_999, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].hops.len(), 2);
}
excess_data: Vec::new()
});
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 60_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let mut route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 60_000);
+ route_params.max_total_routing_fee_msat = Some(15_000);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
// Overpay fees to hit htlc_minimum_msat.
let overpaid_fees = route.paths[0].hops[0].fee_msat + route.paths[1].hops[0].fee_msat;
// TODO: this could be better balanced to overpay 10k and not 15k.
excess_data: Vec::new()
});
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 60_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
// Fine to overpay for htlc_minimum_msat if it allows us to save fee.
assert_eq!(route.paths.len(), 1);
assert_eq!(route.paths[0].hops[0].short_channel_id, 12);
let fees = route.paths[0].hops[0].fee_msat;
assert_eq!(fees, 5_000);
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 50_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, 50_000);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
// Not fine to overpay for htlc_minimum_msat if it requires paying more than fee on
// the other channel.
assert_eq!(route.paths.len(), 1);
});
// If all the channels require some features we don't understand, route should fail
- if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, Arc::clone(&logger), &scorer, &(), &random_seed_bytes) {
- assert_eq!(err, "Failed to find a path to the given destination");
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
+ if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id,
+ &route_params, &network_graph.read_only(), None, Arc::clone(&logger), &scorer,
+ &Default::default(), &random_seed_bytes) {
+ assert_eq!(err, "Failed to find a path to the given destination");
} else { panic!(); }
// If we specify a channel to node7, that overrides our local channel view and that gets used
- let our_chans = vec![get_channel_details(Some(42), nodes[7].clone(), InitFeatures::from_le_bytes(vec![0b11]), 250_000_000)];
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), Some(&our_chans.iter().collect::<Vec<_>>()), 100, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let our_chans = vec![get_channel_details(Some(42), nodes[7].clone(),
+ InitFeatures::from_le_bytes(vec![0b11]), 250_000_000)];
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(),
+ Some(&our_chans.iter().collect::<Vec<_>>()), Arc::clone(&logger), &scorer,
+ &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].hops.len(), 2);
assert_eq!(route.paths[0].hops[0].pubkey, nodes[7]);
add_or_update_node(&gossip_sync, &secp_ctx, &privkeys[7], unknown_features.clone(), 1);
// If all nodes require some features we don't understand, route should fail
- if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, Arc::clone(&logger), &scorer, &(), &random_seed_bytes) {
- assert_eq!(err, "Failed to find a path to the given destination");
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
+ if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id,
+ &route_params, &network_graph.read_only(), None, Arc::clone(&logger), &scorer,
+ &Default::default(), &random_seed_bytes) {
+ assert_eq!(err, "Failed to find a path to the given destination");
} else { panic!(); }
// If we specify a channel to node7, that overrides our local channel view and that gets used
- let our_chans = vec![get_channel_details(Some(42), nodes[7].clone(), InitFeatures::from_le_bytes(vec![0b11]), 250_000_000)];
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), Some(&our_chans.iter().collect::<Vec<_>>()), 100, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let our_chans = vec![get_channel_details(Some(42), nodes[7].clone(),
+ InitFeatures::from_le_bytes(vec![0b11]), 250_000_000)];
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(),
+ Some(&our_chans.iter().collect::<Vec<_>>()), Arc::clone(&logger), &scorer,
+ &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].hops.len(), 2);
assert_eq!(route.paths[0].hops[0].pubkey, nodes[7]);
// Route to 1 via 2 and 3 because our channel to 1 is disabled
let payment_params = PaymentParameters::from_node_id(nodes[0], 42);
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].hops.len(), 3);
assert_eq!(route.paths[0].hops[0].pubkey, nodes[1]);
// If we specify a channel to node7, that overrides our local channel view and that gets used
let payment_params = PaymentParameters::from_node_id(nodes[2], 42);
- let our_chans = vec![get_channel_details(Some(42), nodes[7].clone(), InitFeatures::from_le_bytes(vec![0b11]), 250_000_000)];
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), Some(&our_chans.iter().collect::<Vec<_>>()), 100, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
+ let our_chans = vec![get_channel_details(Some(42), nodes[7].clone(),
+ InitFeatures::from_le_bytes(vec![0b11]), 250_000_000)];
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(),
+ Some(&our_chans.iter().collect::<Vec<_>>()), Arc::clone(&logger), &scorer,
+ &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].hops.len(), 2);
assert_eq!(route.paths[0].hops[0].pubkey, nodes[7]);
let mut invalid_last_hops = last_hops_multi_private_channels(&nodes);
invalid_last_hops.push(invalid_last_hop);
{
- let payment_params = PaymentParameters::from_node_id(nodes[6], 42).with_route_hints(invalid_last_hops).unwrap();
- if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, Arc::clone(&logger), &scorer, &(), &random_seed_bytes) {
- assert_eq!(err, "Route hint cannot have the payee as the source.");
+ let payment_params = PaymentParameters::from_node_id(nodes[6], 42)
+ .with_route_hints(invalid_last_hops).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
+ if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id,
+ &route_params, &network_graph.read_only(), None, Arc::clone(&logger), &scorer,
+ &Default::default(), &random_seed_bytes) {
+ assert_eq!(err, "Route hint cannot have the payee as the source.");
} else { panic!(); }
}
- let payment_params = PaymentParameters::from_node_id(nodes[6], 42).with_route_hints(last_hops_multi_private_channels(&nodes)).unwrap();
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let payment_params = PaymentParameters::from_node_id(nodes[6], 42)
+ .with_route_hints(last_hops_multi_private_channels(&nodes)).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].hops.len(), 5);
assert_eq!(route.paths[0].hops[0].pubkey, nodes[1]);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
// Test handling of an empty RouteHint passed in Invoice.
-
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].hops.len(), 5);
assert_eq!(route.paths[0].hops[0].pubkey, nodes[1]);
excess_data: Vec::new()
});
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].hops.len(), 4);
assert_eq!(route.paths[0].hops[0].pubkey, nodes[1]);
excess_data: Vec::new()
});
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, Arc::clone(&logger), &scorer, &(), &[42u8; 32]).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &Default::default(), &[42u8; 32]).unwrap();
assert_eq!(route.paths[0].hops.len(), 4);
assert_eq!(route.paths[0].hops[0].pubkey, nodes[1]);
// This test shows that public routes can be present in the invoice
// which would be handled in the same manner.
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].hops.len(), 5);
assert_eq!(route.paths[0].hops[0].pubkey, nodes[1]);
// Simple test with outbound channel to 4 to test that last_hops and first_hops connect
let our_chans = vec![get_channel_details(Some(42), nodes[3].clone(), InitFeatures::from_le_bytes(vec![0b11]), 250_000_000)];
let mut last_hops = last_hops(&nodes);
- let payment_params = PaymentParameters::from_node_id(nodes[6], 42).with_route_hints(last_hops.clone()).unwrap();
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), Some(&our_chans.iter().collect::<Vec<_>>()), 100, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let payment_params = PaymentParameters::from_node_id(nodes[6], 42)
+ .with_route_hints(last_hops.clone()).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(),
+ Some(&our_chans.iter().collect::<Vec<_>>()), Arc::clone(&logger), &scorer,
+ &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].hops.len(), 2);
assert_eq!(route.paths[0].hops[0].pubkey, nodes[3]);
last_hops[0].0[0].fees.base_msat = 1000;
// Revert to via 6 as the fee on 8 goes up
- let payment_params = PaymentParameters::from_node_id(nodes[6], 42).with_route_hints(last_hops).unwrap();
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let payment_params = PaymentParameters::from_node_id(nodes[6], 42)
+ .with_route_hints(last_hops).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 100);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].hops.len(), 4);
assert_eq!(route.paths[0].hops[0].pubkey, nodes[1]);
assert_eq!(route.paths[0].hops[3].channel_features.le_flags(), &Vec::<u8>::new()); // We can't learn any flags from invoices, sadly
// ...but still use 8 for larger payments as 6 has a variable feerate
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 2000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, 2000);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].hops.len(), 5);
assert_eq!(route.paths[0].hops[0].pubkey, nodes[1]);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let logger = ln_test_utils::TestLogger::new();
let network_graph = NetworkGraph::new(Network::Testnet, &logger);
- let route = get_route(&source_node_id, &payment_params, &network_graph.read_only(),
- Some(&our_chans.iter().collect::<Vec<_>>()), route_val, &logger, &scorer, &(), &random_seed_bytes);
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, route_val);
+ let route = get_route(&source_node_id, &route_params, &network_graph.read_only(),
+ Some(&our_chans.iter().collect::<Vec<_>>()), &logger, &scorer, &Default::default(),
+ &random_seed_bytes);
route
}
fn available_amount_while_routing_test() {
// Tests whether we choose the correct available channel amount while routing.
- let (secp_ctx, network_graph, mut gossip_sync, chain_monitor, logger) = build_graph();
+ let (secp_ctx, network_graph, gossip_sync, chain_monitor, logger) = build_graph();
let (our_privkey, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
let scorer = ln_test_utils::TestScorer::new();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
{
// Attempt to route more than available results in a failure.
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 250_000_001);
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
- &our_id, &payment_params, &network_graph.read_only(), None, 250_000_001, Arc::clone(&logger), &scorer, &(), &random_seed_bytes) {
- assert_eq!(err, "Failed to find a sufficient route to the given destination");
+ &our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes) {
+ assert_eq!(err, "Failed to find a sufficient route to the given destination");
} else { panic!(); }
}
{
// Now, attempt to route an exact amount we have should be fine.
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 250_000_000, Arc::clone(&logger), &scorer, &(),&random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 250_000_000);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
let path = route.paths.last().unwrap();
assert_eq!(path.hops.len(), 2);
{
// Attempt to route more than available results in a failure.
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 200_000_001);
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
- &our_id, &payment_params, &network_graph.read_only(), Some(&our_chans.iter().collect::<Vec<_>>()), 200_000_001, Arc::clone(&logger), &scorer, &(), &random_seed_bytes) {
- assert_eq!(err, "Failed to find a sufficient route to the given destination");
+ &our_id, &route_params, &network_graph.read_only(),
+ Some(&our_chans.iter().collect::<Vec<_>>()), Arc::clone(&logger), &scorer,
+ &Default::default(), &random_seed_bytes) {
+ assert_eq!(err, "Failed to find a sufficient route to the given destination");
} else { panic!(); }
}
{
// Now, attempt to route an exact amount we have should be fine.
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), Some(&our_chans.iter().collect::<Vec<_>>()), 200_000_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 200_000_000);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(),
+ Some(&our_chans.iter().collect::<Vec<_>>()), Arc::clone(&logger), &scorer,
+ &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
let path = route.paths.last().unwrap();
assert_eq!(path.hops.len(), 2);
{
// Attempt to route more than available results in a failure.
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 15_001);
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
- &our_id, &payment_params, &network_graph.read_only(), None, 15_001, Arc::clone(&logger), &scorer, &(), &random_seed_bytes) {
- assert_eq!(err, "Failed to find a sufficient route to the given destination");
+ &our_id, &route_params, &network_graph.read_only(), None, Arc::clone(&logger),
+ &scorer, &Default::default(), &random_seed_bytes) {
+ assert_eq!(err, "Failed to find a sufficient route to the given destination");
} else { panic!(); }
}
{
// Now, attempt to route an exact amount we have should be fine.
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 15_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 15_000);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
let path = route.paths.last().unwrap();
assert_eq!(path.hops.len(), 2);
{
// Attempt to route more than available results in a failure.
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 15_001);
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
- &our_id, &payment_params, &network_graph.read_only(), None, 15_001, Arc::clone(&logger), &scorer, &(), &random_seed_bytes) {
- assert_eq!(err, "Failed to find a sufficient route to the given destination");
+ &our_id, &route_params, &network_graph.read_only(), None, Arc::clone(&logger),
+ &scorer, &Default::default(), &random_seed_bytes) {
+ assert_eq!(err, "Failed to find a sufficient route to the given destination");
} else { panic!(); }
}
{
// Now, attempt to route an exact amount we have should be fine.
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 15_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 15_000);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
let path = route.paths.last().unwrap();
assert_eq!(path.hops.len(), 2);
{
// Attempt to route more than available results in a failure.
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 10_001);
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
- &our_id, &payment_params, &network_graph.read_only(), None, 10_001, Arc::clone(&logger), &scorer, &(), &random_seed_bytes) {
- assert_eq!(err, "Failed to find a sufficient route to the given destination");
+ &our_id, &route_params, &network_graph.read_only(), None, Arc::clone(&logger),
+ &scorer, &Default::default(), &random_seed_bytes) {
+ assert_eq!(err, "Failed to find a sufficient route to the given destination");
} else { panic!(); }
}
{
// Now, attempt to route an exact amount we have should be fine.
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 10_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 10_000);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
let path = route.paths.last().unwrap();
assert_eq!(path.hops.len(), 2);
});
{
// Attempt to route more than available results in a failure.
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 60_000);
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
- &our_id, &payment_params, &network_graph.read_only(), None, 60_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes) {
- assert_eq!(err, "Failed to find a sufficient route to the given destination");
+ &our_id, &route_params, &network_graph.read_only(), None, Arc::clone(&logger),
+ &scorer, &Default::default(), &random_seed_bytes) {
+ assert_eq!(err, "Failed to find a sufficient route to the given destination");
} else { panic!(); }
}
{
// Now, attempt to route 49 sats (just a bit below the capacity).
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 49_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 49_000);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
let mut total_amount_paid_msat = 0;
for path in &route.paths {
{
// Attempt to route an exact amount is also fine
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 50_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params, 50_000);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
let mut total_amount_paid_msat = 0;
for path in &route.paths {
});
{
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 50_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params, 50_000);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
let mut total_amount_paid_msat = 0;
for path in &route.paths {
{
// Attempt to route more than available results in a failure.
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 300_000);
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
- &our_id, &payment_params, &network_graph.read_only(), None, 300_000,
- Arc::clone(&logger), &scorer, &(), &random_seed_bytes) {
+ &our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes) {
assert_eq!(err, "Failed to find a sufficient route to the given destination");
} else { panic!(); }
}
{
// Attempt to route while setting max_path_count to 0 results in a failure.
let zero_payment_params = payment_params.clone().with_max_path_count(0);
+ let route_params = RouteParameters::from_payment_params_and_value(
+ zero_payment_params, 100);
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
- &our_id, &zero_payment_params, &network_graph.read_only(), None, 100,
- Arc::clone(&logger), &scorer, &(), &random_seed_bytes) {
+ &our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes) {
assert_eq!(err, "Can't find a route with no paths allowed.");
} else { panic!(); }
}
// This is the case because the minimal_value_contribution_msat would require each path
// to account for 1/3 of the total value, which is violated by 2 out of 3 paths.
let fail_payment_params = payment_params.clone().with_max_path_count(3);
+ let route_params = RouteParameters::from_payment_params_and_value(
+ fail_payment_params, 250_000);
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
- &our_id, &fail_payment_params, &network_graph.read_only(), None, 250_000,
- Arc::clone(&logger), &scorer, &(), &random_seed_bytes) {
+ &our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes) {
assert_eq!(err, "Failed to find a sufficient route to the given destination");
} else { panic!(); }
}
{
// Now, attempt to route 250 sats (just a bit below the capacity).
// Our algorithm should provide us with these 3 paths.
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None,
- 250_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 250_000);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 3);
let mut total_amount_paid_msat = 0;
for path in &route.paths {
{
// Attempt to route an exact amount is also fine
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None,
- 290_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 290_000);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 3);
let mut total_amount_paid_msat = 0;
for path in &route.paths {
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_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}.
{
// Attempt to route more than available results in a failure.
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 350_000);
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
- &our_id, &payment_params, &network_graph.read_only(), None, 350_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes) {
- assert_eq!(err, "Failed to find a sufficient route to the given destination");
+ &our_id, &route_params, &network_graph.read_only(), None, Arc::clone(&logger),
+ &scorer, &Default::default(), &random_seed_bytes) {
+ assert_eq!(err, "Failed to find a sufficient route to the given destination");
} else { panic!(); }
}
{
// Now, attempt to route 300 sats (exact amount we can route).
// Our algorithm should provide us with these 3 paths, 100 sats each.
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 300_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params, 300_000);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 3);
let mut total_amount_paid_msat = 0;
{
// Now, attempt to route 180 sats.
// Our algorithm should provide us with these 2 paths.
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 180_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params, 180_000);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 2);
let mut total_value_transferred_msat = 0;
{
// Attempt to route more than available results in a failure.
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 210_000);
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
- &our_id, &payment_params, &network_graph.read_only(), None, 210_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes) {
- assert_eq!(err, "Failed to find a sufficient route to the given destination");
+ &our_id, &route_params, &network_graph.read_only(), None, Arc::clone(&logger),
+ &scorer, &Default::default(), &random_seed_bytes) {
+ assert_eq!(err, "Failed to find a sufficient route to the given destination");
+ } else { panic!(); }
+ }
+
+ {
+ // Attempt to route while setting max_total_routing_fee_msat to 149_999 results in a failure.
+ let route_params = RouteParameters { payment_params: payment_params.clone(), final_value_msat: 200_000,
+ 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) {
+ assert_eq!(err, "Failed to find a sufficient route to the given destination");
} else { panic!(); }
}
{
// Now, attempt to route 200 sats (exact amount we can route).
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 200_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters { payment_params: payment_params.clone(), final_value_msat: 200_000,
+ max_total_routing_fee_msat: Some(150_000) };
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 2);
let mut total_amount_paid_msat = 0;
// Get a route for 100 sats and check that we found the MPP route no problem and didn't
// overpay at all.
- let mut route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params, 100_000);
+ let mut route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 2);
route.paths.sort_by_key(|path| path.hops[0].short_channel_id);
// Paths are manually ordered ordered by SCID, so:
{
// Attempt to route more than available results in a failure.
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 150_000);
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
- &our_id, &payment_params, &network_graph.read_only(), None, 150_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes) {
- assert_eq!(err, "Failed to find a sufficient route to the given destination");
+ &our_id, &route_params, &network_graph.read_only(), None, Arc::clone(&logger),
+ &scorer, &Default::default(), &random_seed_bytes) {
+ assert_eq!(err, "Failed to find a sufficient route to the given destination");
} else { panic!(); }
}
{
// Now, attempt to route 125 sats (just a bit below the capacity of 3 channels).
// Our algorithm should provide us with these 3 paths.
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 125_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 125_000);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 3);
let mut total_amount_paid_msat = 0;
for path in &route.paths {
{
// Attempt to route without the last small cheap channel
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 90_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params, 90_000);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 2);
let mut total_amount_paid_msat = 0;
for path in &route.paths {
{
// Now ensure the route flows simply over nodes 1 and 4 to 6.
- let route = get_route(&our_id, &payment_params, &network.read_only(), None, 10_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params, 10_000);
+ let route = get_route(&our_id, &route_params, &network.read_only(), None,
+ Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
assert_eq!(route.paths[0].hops.len(), 3);
{
// Now, attempt to route 90 sats, which is exactly 90 sats at the last hop, plus the
// 200% fee charged channel 13 in the 1-to-2 direction.
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 90_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let mut route_params = RouteParameters::from_payment_params_and_value(
+ payment_params, 90_000);
+ route_params.max_total_routing_fee_msat = Some(90_000*2);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
assert_eq!(route.paths[0].hops.len(), 2);
// Now, attempt to route 90 sats, hitting the htlc_minimum on channel 4, but
// overshooting the htlc_maximum on channel 2. Thus, we should pick the (absurdly
// expensive) channels 12-13 path.
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 90_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let mut route_params = RouteParameters::from_payment_params_and_value(
+ payment_params, 90_000);
+ route_params.max_total_routing_fee_msat = Some(90_000*2);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
assert_eq!(route.paths[0].hops.len(), 2);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
{
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), Some(&[
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 100_000);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), Some(&[
&get_channel_details(Some(3), nodes[0], channelmanager::provided_init_features(&config), 200_000),
&get_channel_details(Some(2), nodes[0], channelmanager::provided_init_features(&config), 10_000),
- ]), 100_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ ]), Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
assert_eq!(route.paths[0].hops.len(), 1);
assert_eq!(route.paths[0].hops[0].fee_msat, 100_000);
}
{
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), Some(&[
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 100_000);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), Some(&[
&get_channel_details(Some(3), nodes[0], channelmanager::provided_init_features(&config), 50_000),
&get_channel_details(Some(2), nodes[0], channelmanager::provided_init_features(&config), 50_000),
- ]), 100_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ ]), Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 2);
assert_eq!(route.paths[0].hops.len(), 1);
assert_eq!(route.paths[1].hops.len(), 1);
// If we have several options above the 3xpayment value threshold, we should pick the
// smallest of them, avoiding further fragmenting our available outbound balance to
// this node.
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), Some(&[
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params, 100_000);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), Some(&[
&get_channel_details(Some(2), nodes[0], channelmanager::provided_init_features(&config), 50_000),
&get_channel_details(Some(3), nodes[0], channelmanager::provided_init_features(&config), 50_000),
&get_channel_details(Some(5), nodes[0], channelmanager::provided_init_features(&config), 50_000),
&get_channel_details(Some(8), nodes[0], channelmanager::provided_init_features(&config), 50_000),
&get_channel_details(Some(9), nodes[0], channelmanager::provided_init_features(&config), 50_000),
&get_channel_details(Some(4), nodes[0], channelmanager::provided_init_features(&config), 1_000_000),
- ]), 100_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ ]), Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
assert_eq!(route.paths[0].hops.len(), 1);
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 route = get_route(
- &our_id, &payment_params, &network_graph.read_only(), None, 100,
- Arc::clone(&logger), &scorer, &(), &random_seed_bytes
- ).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 100);
+ let route = get_route( &our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
let path = route.paths[0].hops.iter().map(|hop| hop.short_channel_id).collect::<Vec<_>>();
assert_eq!(route.get_total_fees(), 100);
// Applying a 100 msat penalty to each hop results in taking channels 7 and 10 to nodes[6]
// from nodes[2] rather than channel 6, 11, and 8, even though the longer path is cheaper.
let scorer = FixedPenaltyScorer::with_penalty(100);
- let route = get_route(
- &our_id, &payment_params, &network_graph.read_only(), None, 100,
- Arc::clone(&logger), &scorer, &(), &random_seed_bytes
- ).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params, 100);
+ let route = get_route( &our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
let path = route.paths[0].hops.iter().map(|hop| hop.short_channel_id).collect::<Vec<_>>();
assert_eq!(route.get_total_fees(), 300);
impl Writeable for BadChannelScorer {
fn write<W: Writer>(&self, _w: &mut W) -> Result<(), crate::io::Error> { unimplemented!() }
}
- impl Score for BadChannelScorer {
+ impl ScoreLookUp for BadChannelScorer {
type ScoreParams = ();
fn channel_penalty_msat(&self, short_channel_id: u64, _: &NodeId, _: &NodeId, _: ChannelUsage, _score_params:&Self::ScoreParams) -> u64 {
if short_channel_id == self.short_channel_id { u64::max_value() } else { 0 }
}
-
- fn payment_path_failed(&mut self, _path: &Path, _short_channel_id: u64) {}
- fn payment_path_successful(&mut self, _path: &Path) {}
- fn probe_failed(&mut self, _path: &Path, _short_channel_id: u64) {}
- fn probe_successful(&mut self, _path: &Path) {}
}
struct BadNodeScorer {
fn write<W: Writer>(&self, _w: &mut W) -> Result<(), crate::io::Error> { unimplemented!() }
}
- impl Score for BadNodeScorer {
+ impl ScoreLookUp for BadNodeScorer {
type ScoreParams = ();
fn channel_penalty_msat(&self, _: u64, _: &NodeId, target: &NodeId, _: ChannelUsage, _score_params:&Self::ScoreParams) -> u64 {
if *target == self.node_id { u64::max_value() } else { 0 }
}
-
- fn payment_path_failed(&mut self, _path: &Path, _short_channel_id: u64) {}
- fn payment_path_successful(&mut self, _path: &Path) {}
- fn probe_failed(&mut self, _path: &Path, _short_channel_id: u64) {}
- fn probe_successful(&mut self, _path: &Path) {}
}
#[test]
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 route = get_route(
- &our_id, &payment_params, &network_graph, None, 100,
- Arc::clone(&logger), &scorer, &(), &random_seed_bytes
- ).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params, 100);
+ let route = get_route( &our_id, &route_params, &network_graph, None, Arc::clone(&logger),
+ &scorer, &Default::default(), &random_seed_bytes).unwrap();
let path = route.paths[0].hops.iter().map(|hop| hop.short_channel_id).collect::<Vec<_>>();
assert_eq!(route.get_total_fees(), 100);
// A different path to nodes[6] exists if channel 6 cannot be routed over.
let scorer = BadChannelScorer { short_channel_id: 6 };
- let route = get_route(
- &our_id, &payment_params, &network_graph, None, 100,
- Arc::clone(&logger), &scorer, &(), &random_seed_bytes
- ).unwrap();
+ let route = get_route( &our_id, &route_params, &network_graph, None, Arc::clone(&logger),
+ &scorer, &Default::default(), &random_seed_bytes).unwrap();
let path = route.paths[0].hops.iter().map(|hop| hop.short_channel_id).collect::<Vec<_>>();
assert_eq!(route.get_total_fees(), 300);
// A path to nodes[6] does not exist if nodes[2] cannot be routed through.
let scorer = BadNodeScorer { node_id: NodeId::from_pubkey(&nodes[2]) };
- match get_route(
- &our_id, &payment_params, &network_graph, None, 100,
- Arc::clone(&logger), &scorer, &(), &random_seed_bytes
- ) {
- Err(LightningError { err, .. } ) => {
- assert_eq!(err, "Failed to find a path to the given destination");
- },
- Ok(_) => panic!("Expected error"),
+ match get_route( &our_id, &route_params, &network_graph, None, Arc::clone(&logger),
+ &scorer, &Default::default(), &random_seed_bytes) {
+ Err(LightningError { err, .. } ) => {
+ assert_eq!(err, "Failed to find a path to the given destination");
+ },
+ Ok(_) => panic!("Expected error"),
}
}
RouteHop {
pubkey: PublicKey::from_slice(&hex::decode("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619").unwrap()[..]).unwrap(),
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
- short_channel_id: 0, fee_msat: 100, cltv_expiry_delta: 0
+ short_channel_id: 0, fee_msat: 100, cltv_expiry_delta: 0, maybe_announced_channel: true,
},
RouteHop {
pubkey: PublicKey::from_slice(&hex::decode("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c").unwrap()[..]).unwrap(),
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
- short_channel_id: 0, fee_msat: 150, cltv_expiry_delta: 0
+ short_channel_id: 0, fee_msat: 150, cltv_expiry_delta: 0, maybe_announced_channel: true,
},
RouteHop {
pubkey: PublicKey::from_slice(&hex::decode("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007").unwrap()[..]).unwrap(),
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
- short_channel_id: 0, fee_msat: 225, cltv_expiry_delta: 0
+ short_channel_id: 0, fee_msat: 225, cltv_expiry_delta: 0, maybe_announced_channel: true,
},
], blinded_tail: None }],
- payment_params: None,
+ route_params: None,
};
assert_eq!(route.get_total_fees(), 250);
RouteHop {
pubkey: PublicKey::from_slice(&hex::decode("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619").unwrap()[..]).unwrap(),
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
- short_channel_id: 0, fee_msat: 100, cltv_expiry_delta: 0
+ short_channel_id: 0, fee_msat: 100, cltv_expiry_delta: 0, maybe_announced_channel: true,
},
RouteHop {
pubkey: PublicKey::from_slice(&hex::decode("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c").unwrap()[..]).unwrap(),
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
- short_channel_id: 0, fee_msat: 150, cltv_expiry_delta: 0
+ short_channel_id: 0, fee_msat: 150, cltv_expiry_delta: 0, maybe_announced_channel: true,
},
], blinded_tail: None }, Path { hops: vec![
RouteHop {
pubkey: PublicKey::from_slice(&hex::decode("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619").unwrap()[..]).unwrap(),
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
- short_channel_id: 0, fee_msat: 100, cltv_expiry_delta: 0
+ short_channel_id: 0, fee_msat: 100, cltv_expiry_delta: 0, maybe_announced_channel: true,
},
RouteHop {
pubkey: PublicKey::from_slice(&hex::decode("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c").unwrap()[..]).unwrap(),
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
- short_channel_id: 0, fee_msat: 150, cltv_expiry_delta: 0
+ short_channel_id: 0, fee_msat: 150, cltv_expiry_delta: 0, maybe_announced_channel: true,
},
], blinded_tail: None }],
- payment_params: None,
+ route_params: None,
};
assert_eq!(route.get_total_fees(), 200);
// In an earlier version of `Route::get_total_fees` and `Route::get_total_amount`, they
// would both panic if the route was completely empty. We test to ensure they return 0
// here, even though its somewhat nonsensical as a route.
- let route = Route { paths: Vec::new(), payment_params: None };
+ let route = Route { paths: Vec::new(), route_params: None };
assert_eq!(route.get_total_fees(), 0);
assert_eq!(route.get_total_amount(), 0);
.with_max_total_cltv_expiry_delta(feasible_max_total_cltv_delta);
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
- let route = get_route(&our_id, &feasible_payment_params, &network_graph, None, 100, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ feasible_payment_params, 100);
+ let route = get_route(&our_id, &route_params, &network_graph, None, Arc::clone(&logger),
+ &scorer, &Default::default(), &random_seed_bytes).unwrap();
let path = route.paths[0].hops.iter().map(|hop| hop.short_channel_id).collect::<Vec<_>>();
assert_ne!(path.len(), 0);
let fail_max_total_cltv_delta = 23;
let fail_payment_params = PaymentParameters::from_node_id(nodes[6], 0).with_route_hints(last_hops(&nodes)).unwrap()
.with_max_total_cltv_expiry_delta(fail_max_total_cltv_delta);
- match get_route(&our_id, &fail_payment_params, &network_graph, None, 100, Arc::clone(&logger), &scorer, &(), &random_seed_bytes)
+ let route_params = RouteParameters::from_payment_params_and_value(
+ fail_payment_params, 100);
+ match get_route(&our_id, &route_params, &network_graph, None, Arc::clone(&logger), &scorer,
+ &Default::default(), &random_seed_bytes)
{
Err(LightningError { err, .. } ) => {
assert_eq!(err, "Failed to find a path to the given destination");
// We should be able to find a route initially, and then after we fail a few random
// channels eventually we won't be able to any longer.
- assert!(get_route(&our_id, &payment_params, &network_graph, None, 100, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).is_ok());
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 100);
+ assert!(get_route(&our_id, &route_params, &network_graph, None, Arc::clone(&logger),
+ &scorer, &Default::default(), &random_seed_bytes).is_ok());
loop {
- if let Ok(route) = get_route(&our_id, &payment_params, &network_graph, None, 100, Arc::clone(&logger), &scorer, &(), &random_seed_bytes) {
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 100);
+ if let Ok(route) = get_route(&our_id, &route_params, &network_graph, None,
+ Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes)
+ {
for chan in route.paths[0].hops.iter() {
assert!(!payment_params.previously_failed_channels.contains(&chan.short_channel_id));
}
// First check we can actually create a long route on this graph.
let feasible_payment_params = PaymentParameters::from_node_id(nodes[18], 0);
- let route = get_route(&our_id, &feasible_payment_params, &network_graph, None, 100,
- Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ feasible_payment_params, 100);
+ let route = get_route(&our_id, &route_params, &network_graph, None, Arc::clone(&logger),
+ &scorer, &Default::default(), &random_seed_bytes).unwrap();
let path = route.paths[0].hops.iter().map(|hop| hop.short_channel_id).collect::<Vec<_>>();
assert!(path.len() == MAX_PATH_LENGTH_ESTIMATE.into());
// But we can't create a path surpassing the MAX_PATH_LENGTH_ESTIMATE limit.
let fail_payment_params = PaymentParameters::from_node_id(nodes[19], 0);
- match get_route(&our_id, &fail_payment_params, &network_graph, None, 100,
- Arc::clone(&logger), &scorer, &(), &random_seed_bytes)
+ let route_params = RouteParameters::from_payment_params_and_value(
+ fail_payment_params, 100);
+ match get_route(&our_id, &route_params, &network_graph, None, Arc::clone(&logger), &scorer,
+ &Default::default(), &random_seed_bytes)
{
Err(LightningError { err, .. } ) => {
assert_eq!(err, "Failed to find a path to the given destination");
let payment_params = PaymentParameters::from_node_id(nodes[6], 42).with_route_hints(last_hops(&nodes)).unwrap();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 100);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
let cltv_expiry_deltas_before = route.paths[0].hops.iter().map(|h| h.cltv_expiry_delta).collect::<Vec<u32>>();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[4u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
- let mut route = get_route(&our_id, &payment_params, &network_graph, None, 100,
- Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 100);
+ let mut route = get_route(&our_id, &route_params, &network_graph, None,
+ Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
add_random_cltv_offset(&mut route, &payment_params, &network_graph, &random_seed_bytes);
let mut path_plausibility = vec![];
let payment_params = PaymentParameters::from_node_id(nodes[3], 0);
let hops = [nodes[1], nodes[2], nodes[4], nodes[3]];
- let route = build_route_from_hops_internal(&our_id, &hops, &payment_params,
- &network_graph, 100, Arc::clone(&logger), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
+ let route = build_route_from_hops_internal(&our_id, &hops, &route_params, &network_graph,
+ Arc::clone(&logger), &random_seed_bytes).unwrap();
let route_hop_pubkeys = route.paths[0].hops.iter().map(|hop| hop.pubkey).collect::<Vec<_>>();
assert_eq!(hops.len(), route.paths[0].hops.len());
for (idx, hop_pubkey) in hops.iter().enumerate() {
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 route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100_000_000, Arc::clone(&logger), &scorer, &ProbabilisticScoringFeeParameters::default(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params, 100_000_000);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &ProbabilisticScoringFeeParameters::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 2);
assert!((route.paths[0].hops[1].short_channel_id == 4 && route.paths[1].hops[1].short_channel_id == 13) ||
(route.paths[1].hops[1].short_channel_id == 4 && route.paths[0].hops[1].short_channel_id == 13));
let params = ProbabilisticScoringFeeParameters::default();
let mut scorer = ProbabilisticScorer::new(ProbabilisticScoringDecayParameters::default(), &graph, &logger);
- let features = super::InvoiceFeatures::empty();
+ let features = super::Bolt11InvoiceFeatures::empty();
super::bench_utils::generate_test_routes(&graph, &mut scorer, ¶ms, features, random_init_seed(), 0, 2);
}
// Then check we can get a normal route
let payment_params = PaymentParameters::from_node_id(nodes[10], 42);
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, Arc::clone(&logger), &scorer, &scorer_params,&random_seed_bytes);
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params, 100);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &scorer_params, &random_seed_bytes);
assert!(route.is_ok());
// Then check that we can't get a route if we ban an intermediate node.
scorer_params.add_banned(&NodeId::from_pubkey(&nodes[3]));
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, Arc::clone(&logger), &scorer, &scorer_params,&random_seed_bytes);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &scorer_params, &random_seed_bytes);
assert!(route.is_err());
// Finally make sure we can route again, when we remove the ban.
scorer_params.remove_banned(&NodeId::from_pubkey(&nodes[3]));
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, Arc::clone(&logger), &scorer, &scorer_params,&random_seed_bytes);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &scorer_params, &random_seed_bytes);
assert!(route.is_ok());
}
// Make sure we'll error if our route hints don't have enough liquidity according to their
// htlc_maximum_msat.
+ let mut route_params = RouteParameters::from_payment_params_and_value(
+ payment_params, max_htlc_msat + 1);
+ route_params.max_total_routing_fee_msat = None;
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id,
- &payment_params, &netgraph, None, max_htlc_msat + 1, Arc::clone(&logger), &scorer, &(),
+ &route_params, &netgraph, None, Arc::clone(&logger), &scorer, &Default::default(),
&random_seed_bytes)
{
assert_eq!(err, "Failed to find a sufficient route to the given destination");
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();
- let route = get_route(&our_id, &payment_params, &netgraph, None, max_htlc_msat + 1,
- Arc::clone(&logger), &scorer, &(), &random_seed_bytes).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 route = get_route(&our_id, &route_params, &netgraph, None, Arc::clone(&logger),
+ &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 2);
assert!(route.paths[0].hops.last().unwrap().fee_msat <= max_htlc_msat);
assert!(route.paths[1].hops.last().unwrap().fee_msat <= max_htlc_msat);
.with_route_hints(vec![route_hint_1, route_hint_2]).unwrap()
.with_bolt11_features(channelmanager::provided_invoice_features(&config)).unwrap();
- let route = get_route(&our_node_id, &payment_params, &network_graph.read_only(),
- Some(&first_hop.iter().collect::<Vec<_>>()), amt_msat, Arc::clone(&logger), &scorer, &(),
- &random_seed_bytes).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_hop.iter().collect::<Vec<_>>()), Arc::clone(&logger), &scorer,
+ &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 2);
assert!(route.paths[0].hops.last().unwrap().fee_msat <= max_htlc_msat);
assert!(route.paths[1].hops.last().unwrap().fee_msat <= max_htlc_msat);
get_channel_details(Some(42), intermed_node_id, InitFeatures::from_le_bytes(vec![0b11]), amt_msat - 10),
get_channel_details(Some(43), intermed_node_id, InitFeatures::from_le_bytes(vec![0b11]), amt_msat - 10),
];
- let route = get_route(&our_node_id, &payment_params, &network_graph.read_only(),
- Some(&first_hops.iter().collect::<Vec<_>>()), amt_msat, Arc::clone(&logger), &scorer, &(),
- &random_seed_bytes).unwrap();
+ 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(), 2);
assert!(route.paths[0].hops.last().unwrap().fee_msat <= max_htlc_msat);
assert!(route.paths[1].hops.last().unwrap().fee_msat <= max_htlc_msat);
(blinded_payinfo.clone(), blinded_path.clone()),
(blinded_payinfo.clone(), blinded_path.clone())])
.with_bolt12_features(bolt12_features).unwrap();
- let route = get_route(&our_node_id, &payment_params, &network_graph.read_only(),
- Some(&first_hops.iter().collect::<Vec<_>>()), amt_msat, Arc::clone(&logger), &scorer, &(),
- &random_seed_bytes).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(), 2);
assert!(route.paths[0].hops.last().unwrap().fee_msat <= max_htlc_msat);
assert!(route.paths[1].hops.last().unwrap().fee_msat <= max_htlc_msat);
channel_features: ChannelFeatures::empty(),
fee_msat: 100,
cltv_expiry_delta: 0,
+ maybe_announced_channel: true,
}],
blinded_tail: Some(BlindedTail {
hops: blinded_path_1.blinded_hops,
channel_features: ChannelFeatures::empty(),
fee_msat: 100,
cltv_expiry_delta: 0,
+ maybe_announced_channel: true,
}], blinded_tail: None }],
- payment_params: None,
+ route_params: None,
};
let encoded_route = route.encode();
let decoded_route: Route = Readable::read(&mut Cursor::new(&encoded_route[..])).unwrap();
channel_features: ChannelFeatures::empty(),
fee_msat: 100,
cltv_expiry_delta: 0,
+ maybe_announced_channel: false,
},
RouteHop {
pubkey: blinded_path.introduction_node_id,
channel_features: ChannelFeatures::empty(),
fee_msat: 1,
cltv_expiry_delta: 0,
+ maybe_announced_channel: false,
}],
blinded_tail: Some(BlindedTail {
hops: blinded_path.blinded_hops,
channel_features: ChannelFeatures::empty(),
fee_msat: 100,
cltv_expiry_delta: 0,
+ maybe_announced_channel: false,
},
RouteHop {
pubkey: blinded_path.introduction_node_id,
channel_features: ChannelFeatures::empty(),
fee_msat: 1,
cltv_expiry_delta: 0,
+ maybe_announced_channel: false,
}
],
blinded_tail: Some(BlindedTail {
excess_final_cltv_expiry_delta: 0,
final_value_msat: 200,
}),
- }], payment_params: None};
+ }], route_params: None};
let payment_params = PaymentParameters::from_node_id(ln_test_utils::pubkey(47), 18);
let (_, network_graph, _, _, _) = build_line_graph();
features: BlindedHopFeatures::empty(),
};
- let final_amt_msat = 1001;
let payment_params = PaymentParameters::blinded(vec![(blinded_payinfo.clone(), blinded_path.clone())]);
- let route = get_route(&our_id, &payment_params, &network_graph, None, final_amt_msat , Arc::clone(&logger),
- &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params, 1001);
+ let route = get_route(&our_id, &route_params, &network_graph, None, Arc::clone(&logger),
+ &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
assert_eq!(route.paths[0].hops.len(), 2);
let payment_params = PaymentParameters::blinded(vec![
(blinded_payinfo.clone(), invalid_blinded_path.clone()),
(blinded_payinfo.clone(), invalid_blinded_path_2)]);
- match get_route(&our_id, &payment_params, &network_graph, None, 1001, Arc::clone(&logger),
- &scorer, &(), &random_seed_bytes)
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, 1001);
+ match get_route(&our_id, &route_params, &network_graph, None, Arc::clone(&logger),
+ &scorer, &Default::default(), &random_seed_bytes)
{
Err(LightningError { err, .. }) => {
assert_eq!(err, "1-hop blinded paths must all have matching introduction node ids");
invalid_blinded_path.introduction_node_id = our_id;
let payment_params = PaymentParameters::blinded(vec![(blinded_payinfo.clone(), invalid_blinded_path.clone())]);
- match get_route(&our_id, &payment_params, &network_graph, None, 1001, Arc::clone(&logger),
- &scorer, &(), &random_seed_bytes)
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, 1001);
+ match get_route(&our_id, &route_params, &network_graph, None, Arc::clone(&logger), &scorer,
+ &Default::default(), &random_seed_bytes)
{
Err(LightningError { err, .. }) => {
assert_eq!(err, "Cannot generate a route to blinded paths if we are the introduction node to all of them");
invalid_blinded_path.introduction_node_id = ln_test_utils::pubkey(46);
invalid_blinded_path.blinded_hops.clear();
let payment_params = PaymentParameters::blinded(vec![(blinded_payinfo, invalid_blinded_path)]);
- match get_route(&our_id, &payment_params, &network_graph, None, 1001, Arc::clone(&logger),
- &scorer, &(), &random_seed_bytes)
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, 1001);
+ match get_route(&our_id, &route_params, &network_graph, None, Arc::clone(&logger), &scorer,
+ &Default::default(), &random_seed_bytes)
{
Err(LightningError { err, .. }) => {
assert_eq!(err, "0-hop blinded path provided");
let payment_params = PaymentParameters::blinded(blinded_hints.clone())
.with_bolt12_features(bolt12_features.clone()).unwrap();
- let route = get_route(&our_id, &payment_params, &network_graph, None,
- 100_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).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);
+ let route = get_route(&our_id, &route_params, &network_graph, None, Arc::clone(&logger),
+ &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 2);
let mut total_amount_paid_msat = 0;
for path in route.paths.into_iter() {
}
assert_eq!(total_amount_paid_msat, 100_000);
}
+
+ #[test]
+ fn direct_to_intro_node() {
+ // This previously caused a debug panic in the router when asserting
+ // `used_liquidity_msat <= hop_max_msat`, because when adding first_hop<>blinded_route_hint
+ // direct channels we failed to account for the fee charged for use of the blinded path.
+
+ // Build a graph:
+ // node0 -1(1)2 - node1
+ // such that there isn't enough liquidity to reach node1, but the router thinks there is if it
+ // doesn't account for the blinded path fee.
+
+ 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 amt_msat = 10_000_000;
+ let (_, _, privkeys, nodes) = get_nodes(&secp_ctx);
+ 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(),
+ 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[1], UnsignedChannelUpdate {
+ chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ 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()
+ });
+ let first_hops = vec![
+ get_channel_details(Some(1), nodes[1], InitFeatures::from_le_bytes(vec![0b11]), 10_000_000)];
+
+ let blinded_path = BlindedPath {
+ introduction_node_id: nodes[1],
+ blinding_point: ln_test_utils::pubkey(42),
+ blinded_hops: vec![
+ BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() },
+ BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }
+ ],
+ };
+ let blinded_payinfo = BlindedPayInfo {
+ fee_base_msat: 1000,
+ fee_proportional_millionths: 0,
+ htlc_minimum_msat: 1000,
+ htlc_maximum_msat: MAX_VALUE_MSAT,
+ cltv_expiry_delta: 0,
+ features: BlindedHopFeatures::empty(),
+ };
+ let blinded_hints = vec![(blinded_payinfo.clone(), blinded_path)];
+
+ let payment_params = PaymentParameters::blinded(blinded_hints.clone());
+
+ let netgraph = network_graph.read_only();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), amt_msat);
+ if let Err(LightningError { err, .. }) = get_route(&nodes[0], &route_params, &netgraph,
+ Some(&first_hops.iter().collect::<Vec<_>>()), Arc::clone(&logger), &scorer,
+ &Default::default(), &random_seed_bytes) {
+ assert_eq!(err, "Failed to find a path to the given destination");
+ } else { panic!("Expected error") }
+
+ // Sending an exact amount accounting for the blinded path fee works.
+ let amt_minus_blinded_path_fee = amt_msat - blinded_payinfo.fee_base_msat as u64;
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params, amt_minus_blinded_path_fee);
+ let route = get_route(&nodes[0], &route_params, &netgraph,
+ Some(&first_hops.iter().collect::<Vec<_>>()), Arc::clone(&logger), &scorer,
+ &Default::default(), &random_seed_bytes).unwrap();
+ assert_eq!(route.get_total_fees(), blinded_payinfo.fee_base_msat as u64);
+ assert_eq!(route.get_total_amount(), amt_minus_blinded_path_fee);
+ }
+
+ #[test]
+ fn direct_to_matching_intro_nodes() {
+ // This previously caused us to enter `unreachable` code in the following situation:
+ // 1. We add a route candidate for intro_node contributing a high amount
+ // 2. We add a first_hop<>intro_node route candidate for the same high amount
+ // 3. We see a cheaper blinded route hint for the same intro node but a much lower contribution
+ // amount, and update our route candidate for intro_node for the lower amount
+ // 4. We then attempt to update the aforementioned first_hop<>intro_node route candidate for the
+ // lower contribution amount, but fail (this was previously caused by failure to account for
+ // blinded path fees when adding first_hop<>intro_node candidates)
+ // 5. We go to construct the path from these route candidates and our first_hop<>intro_node
+ // candidate still thinks its path is contributing the original higher amount. This caused us
+ // to hit an `unreachable` overflow when calculating the cheaper intro_node fees over the
+ // larger amount
+ 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();
+
+ // Values are taken from the fuzz input that uncovered this panic.
+ let amt_msat = 21_7020_5185_1403_2640;
+ let (_, _, _, nodes) = get_nodes(&secp_ctx);
+ let first_hops = vec![
+ get_channel_details(Some(1), nodes[1], channelmanager::provided_init_features(&config),
+ 18446744073709551615)];
+
+ let blinded_path = BlindedPath {
+ introduction_node_id: nodes[1],
+ blinding_point: ln_test_utils::pubkey(42),
+ blinded_hops: vec![
+ BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() },
+ BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }
+ ],
+ };
+ let blinded_payinfo = BlindedPayInfo {
+ fee_base_msat: 5046_2720,
+ fee_proportional_millionths: 0,
+ htlc_minimum_msat: 4503_5996_2737_0496,
+ htlc_maximum_msat: 45_0359_9627_3704_9600,
+ cltv_expiry_delta: 0,
+ features: BlindedHopFeatures::empty(),
+ };
+ let mut blinded_hints = vec![
+ (blinded_payinfo.clone(), blinded_path.clone()),
+ (blinded_payinfo.clone(), blinded_path.clone()),
+ ];
+ blinded_hints[1].0.fee_base_msat = 419_4304;
+ blinded_hints[1].0.fee_proportional_millionths = 257;
+ blinded_hints[1].0.htlc_minimum_msat = 280_8908_6115_8400;
+ 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 payment_params = PaymentParameters::blinded(blinded_hints.clone())
+ .with_bolt12_features(bolt12_features.clone()).unwrap();
+
+ let netgraph = network_graph.read_only();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params, amt_msat);
+ let route = get_route(&nodes[0], &route_params, &netgraph,
+ Some(&first_hops.iter().collect::<Vec<_>>()), Arc::clone(&logger), &scorer,
+ &Default::default(), &random_seed_bytes).unwrap();
+ assert_eq!(route.get_total_fees(), blinded_payinfo.fee_base_msat as u64);
+ assert_eq!(route.get_total_amount(), amt_msat);
+ }
+
+ #[test]
+ fn we_are_intro_node_candidate_hops() {
+ // This previously led to a panic in the router because we'd generate a Path with only a
+ // BlindedTail and 0 unblinded hops, due to the only candidate hops being blinded route hints
+ // where the origin node is the intro node. We now fully disallow considering candidate hops
+ // where the origin node is the intro node.
+ let (secp_ctx, network_graph, _, _, logger) = build_graph();
+ let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
+ 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();
+
+ // Values are taken from the fuzz input that uncovered this panic.
+ let amt_msat = 21_7020_5185_1423_0019;
+
+ let blinded_path = BlindedPath {
+ introduction_node_id: our_id,
+ blinding_point: ln_test_utils::pubkey(42),
+ blinded_hops: vec![
+ BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() },
+ BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }
+ ],
+ };
+ let blinded_payinfo = BlindedPayInfo {
+ fee_base_msat: 5052_9027,
+ fee_proportional_millionths: 0,
+ htlc_minimum_msat: 21_7020_5185_1423_0019,
+ htlc_maximum_msat: 1844_6744_0737_0955_1615,
+ cltv_expiry_delta: 0,
+ features: BlindedHopFeatures::empty(),
+ };
+ let mut blinded_hints = vec![
+ (blinded_payinfo.clone(), blinded_path.clone()),
+ (blinded_payinfo.clone(), blinded_path.clone()),
+ ];
+ blinded_hints[1].1.introduction_node_id = nodes[6];
+
+ let bolt12_features: Bolt12InvoiceFeatures = channelmanager::provided_invoice_features(&config).to_context();
+ let payment_params = PaymentParameters::blinded(blinded_hints.clone())
+ .with_bolt12_features(bolt12_features.clone()).unwrap();
+
+ let netgraph = network_graph.read_only();
+ 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
+ ) {
+ assert_eq!(err, "Failed to find a path to the given destination");
+ } else { panic!() }
+ }
+
+ #[test]
+ fn we_are_intro_node_bp_in_final_path_fee_calc() {
+ // This previously led to a debug panic in the router because we'd find an invalid Path with
+ // 0 unblinded hops and a blinded tail, leading to the generation of a final
+ // PaymentPathHop::fee_msat that included both the blinded path fees and the final value of
+ // the payment, when it was intended to only include the final value of the payment.
+ let (secp_ctx, network_graph, _, _, logger) = build_graph();
+ let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
+ 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();
+
+ // Values are taken from the fuzz input that uncovered this panic.
+ let amt_msat = 21_7020_5185_1423_0019;
+
+ let blinded_path = BlindedPath {
+ introduction_node_id: our_id,
+ blinding_point: ln_test_utils::pubkey(42),
+ blinded_hops: vec![
+ BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() },
+ BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }
+ ],
+ };
+ let blinded_payinfo = BlindedPayInfo {
+ fee_base_msat: 10_4425_1395,
+ fee_proportional_millionths: 0,
+ htlc_minimum_msat: 21_7301_9934_9094_0931,
+ htlc_maximum_msat: 1844_6744_0737_0955_1615,
+ cltv_expiry_delta: 0,
+ features: BlindedHopFeatures::empty(),
+ };
+ let mut blinded_hints = vec![
+ (blinded_payinfo.clone(), blinded_path.clone()),
+ (blinded_payinfo.clone(), blinded_path.clone()),
+ (blinded_payinfo.clone(), blinded_path.clone()),
+ ];
+ blinded_hints[1].0.fee_base_msat = 5052_9027;
+ blinded_hints[1].0.htlc_minimum_msat = 21_7020_5185_1423_0019;
+ blinded_hints[1].0.htlc_maximum_msat = 1844_6744_0737_0955_1615;
+
+ blinded_hints[2].1.introduction_node_id = nodes[6];
+
+ let bolt12_features: Bolt12InvoiceFeatures = channelmanager::provided_invoice_features(&config).to_context();
+ let payment_params = PaymentParameters::blinded(blinded_hints.clone())
+ .with_bolt12_features(bolt12_features.clone()).unwrap();
+
+ let netgraph = network_graph.read_only();
+ 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
+ ) {
+ assert_eq!(err, "Failed to find a path to the given destination");
+ } else { panic!() }
+ }
+
+ #[test]
+ fn min_htlc_overpay_violates_max_htlc() {
+ do_min_htlc_overpay_violates_max_htlc(true);
+ do_min_htlc_overpay_violates_max_htlc(false);
+ }
+ fn do_min_htlc_overpay_violates_max_htlc(blinded_payee: bool) {
+ // Test that if overpaying to meet a later hop's min_htlc and causes us to violate an earlier
+ // hop's max_htlc, we don't consider that candidate hop valid. Previously we would add this hop
+ // to `targets` and build an invalid path with it, and subsquently hit a debug panic asserting
+ // that the used liquidity for a hop was less than its available liquidity limit.
+ 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();
+
+ // Values are taken from the fuzz input that uncovered this panic.
+ let amt_msat = 7_4009_8048;
+ let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
+ let first_hop_outbound_capacity = 2_7345_2000;
+ let first_hops = vec![get_channel_details(
+ Some(200), nodes[0], channelmanager::provided_init_features(&config),
+ first_hop_outbound_capacity
+ )];
+
+ let base_fee = 1_6778_3453;
+ let htlc_min = 2_5165_8240;
+ let payment_params = if blinded_payee {
+ let blinded_path = BlindedPath {
+ introduction_node_id: nodes[0],
+ blinding_point: ln_test_utils::pubkey(42),
+ blinded_hops: vec![
+ BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() },
+ BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }
+ ],
+ };
+ let blinded_payinfo = BlindedPayInfo {
+ fee_base_msat: base_fee,
+ fee_proportional_millionths: 0,
+ htlc_minimum_msat: htlc_min,
+ htlc_maximum_msat: htlc_min * 1000,
+ cltv_expiry_delta: 0,
+ features: BlindedHopFeatures::empty(),
+ };
+ let bolt12_features: Bolt12InvoiceFeatures = channelmanager::provided_invoice_features(&config).to_context();
+ PaymentParameters::blinded(vec![(blinded_payinfo, blinded_path)])
+ .with_bolt12_features(bolt12_features.clone()).unwrap()
+ } else {
+ let route_hint = RouteHint(vec![RouteHintHop {
+ src_node_id: nodes[0],
+ short_channel_id: 42,
+ fees: RoutingFees {
+ base_msat: base_fee,
+ proportional_millionths: 0,
+ },
+ cltv_expiry_delta: 10,
+ htlc_minimum_msat: Some(htlc_min),
+ htlc_maximum_msat: None,
+ }]);
+
+ PaymentParameters::from_node_id(nodes[1], 42)
+ .with_route_hints(vec![route_hint]).unwrap()
+ .with_bolt11_features(channelmanager::provided_invoice_features(&config)).unwrap()
+ };
+
+ let netgraph = network_graph.read_only();
+ 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, Some(&first_hops.iter().collect::<Vec<_>>()),
+ Arc::clone(&logger), &scorer, &(), &random_seed_bytes
+ ) {
+ assert_eq!(err, "Failed to find a path to the given destination");
+ } else { panic!() }
+ }
+
+ #[test]
+ fn previously_used_liquidity_violates_max_htlc() {
+ do_previously_used_liquidity_violates_max_htlc(true);
+ do_previously_used_liquidity_violates_max_htlc(false);
+
+ }
+ fn do_previously_used_liquidity_violates_max_htlc(blinded_payee: bool) {
+ // Test that if a candidate first_hop<>route_hint_src_node channel does not have enough
+ // contribution amount to cover the next hop's min_htlc plus fees, we will not consider that
+ // candidate. In this case, the candidate does not have enough due to a previous path taking up
+ // some of its liquidity. Previously we would construct an invalid path and hit a debug panic
+ // asserting that the used liquidity for a hop was less than its available liquidity limit.
+ 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();
+
+ // Values are taken from the fuzz input that uncovered this panic.
+ let amt_msat = 52_4288;
+ let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
+ let first_hops = vec![get_channel_details(
+ Some(161), nodes[0], channelmanager::provided_init_features(&config), 486_4000
+ ), get_channel_details(
+ Some(122), nodes[0], channelmanager::provided_init_features(&config), 179_5000
+ )];
+
+ let base_fees = [0, 425_9840, 0, 0];
+ let htlc_mins = [1_4392, 19_7401, 1027, 6_5535];
+ let payment_params = if blinded_payee {
+ let blinded_path = BlindedPath {
+ introduction_node_id: nodes[0],
+ blinding_point: ln_test_utils::pubkey(42),
+ blinded_hops: vec![
+ BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() },
+ BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }
+ ],
+ };
+ let mut blinded_hints = Vec::new();
+ for (base_fee, htlc_min) in base_fees.iter().zip(htlc_mins.iter()) {
+ blinded_hints.push((BlindedPayInfo {
+ fee_base_msat: *base_fee,
+ fee_proportional_millionths: 0,
+ htlc_minimum_msat: *htlc_min,
+ htlc_maximum_msat: htlc_min * 100,
+ cltv_expiry_delta: 10,
+ features: BlindedHopFeatures::empty(),
+ }, blinded_path.clone()));
+ }
+ let bolt12_features: Bolt12InvoiceFeatures = channelmanager::provided_invoice_features(&config).to_context();
+ PaymentParameters::blinded(blinded_hints.clone())
+ .with_bolt12_features(bolt12_features.clone()).unwrap()
+ } else {
+ let mut route_hints = Vec::new();
+ for (idx, (base_fee, htlc_min)) in base_fees.iter().zip(htlc_mins.iter()).enumerate() {
+ route_hints.push(RouteHint(vec![RouteHintHop {
+ src_node_id: nodes[0],
+ short_channel_id: 42 + idx as u64,
+ fees: RoutingFees {
+ base_msat: *base_fee,
+ proportional_millionths: 0,
+ },
+ cltv_expiry_delta: 10,
+ htlc_minimum_msat: Some(*htlc_min),
+ htlc_maximum_msat: Some(htlc_min * 100),
+ }]));
+ }
+ PaymentParameters::from_node_id(nodes[1], 42)
+ .with_route_hints(route_hints).unwrap()
+ .with_bolt11_features(channelmanager::provided_invoice_features(&config)).unwrap()
+ };
+
+ let netgraph = network_graph.read_only();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params, amt_msat);
+
+ let route = get_route(
+ &our_id, &route_params, &netgraph, Some(&first_hops.iter().collect::<Vec<_>>()),
+ Arc::clone(&logger), &scorer, &(), &random_seed_bytes
+ ).unwrap();
+ assert_eq!(route.paths.len(), 1);
+ assert_eq!(route.get_total_amount(), amt_msat);
+ }
+
+ #[test]
+ fn candidate_path_min() {
+ // Test that if a candidate first_hop<>network_node channel does not have enough contribution
+ // amount to cover the next channel's min htlc plus fees, we will not consider that candidate.
+ // Previously, we were storing RouteGraphNodes with a path_min that did not include fees, and
+ // would add a connecting first_hop node that did not have enough contribution amount, leading
+ // to a debug panic upon invalid path construction.
+ 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(network_graph.clone(), None, logger.clone());
+ let scorer = ProbabilisticScorer::new(ProbabilisticScoringDecayParameters::default(), network_graph.clone(), logger.clone());
+ 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();
+
+ // Values are taken from the fuzz input that uncovered this panic.
+ let amt_msat = 7_4009_8048;
+ let (_, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
+ let first_hops = vec![get_channel_details(
+ Some(200), nodes[0], channelmanager::provided_init_features(&config), 2_7345_2000
+ )];
+
+ 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(),
+ short_channel_id: 6,
+ timestamp: 1,
+ flags: 0,
+ cltv_expiry_delta: (6 << 4) | 0,
+ htlc_minimum_msat: 0,
+ htlc_maximum_msat: MAX_VALUE_MSAT,
+ fee_base_msat: 0,
+ fee_proportional_millionths: 0,
+ excess_data: Vec::new()
+ });
+ add_or_update_node(&gossip_sync, &secp_ctx, &privkeys[0], NodeFeatures::from_le_bytes(id_to_feature_flags(1)), 0);
+
+ let htlc_min = 2_5165_8240;
+ let blinded_hints = vec![
+ (BlindedPayInfo {
+ fee_base_msat: 1_6778_3453,
+ fee_proportional_millionths: 0,
+ htlc_minimum_msat: htlc_min,
+ htlc_maximum_msat: htlc_min * 100,
+ cltv_expiry_delta: 10,
+ features: BlindedHopFeatures::empty(),
+ }, BlindedPath {
+ introduction_node_id: nodes[0],
+ blinding_point: ln_test_utils::pubkey(42),
+ blinded_hops: vec![
+ BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() },
+ BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }
+ ],
+ })
+ ];
+ let bolt12_features: Bolt12InvoiceFeatures = channelmanager::provided_invoice_features(&config).to_context();
+ 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);
+ let netgraph = network_graph.read_only();
+
+ if let Err(LightningError { err, .. }) = get_route(
+ &our_id, &route_params, &netgraph, Some(&first_hops.iter().collect::<Vec<_>>()),
+ Arc::clone(&logger), &scorer, &ProbabilisticScoringFeeParameters::default(),
+ &random_seed_bytes
+ ) {
+ assert_eq!(err, "Failed to find a path to the given destination");
+ } else { panic!() }
+ }
+
+ #[test]
+ fn path_contribution_includes_min_htlc_overpay() {
+ // Previously, the fuzzer hit a debug panic because we wouldn't include the amount overpaid to
+ // meet a last hop's min_htlc in the total collected paths value. We now include this value and
+ // also penalize hops along the overpaying path to ensure that it gets deprioritized in path
+ // selection, both tested here.
+ 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 = ProbabilisticScorer::new(ProbabilisticScoringDecayParameters::default(), network_graph.clone(), logger.clone());
+ 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();
+
+ // Values are taken from the fuzz input that uncovered this panic.
+ let amt_msat = 562_0000;
+ let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
+ let first_hops = vec![
+ get_channel_details(
+ Some(83), nodes[0], channelmanager::provided_init_features(&config), 2199_0000,
+ ),
+ ];
+
+ let htlc_mins = [49_0000, 1125_0000];
+ let payment_params = {
+ let blinded_path = BlindedPath {
+ introduction_node_id: nodes[0],
+ blinding_point: ln_test_utils::pubkey(42),
+ blinded_hops: vec![
+ BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() },
+ BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }
+ ],
+ };
+ let mut blinded_hints = Vec::new();
+ for htlc_min in htlc_mins.iter() {
+ blinded_hints.push((BlindedPayInfo {
+ fee_base_msat: 0,
+ fee_proportional_millionths: 0,
+ htlc_minimum_msat: *htlc_min,
+ htlc_maximum_msat: *htlc_min * 100,
+ cltv_expiry_delta: 10,
+ features: BlindedHopFeatures::empty(),
+ }, blinded_path.clone()));
+ }
+ let bolt12_features: Bolt12InvoiceFeatures = channelmanager::provided_invoice_features(&config).to_context();
+ PaymentParameters::blinded(blinded_hints.clone())
+ .with_bolt12_features(bolt12_features.clone()).unwrap()
+ };
+
+ let netgraph = network_graph.read_only();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params, amt_msat);
+ let route = get_route(
+ &our_id, &route_params, &netgraph, Some(&first_hops.iter().collect::<Vec<_>>()),
+ Arc::clone(&logger), &scorer, &ProbabilisticScoringFeeParameters::default(),
+ &random_seed_bytes
+ ).unwrap();
+ assert_eq!(route.paths.len(), 1);
+ assert_eq!(route.get_total_amount(), amt_msat);
+ }
}
#[cfg(all(any(test, ldk_bench), not(feature = "no-std")))]
use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
use crate::chain::transaction::OutPoint;
+ use crate::routing::scoring::ScoreUpdate;
use crate::sign::{EntropySource, KeysManager};
+ use crate::ln::ChannelId;
use crate::ln::channelmanager::{self, ChannelCounterparty, ChannelDetails};
- use crate::ln::features::InvoiceFeatures;
+ use crate::ln::features::Bolt11InvoiceFeatures;
use crate::routing::gossip::NetworkGraph;
use crate::util::config::UserConfig;
use crate::util::ser::ReadableArgs;
#[inline]
pub(crate) fn first_hop(node_id: PublicKey) -> ChannelDetails {
ChannelDetails {
- channel_id: [0; 32],
+ channel_id: ChannelId::new_zero(),
counterparty: ChannelCounterparty {
features: channelmanager::provided_init_features(&UserConfig::default()),
node_id,
outbound_scid_alias: None,
channel_value_satoshis: 10_000_000_000,
user_channel_id: 0,
- balance_msat: 10_000_000_000,
outbound_capacity_msat: 10_000_000_000,
next_outbound_htlc_minimum_msat: 0,
next_outbound_htlc_limit_msat: 10_000_000_000,
}
}
- pub(crate) fn generate_test_routes<S: Score>(graph: &NetworkGraph<&TestLogger>, scorer: &mut S,
- score_params: &S::ScoreParams, features: InvoiceFeatures, mut seed: u64,
+ pub(crate) fn generate_test_routes<S: ScoreLookUp + ScoreUpdate>(graph: &NetworkGraph<&TestLogger>, scorer: &mut S,
+ score_params: &S::ScoreParams, features: Bolt11InvoiceFeatures, mut seed: u64,
starting_amount: u64, route_count: usize,
) -> Vec<(ChannelDetails, PaymentParameters, u64)> {
let payer = payer_pubkey();
let params = PaymentParameters::from_node_id(dst, 42)
.with_bolt11_features(features.clone()).unwrap();
let first_hop = first_hop(src);
- let amt = starting_amount + seed % 1_000_000;
+ let amt_msat = starting_amount + seed % 1_000_000;
+ let route_params = RouteParameters::from_payment_params_and_value(
+ params.clone(), amt_msat);
let path_exists =
- get_route(&payer, ¶ms, &graph.read_only(), Some(&[&first_hop]),
- amt, &TestLogger::new(), &scorer, score_params, &random_seed_bytes).is_ok();
+ get_route(&payer, &route_params, &graph.read_only(), Some(&[&first_hop]),
+ &TestLogger::new(), scorer, score_params, &random_seed_bytes).is_ok();
if path_exists {
// ...and seed the scorer with success and failure data...
seed = seed.overflowing_mul(6364136223846793005).0.overflowing_add(1).0;
let mpp_features = channelmanager::provided_invoice_features(&UserConfig::default());
let params = PaymentParameters::from_node_id(dst, 42)
.with_bolt11_features(mpp_features).unwrap();
-
- let route_res = get_route(&payer, ¶ms, &graph.read_only(),
- Some(&[&first_hop]), score_amt, &TestLogger::new(), &scorer,
- score_params, &random_seed_bytes);
+ let route_params = RouteParameters::from_payment_params_and_value(
+ params.clone(), score_amt);
+ let route_res = get_route(&payer, &route_params, &graph.read_only(),
+ Some(&[&first_hop]), &TestLogger::new(), scorer, score_params,
+ &random_seed_bytes);
if let Ok(route) = route_res {
for path in route.paths {
if seed & 0x80 == 0 {
score_amt /= 100;
}
- route_endpoints.push((first_hop, params, amt));
+ route_endpoints.push((first_hop, params, amt_msat));
break;
}
}
// Because we've changed channel scores, it's possible we'll take different routes to the
// selected destinations, possibly causing us to fail because, eg, the newly-selected path
// requires a too-high CLTV delta.
- route_endpoints.retain(|(first_hop, params, amt)| {
- get_route(&payer, params, &graph.read_only(), Some(&[first_hop]), *amt,
- &TestLogger::new(), &scorer, score_params, &random_seed_bytes).is_ok()
+ route_endpoints.retain(|(first_hop, params, amt_msat)| {
+ let route_params = RouteParameters::from_payment_params_and_value(
+ params.clone(), *amt_msat);
+ get_route(&payer, &route_params, &graph.read_only(), Some(&[first_hop]),
+ &TestLogger::new(), scorer, score_params, &random_seed_bytes).is_ok()
});
route_endpoints.truncate(route_count);
assert_eq!(route_endpoints.len(), route_count);
#[cfg(ldk_bench)]
pub mod benches {
use super::*;
+ use crate::routing::scoring::{ScoreUpdate, ScoreLookUp};
use crate::sign::{EntropySource, KeysManager};
use crate::ln::channelmanager;
- use crate::ln::features::InvoiceFeatures;
+ use crate::ln::features::Bolt11InvoiceFeatures;
use crate::routing::gossip::NetworkGraph;
use crate::routing::scoring::{FixedPenaltyScorer, ProbabilisticScorer, ProbabilisticScoringFeeParameters, ProbabilisticScoringDecayParameters};
use crate::util::config::UserConfig;
let logger = TestLogger::new();
let network_graph = bench_utils::read_network_graph(&logger).unwrap();
let scorer = FixedPenaltyScorer::with_penalty(0);
- generate_routes(bench, &network_graph, scorer, &(), InvoiceFeatures::empty(), 0,
- "generate_routes_with_zero_penalty_scorer");
+ generate_routes(bench, &network_graph, scorer, &Default::default(),
+ Bolt11InvoiceFeatures::empty(), 0, "generate_routes_with_zero_penalty_scorer");
}
pub fn generate_mpp_routes_with_zero_penalty_scorer(bench: &mut Criterion) {
let logger = TestLogger::new();
let network_graph = bench_utils::read_network_graph(&logger).unwrap();
let scorer = FixedPenaltyScorer::with_penalty(0);
- generate_routes(bench, &network_graph, scorer, &(),
+ generate_routes(bench, &network_graph, scorer, &Default::default(),
channelmanager::provided_invoice_features(&UserConfig::default()), 0,
"generate_mpp_routes_with_zero_penalty_scorer");
}
let network_graph = bench_utils::read_network_graph(&logger).unwrap();
let params = ProbabilisticScoringFeeParameters::default();
let scorer = ProbabilisticScorer::new(ProbabilisticScoringDecayParameters::default(), &network_graph, &logger);
- generate_routes(bench, &network_graph, scorer, ¶ms, InvoiceFeatures::empty(), 0,
+ generate_routes(bench, &network_graph, scorer, ¶ms, Bolt11InvoiceFeatures::empty(), 0,
"generate_routes_with_probabilistic_scorer");
}
"generate_large_mpp_routes_with_probabilistic_scorer");
}
- fn generate_routes<S: Score>(
+ pub fn generate_routes_with_nonlinear_probabilistic_scorer(bench: &mut Criterion) {
+ let logger = TestLogger::new();
+ let network_graph = bench_utils::read_network_graph(&logger).unwrap();
+ let mut params = ProbabilisticScoringFeeParameters::default();
+ params.linear_success_probability = false;
+ let scorer = ProbabilisticScorer::new(
+ ProbabilisticScoringDecayParameters::default(), &network_graph, &logger);
+ generate_routes(bench, &network_graph, scorer, ¶ms,
+ channelmanager::provided_invoice_features(&UserConfig::default()), 0,
+ "generate_routes_with_nonlinear_probabilistic_scorer");
+ }
+
+ pub fn generate_mpp_routes_with_nonlinear_probabilistic_scorer(bench: &mut Criterion) {
+ let logger = TestLogger::new();
+ let network_graph = bench_utils::read_network_graph(&logger).unwrap();
+ let mut params = ProbabilisticScoringFeeParameters::default();
+ params.linear_success_probability = false;
+ let scorer = ProbabilisticScorer::new(
+ ProbabilisticScoringDecayParameters::default(), &network_graph, &logger);
+ generate_routes(bench, &network_graph, scorer, ¶ms,
+ channelmanager::provided_invoice_features(&UserConfig::default()), 0,
+ "generate_mpp_routes_with_nonlinear_probabilistic_scorer");
+ }
+
+ pub fn generate_large_mpp_routes_with_nonlinear_probabilistic_scorer(bench: &mut Criterion) {
+ let logger = TestLogger::new();
+ let network_graph = bench_utils::read_network_graph(&logger).unwrap();
+ let mut params = ProbabilisticScoringFeeParameters::default();
+ params.linear_success_probability = false;
+ 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,
+ "generate_large_mpp_routes_with_nonlinear_probabilistic_scorer");
+ }
+
+ fn generate_routes<S: ScoreLookUp + ScoreUpdate>(
bench: &mut Criterion, graph: &NetworkGraph<&TestLogger>, mut scorer: S,
- score_params: &S::ScoreParams, features: InvoiceFeatures, starting_amount: u64,
+ score_params: &S::ScoreParams, features: Bolt11InvoiceFeatures, starting_amount: u64,
bench_name: &'static str,
) {
let payer = bench_utils::payer_pubkey();
let mut idx = 0;
bench.bench_function(bench_name, |b| b.iter(|| {
let (first_hop, params, amt) = &route_endpoints[idx % route_endpoints.len()];
- assert!(get_route(&payer, params, &graph.read_only(), Some(&[first_hop]), *amt,
+ let route_params = RouteParameters::from_payment_params_and_value(params.clone(), *amt);
+ assert!(get_route(&payer, &route_params, &graph.read_only(), Some(&[first_hop]),
&DummyLogger{}, &scorer, score_params, &random_seed_bytes).is_ok());
idx += 1;
}));
//! Utilities for scoring payment channels.
//!
//! [`ProbabilisticScorer`] may be given to [`find_route`] to score payment channels during path
-//! finding when a custom [`Score`] implementation is not needed.
+//! finding when a custom [`ScoreLookUp`] implementation is not needed.
//!
//! # Example
//!
use crate::prelude::*;
use core::{cmp, fmt};
-use core::cell::{RefCell, RefMut};
+use core::cell::{RefCell, RefMut, Ref};
use core::convert::TryInto;
use core::ops::{Deref, DerefMut};
use core::time::Duration;
use crate::io::{self, Read};
-use crate::sync::{Mutex, MutexGuard};
+use crate::sync::{Mutex, MutexGuard, RwLock, RwLockReadGuard, RwLockWriteGuard};
/// We define Score ever-so-slightly differently based on whether we are being built for C bindings
/// or not. For users, `LockableScore` must somehow be writeable to disk. For Rust users, this is
macro_rules! define_score { ($($supertrait: path)*) => {
/// An interface used to score payment channels for path finding.
///
-/// Scoring is in terms of fees willing to be paid in order to avoid routing through a channel.
-pub trait Score $(: $supertrait)* {
+/// `ScoreLookUp` is used to determine the penalty for a given channel.
+///
+/// Scoring is in terms of fees willing to be paid in order to avoid routing through a channel.
+pub trait ScoreLookUp $(: $supertrait)* {
/// A configurable type which should contain various passed-in parameters for configuring the scorer,
/// on a per-routefinding-call basis through to the scorer methods,
/// which are used to determine the parameters for the suitability of channels for use.
fn channel_penalty_msat(
&self, short_channel_id: u64, source: &NodeId, target: &NodeId, usage: ChannelUsage, score_params: &Self::ScoreParams
) -> u64;
+}
+/// `ScoreUpdate` is used to update the scorer's internal state after a payment attempt.
+pub trait ScoreUpdate $(: $supertrait)* {
/// Handles updating channel penalties after failing to route through a channel.
fn payment_path_failed(&mut self, path: &Path, short_channel_id: u64);
fn probe_successful(&mut self, path: &Path);
}
-impl<S: Score, T: DerefMut<Target=S> $(+ $supertrait)*> Score for T {
- type ScoreParams = S::ScoreParams;
+impl<SP: Sized, S: ScoreLookUp<ScoreParams = SP>, T: Deref<Target=S> $(+ $supertrait)*> ScoreLookUp for T {
+ type ScoreParams = SP;
fn channel_penalty_msat(
&self, short_channel_id: u64, source: &NodeId, target: &NodeId, usage: ChannelUsage, score_params: &Self::ScoreParams
) -> u64 {
self.deref().channel_penalty_msat(short_channel_id, source, target, usage, score_params)
}
+}
+impl<S: ScoreUpdate, T: DerefMut<Target=S> $(+ $supertrait)*> ScoreUpdate for T {
fn payment_path_failed(&mut self, path: &Path, short_channel_id: u64) {
self.deref_mut().payment_path_failed(path, short_channel_id)
}
#[cfg(c_bindings)]
define_score!(Writeable);
+
#[cfg(not(c_bindings))]
define_score!();
/// A scorer that is accessed under a lock.
///
-/// Needed so that calls to [`Score::channel_penalty_msat`] in [`find_route`] can be made while
-/// having shared ownership of a scorer but without requiring internal locking in [`Score`]
+/// Needed so that calls to [`ScoreLookUp::channel_penalty_msat`] in [`find_route`] can be made while
+/// having shared ownership of a scorer but without requiring internal locking in [`ScoreUpdate`]
/// implementations. Internal locking would be detrimental to route finding performance and could
-/// result in [`Score::channel_penalty_msat`] returning a different value for the same channel.
+/// result in [`ScoreLookUp::channel_penalty_msat`] returning a different value for the same channel.
///
/// [`find_route`]: crate::routing::router::find_route
pub trait LockableScore<'a> {
- /// The [`Score`] type.
- type Score: 'a + Score;
+ /// The [`ScoreUpdate`] type.
+ type ScoreUpdate: 'a + ScoreUpdate;
+ /// The [`ScoreLookUp`] type.
+ type ScoreLookUp: 'a + ScoreLookUp;
- /// The locked [`Score`] type.
- type Locked: DerefMut<Target = Self::Score> + Sized;
+ /// The write locked [`ScoreUpdate`] type.
+ type WriteLocked: DerefMut<Target = Self::ScoreUpdate> + Sized;
- /// Returns the locked scorer.
- fn lock(&'a self) -> Self::Locked;
+ /// The read locked [`ScoreLookUp`] type.
+ type ReadLocked: Deref<Target = Self::ScoreLookUp> + Sized;
+
+ /// Returns read locked scorer.
+ fn read_lock(&'a self) -> Self::ReadLocked;
+
+ /// Returns write locked scorer.
+ fn write_lock(&'a self) -> Self::WriteLocked;
}
/// Refers to a scorer that is accessible under lock and also writeable to disk
#[cfg(not(c_bindings))]
impl<'a, T> WriteableScore<'a> for T where T: LockableScore<'a> + Writeable {}
-/// This is not exported to bindings users
-impl<'a, T: 'a + Score> LockableScore<'a> for Mutex<T> {
- type Score = T;
- type Locked = MutexGuard<'a, T>;
+#[cfg(not(c_bindings))]
+impl<'a, T: 'a + ScoreLookUp + ScoreUpdate> LockableScore<'a> for Mutex<T> {
+ type ScoreUpdate = T;
+ type ScoreLookUp = T;
+
+ type WriteLocked = MutexGuard<'a, Self::ScoreUpdate>;
+ type ReadLocked = MutexGuard<'a, Self::ScoreLookUp>;
- fn lock(&'a self) -> Self::Locked {
+ fn read_lock(&'a self) -> Self::ReadLocked {
+ Mutex::lock(self).unwrap()
+ }
+
+ fn write_lock(&'a self) -> Self::WriteLocked {
Mutex::lock(self).unwrap()
}
}
-impl<'a, T: 'a + Score> LockableScore<'a> for RefCell<T> {
- type Score = T;
- type Locked = RefMut<'a, T>;
+#[cfg(not(c_bindings))]
+impl<'a, T: 'a + ScoreUpdate + ScoreLookUp> LockableScore<'a> for RefCell<T> {
+ type ScoreUpdate = T;
+ type ScoreLookUp = T;
- fn lock(&'a self) -> Self::Locked {
+ type WriteLocked = RefMut<'a, Self::ScoreUpdate>;
+ type ReadLocked = Ref<'a, Self::ScoreLookUp>;
+
+ fn write_lock(&'a self) -> Self::WriteLocked {
self.borrow_mut()
}
+
+ fn read_lock(&'a self) -> Self::ReadLocked {
+ self.borrow()
+ }
+}
+
+#[cfg(not(c_bindings))]
+impl<'a, SP:Sized, T: 'a + ScoreUpdate + ScoreLookUp<ScoreParams = SP>> LockableScore<'a> for RwLock<T> {
+ type ScoreUpdate = T;
+ type ScoreLookUp = T;
+
+ type WriteLocked = RwLockWriteGuard<'a, Self::ScoreLookUp>;
+ type ReadLocked = RwLockReadGuard<'a, Self::ScoreUpdate>;
+
+ fn read_lock(&'a self) -> Self::ReadLocked {
+ RwLock::read(self).unwrap()
+ }
+
+ fn write_lock(&'a self) -> Self::WriteLocked {
+ RwLock::write(self).unwrap()
+ }
}
#[cfg(c_bindings)]
/// A concrete implementation of [`LockableScore`] which supports multi-threading.
-pub struct MultiThreadedLockableScore<T: Score> {
- score: Mutex<T>,
+pub struct MultiThreadedLockableScore<T: ScoreLookUp + ScoreUpdate> {
+ score: RwLock<T>,
}
#[cfg(c_bindings)]
-impl<'a, T: 'a + Score> LockableScore<'a> for MultiThreadedLockableScore<T> {
- type Score = T;
- type Locked = MultiThreadedScoreLock<'a, T>;
+impl<'a, SP:Sized, T: 'a + ScoreLookUp<ScoreParams = SP> + ScoreUpdate> LockableScore<'a> for MultiThreadedLockableScore<T> {
+ type ScoreUpdate = T;
+ type ScoreLookUp = T;
+ type WriteLocked = MultiThreadedScoreLockWrite<'a, Self::ScoreUpdate>;
+ type ReadLocked = MultiThreadedScoreLockRead<'a, Self::ScoreLookUp>;
+
+ fn read_lock(&'a self) -> Self::ReadLocked {
+ MultiThreadedScoreLockRead(self.score.read().unwrap())
+ }
- fn lock(&'a self) -> Self::Locked {
- MultiThreadedScoreLock(Mutex::lock(&self.score).unwrap())
+ fn write_lock(&'a self) -> Self::WriteLocked {
+ MultiThreadedScoreLockWrite(self.score.write().unwrap())
}
}
#[cfg(c_bindings)]
-impl<T: Score> Writeable for MultiThreadedLockableScore<T> {
+impl<T: ScoreUpdate + ScoreLookUp> Writeable for MultiThreadedLockableScore<T> {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
- self.lock().write(writer)
+ self.score.read().unwrap().write(writer)
}
}
#[cfg(c_bindings)]
-impl<'a, T: 'a + Score> WriteableScore<'a> for MultiThreadedLockableScore<T> {}
+impl<'a, T: 'a + ScoreUpdate + ScoreLookUp> WriteableScore<'a> for MultiThreadedLockableScore<T> {}
#[cfg(c_bindings)]
-impl<T: Score> MultiThreadedLockableScore<T> {
+impl<T: ScoreLookUp + ScoreUpdate> MultiThreadedLockableScore<T> {
/// Creates a new [`MultiThreadedLockableScore`] given an underlying [`Score`].
pub fn new(score: T) -> Self {
- MultiThreadedLockableScore { score: Mutex::new(score) }
+ MultiThreadedLockableScore { score: RwLock::new(score) }
}
}
#[cfg(c_bindings)]
/// A locked `MultiThreadedLockableScore`.
-pub struct MultiThreadedScoreLock<'a, T: Score>(MutexGuard<'a, T>);
+pub struct MultiThreadedScoreLockRead<'a, T: ScoreLookUp>(RwLockReadGuard<'a, T>);
#[cfg(c_bindings)]
-impl<'a, T: 'a + Score> Writeable for MultiThreadedScoreLock<'a, T> {
- fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
- self.0.write(writer)
+/// A locked `MultiThreadedLockableScore`.
+pub struct MultiThreadedScoreLockWrite<'a, T: ScoreUpdate>(RwLockWriteGuard<'a, T>);
+
+#[cfg(c_bindings)]
+impl<'a, T: 'a + ScoreLookUp> Deref for MultiThreadedScoreLockRead<'a, T> {
+ type Target = T;
+
+ fn deref(&self) -> &Self::Target {
+ self.0.deref()
}
}
#[cfg(c_bindings)]
-impl<'a, T: 'a + Score> DerefMut for MultiThreadedScoreLock<'a, T> {
- fn deref_mut(&mut self) -> &mut Self::Target {
- self.0.deref_mut()
- }
+impl<'a, T: 'a + ScoreUpdate> Writeable for MultiThreadedScoreLockWrite<'a, T> {
+ fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
+ self.0.write(writer)
+ }
}
#[cfg(c_bindings)]
-impl<'a, T: 'a + Score> Deref for MultiThreadedScoreLock<'a, T> {
+impl<'a, T: 'a + ScoreUpdate> Deref for MultiThreadedScoreLockWrite<'a, T> {
type Target = T;
- fn deref(&self) -> &Self::Target {
- self.0.deref()
- }
-}
-
-#[cfg(c_bindings)]
-/// This is not exported to bindings users
-impl<'a, T: Writeable> Writeable for RefMut<'a, T> {
- fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
- T::write(&**self, writer)
+ fn deref(&self) -> &Self::Target {
+ self.0.deref()
}
}
#[cfg(c_bindings)]
-/// This is not exported to bindings users
-impl<'a, S: Writeable> Writeable for MutexGuard<'a, S> {
- fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
- S::write(&**self, writer)
+impl<'a, T: 'a + ScoreUpdate> DerefMut for MultiThreadedScoreLockWrite<'a, T> {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ self.0.deref_mut()
}
}
-/// Proposed use of a channel passed as a parameter to [`Score::channel_penalty_msat`].
+
+/// Proposed use of a channel passed as a parameter to [`ScoreLookUp::channel_penalty_msat`].
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ChannelUsage {
/// The amount to send through the channel, denominated in millisatoshis.
}
#[derive(Clone)]
-/// [`Score`] implementation that uses a fixed penalty.
+/// [`ScoreLookUp`] implementation that uses a fixed penalty.
pub struct FixedPenaltyScorer {
penalty_msat: u64,
}
}
}
-impl Score for FixedPenaltyScorer {
+impl ScoreLookUp for FixedPenaltyScorer {
type ScoreParams = ();
fn channel_penalty_msat(&self, _: u64, _: &NodeId, _: &NodeId, _: ChannelUsage, _score_params: &Self::ScoreParams) -> u64 {
self.penalty_msat
}
+}
+impl ScoreUpdate for FixedPenaltyScorer {
fn payment_path_failed(&mut self, _path: &Path, _short_channel_id: u64) {}
fn payment_path_successful(&mut self, _path: &Path) {}
#[cfg(feature = "no-std")]
type ConfiguredTime = Eternity;
-/// [`Score`] implementation using channel success probability distributions.
+/// [`ScoreLookUp`] implementation using channel success probability distributions.
///
/// Channels are tracked with upper and lower liquidity bounds - when an HTLC fails at a channel,
/// we learn that the upper-bound on the available liquidity is lower than the amount of the HTLC.
/// [`historical_liquidity_penalty_amount_multiplier_msat`]: ProbabilisticScoringFeeParameters::historical_liquidity_penalty_amount_multiplier_msat
pub type ProbabilisticScorer<G, L> = ProbabilisticScorerUsingTime::<G, L, ConfiguredTime>;
-/// Probabilistic [`Score`] implementation.
+/// Probabilistic [`ScoreLookUp`] implementation.
///
/// This is not exported to bindings users generally all users should use the [`ProbabilisticScorer`] type alias.
pub struct ProbabilisticScorerUsingTime<G: Deref<Target = NetworkGraph<L>>, L: Deref, T: Time>
/// Default value: 500 msat
pub base_penalty_msat: u64,
- /// A multiplier used with the payment amount to calculate a fixed penalty applied to each
- /// channel, in excess of the [`base_penalty_msat`].
+ /// A multiplier used with the total amount flowing over a channel to calculate a fixed penalty
+ /// applied to each channel, in excess of the [`base_penalty_msat`].
///
/// The purpose of the amount penalty is to avoid having fees dominate the channel cost (i.e.,
/// fees plus penalty) for large payments. The penalty is computed as the product of this
- /// multiplier and `2^30`ths of the payment amount.
+ /// multiplier and `2^30`ths of the total amount flowing over a channel (i.e. the payment
+ /// amount plus the amount of any other HTLCs flowing we sent over the same channel).
///
/// ie `base_penalty_amount_multiplier_msat * amount_msat / 2^30`
///
/// [`liquidity_offset_half_life`]: ProbabilisticScoringDecayParameters::liquidity_offset_half_life
pub liquidity_penalty_multiplier_msat: u64,
- /// A multiplier used in conjunction with a payment amount and the negative `log10` of the
- /// channel's success probability for the payment, as determined by our latest estimates of the
- /// channel's liquidity, to determine the amount penalty.
+ /// A multiplier used in conjunction with the total amount flowing over a channel and the
+ /// negative `log10` of the channel's success probability for the payment, as determined by our
+ /// latest estimates of the channel's liquidity, to determine the amount penalty.
///
/// The purpose of the amount penalty is to avoid having fees dominate the channel cost (i.e.,
/// fees plus penalty) for large payments. The penalty is computed as the product of this
- /// multiplier and `2^20`ths of the payment amount, weighted by the negative `log10` of the
- /// success probability.
+ /// multiplier and `2^20`ths of the amount flowing over this channel, weighted by the negative
+ /// `log10` of the success probability.
///
/// `-log10(success_probability) * liquidity_penalty_amount_multiplier_msat * amount_msat / 2^20`
///
/// [`liquidity_penalty_multiplier_msat`]: Self::liquidity_penalty_multiplier_msat
pub historical_liquidity_penalty_multiplier_msat: u64,
- /// A multiplier used in conjunction with the payment amount and the negative `log10` of the
- /// channel's success probability for the payment, as determined based on the history of our
- /// estimates of the channel's available liquidity, to determine a penalty.
+ /// A multiplier used in conjunction with the total amount flowing over a channel and the
+ /// negative `log10` of the channel's success probability for the payment, as determined based
+ /// on the history of our estimates of the channel's available liquidity, to determine a
+ /// penalty.
///
/// The purpose of the amount penalty is to avoid having fees dominate the channel cost for
- /// large payments. The penalty is computed as the product of this multiplier and the `2^20`ths
- /// of the payment amount, weighted by the negative `log10` of the success probability.
+ /// large payments. The penalty is computed as the product of this multiplier and `2^20`ths
+ /// of the amount flowing over this channel, weighted by the negative `log10` of the success
+ /// probability.
///
/// This penalty is similar to [`liquidity_penalty_amount_multiplier_msat`], however, instead
/// of using only our latest estimate for the current liquidity available in the channel, it
pub manual_node_penalties: HashMap<NodeId, u64>,
/// This penalty is applied when `htlc_maximum_msat` is equal to or larger than half of the
- /// channel's capacity, (ie. htlc_maximum_msat ≥ 0.5 * channel_capacity) which makes us
+ /// channel's capacity, (ie. htlc_maximum_msat >= 0.5 * channel_capacity) which makes us
/// prefer nodes with a smaller `htlc_maximum_msat`. We treat such nodes preferentially
/// as this makes balance discovery attacks harder to execute, thereby creating an incentive
/// to restrict `htlc_maximum_msat` and improve privacy.
/// Default value: 250 msat
pub anti_probing_penalty_msat: u64,
- /// This penalty is applied when the amount we're attempting to send over a channel exceeds our
- /// current estimate of the channel's available liquidity.
+ /// This penalty is applied when the total amount flowing over a channel exceeds our current
+ /// estimate of the channel's available liquidity. The total amount is the amount of the
+ /// current HTLC plus any HTLCs which we've sent over the same channel.
///
/// Note that in this case all other penalties, including the
/// [`liquidity_penalty_multiplier_msat`] and [`liquidity_penalty_amount_multiplier_msat`]-based
/// [`base_penalty_msat`]: Self::base_penalty_msat
/// [`anti_probing_penalty_msat`]: Self::anti_probing_penalty_msat
pub considered_impossible_penalty_msat: u64,
+
+ /// In order to calculate most of the scores above, we must first convert a lower and upper
+ /// bound on the available liquidity in a channel into the probability that we think a payment
+ /// will succeed. That probability is derived from a Probability Density Function for where we
+ /// think the liquidity in a channel likely lies, given such bounds.
+ ///
+ /// If this flag is set, that PDF is simply a constant - we assume that the actual available
+ /// liquidity in a channel is just as likely to be at any point between our lower and upper
+ /// bounds.
+ ///
+ /// If this flag is *not* set, that PDF is `(x - 0.5*capacity) ^ 2`. That is, we use an
+ /// exponential curve which expects the liquidity of a channel to lie "at the edges". This
+ /// matches experimental results - most routing nodes do not aggressively rebalance their
+ /// channels and flows in the network are often unbalanced, leaving liquidity usually
+ /// unavailable.
+ ///
+ /// Thus, for the "best" routes, leave this flag `false`. However, the flag does imply a number
+ /// of floating-point multiplications in the hottest routing code, which may lead to routing
+ /// performance degradation on some machines.
+ ///
+ /// Default value: false
+ pub linear_success_probability: bool,
}
impl Default for ProbabilisticScoringFeeParameters {
considered_impossible_penalty_msat: 1_0000_0000_000,
historical_liquidity_penalty_multiplier_msat: 10_000,
historical_liquidity_penalty_amount_multiplier_msat: 64,
+ linear_success_probability: false,
}
}
}
manual_node_penalties: HashMap::new(),
anti_probing_penalty_msat: 0,
considered_impossible_penalty_msat: 0,
+ linear_success_probability: true,
}
}
}
}
}
-/// Tracks the historical state of a distribution as a weighted average of how much time was spent
-/// in each of 8 buckets.
-#[derive(Clone, Copy)]
-struct HistoricalBucketRangeTracker {
- buckets: [u16; 8],
-}
-
-impl HistoricalBucketRangeTracker {
- fn new() -> Self { Self { buckets: [0; 8] } }
- fn track_datapoint(&mut self, liquidity_offset_msat: u64, capacity_msat: u64) {
- // We have 8 leaky buckets for min and max liquidity. Each bucket tracks the amount of time
- // we spend in each bucket as a 16-bit fixed-point number with a 5 bit fractional part.
- //
- // Each time we update our liquidity estimate, we add 32 (1.0 in our fixed-point system) to
- // the buckets for the current min and max liquidity offset positions.
- //
- // We then decay each bucket by multiplying by 2047/2048 (avoiding dividing by a
- // non-power-of-two). This ensures we can't actually overflow the u16 - when we get to
- // 63,457 adding 32 and decaying by 2047/2048 leaves us back at 63,457.
- //
- // In total, this allows us to track data for the last 8,000 or so payments across a given
- // channel.
- //
- // These constants are a balance - we try to fit in 2 bytes per bucket to reduce overhead,
- // and need to balance having more bits in the decimal part (to ensure decay isn't too
- // non-linear) with having too few bits in the mantissa, causing us to not store very many
- // datapoints.
- //
- // The constants were picked experimentally, selecting a decay amount that restricts us
- // from overflowing buckets without having to cap them manually.
-
- // Ensure the bucket index is in the range [0, 7], even if the liquidity offset is zero or
- // the channel's capacity, though the second should generally never happen.
- debug_assert!(liquidity_offset_msat <= capacity_msat);
- let bucket_idx: u8 = (liquidity_offset_msat * 8 / capacity_msat.saturating_add(1))
- .try_into().unwrap_or(32); // 32 is bogus for 8 buckets, and will be ignored
- debug_assert!(bucket_idx < 8);
- if bucket_idx < 8 {
- for e in self.buckets.iter_mut() {
- *e = ((*e as u32) * 2047 / 2048) as u16;
- }
- self.buckets[bucket_idx as usize] = self.buckets[bucket_idx as usize].saturating_add(32);
- }
- }
- /// Decay all buckets by the given number of half-lives. Used to more aggressively remove old
- /// datapoints as we receive newer information.
- fn time_decay_data(&mut self, half_lives: u32) {
- for e in self.buckets.iter_mut() {
- *e = e.checked_shr(half_lives).unwrap_or(0);
- }
- }
-}
-
-impl_writeable_tlv_based!(HistoricalBucketRangeTracker, { (0, buckets, required) });
-
-struct HistoricalMinMaxBuckets<'a> {
- min_liquidity_offset_history: &'a HistoricalBucketRangeTracker,
- max_liquidity_offset_history: &'a HistoricalBucketRangeTracker,
-}
-
-impl HistoricalMinMaxBuckets<'_> {
- #[inline]
- fn get_decayed_buckets<T: Time>(&self, now: T, last_updated: T, half_life: Duration)
- -> ([u16; 8], [u16; 8], u32) {
- let required_decays = now.duration_since(last_updated).as_secs()
- .checked_div(half_life.as_secs())
- .map_or(u32::max_value(), |decays| cmp::min(decays, u32::max_value() as u64) as u32);
- let mut min_buckets = *self.min_liquidity_offset_history;
- min_buckets.time_decay_data(required_decays);
- let mut max_buckets = *self.max_liquidity_offset_history;
- max_buckets.time_decay_data(required_decays);
- (min_buckets.buckets, max_buckets.buckets, required_decays)
- }
-
- #[inline]
- fn calculate_success_probability_times_billion<T: Time>(
- &self, now: T, last_updated: T, half_life: Duration, payment_amt_64th_bucket: u8)
- -> Option<u64> {
- // If historical penalties are enabled, calculate the penalty by walking the set of
- // historical liquidity bucket (min, max) combinations (where min_idx < max_idx) and, for
- // each, calculate the probability of success given our payment amount, then total the
- // weighted average probability of success.
- //
- // We use a sliding scale to decide which point within a given bucket will be compared to
- // the amount being sent - for lower-bounds, the amount being sent is compared to the lower
- // edge of the first bucket (i.e. zero), but compared to the upper 7/8ths of the last
- // bucket (i.e. 9 times the index, or 63), with each bucket in between increasing the
- // comparison point by 1/64th. For upper-bounds, the same applies, however with an offset
- // of 1/64th (i.e. starting at one and ending at 64). This avoids failing to assign
- // penalties to channels at the edges.
- //
- // If we used the bottom edge of buckets, we'd end up never assigning any penalty at all to
- // such a channel when sending less than ~0.19% of the channel's capacity (e.g. ~200k sats
- // for a 1 BTC channel!).
- //
- // If we used the middle of each bucket we'd never assign any penalty at all when sending
- // less than 1/16th of a channel's capacity, or 1/8th if we used the top of the bucket.
- let mut total_valid_points_tracked = 0;
-
- // Check if all our buckets are zero, once decayed and treat it as if we had no data. We
- // don't actually use the decayed buckets, though, as that would lose precision.
- let (decayed_min_buckets, decayed_max_buckets, required_decays) =
- self.get_decayed_buckets(now, last_updated, half_life);
- if decayed_min_buckets.iter().all(|v| *v == 0) || decayed_max_buckets.iter().all(|v| *v == 0) {
- return None;
- }
-
- for (min_idx, min_bucket) in self.min_liquidity_offset_history.buckets.iter().enumerate() {
- for max_bucket in self.max_liquidity_offset_history.buckets.iter().take(8 - min_idx) {
- total_valid_points_tracked += (*min_bucket as u64) * (*max_bucket as u64);
- }
- }
- // If the total valid points is smaller than 1.0 (i.e. 32 in our fixed-point scheme), treat
- // it as if we were fully decayed.
- if total_valid_points_tracked.checked_shr(required_decays).unwrap_or(0) < 32*32 {
- return None;
- }
-
- let mut cumulative_success_prob_times_billion = 0;
- for (min_idx, min_bucket) in self.min_liquidity_offset_history.buckets.iter().enumerate() {
- for (max_idx, max_bucket) in self.max_liquidity_offset_history.buckets.iter().enumerate().take(8 - min_idx) {
- let bucket_prob_times_million = (*min_bucket as u64) * (*max_bucket as u64)
- * 1024 * 1024 / total_valid_points_tracked;
- let min_64th_bucket = min_idx as u8 * 9;
- let max_64th_bucket = (7 - max_idx as u8) * 9 + 1;
- if payment_amt_64th_bucket > max_64th_bucket {
- // Success probability 0, the payment amount is above the max liquidity
- } else if payment_amt_64th_bucket <= min_64th_bucket {
- cumulative_success_prob_times_billion += bucket_prob_times_million * 1024;
- } else {
- cumulative_success_prob_times_billion += bucket_prob_times_million *
- ((max_64th_bucket - payment_amt_64th_bucket) as u64) * 1024 /
- ((max_64th_bucket - min_64th_bucket) as u64);
- }
- }
- }
-
- Some(cumulative_success_prob_times_billion)
- }
-}
-
/// Accounting for channel liquidity balance uncertainty.
///
/// Direction is defined in terms of [`NodeId`] partial ordering, where the source node is the
struct DirectedChannelLiquidity<L: Deref<Target = u64>, BRT: Deref<Target = HistoricalBucketRangeTracker>, T: Time, U: Deref<Target = T>> {
min_liquidity_offset_msat: L,
max_liquidity_offset_msat: L,
- min_liquidity_offset_history: BRT,
- max_liquidity_offset_history: BRT,
- inflight_htlc_msat: u64,
+ liquidity_history: HistoricalMinMaxBuckets<BRT>,
capacity_msat: u64,
last_updated: U,
now: T,
let log_direction = |source, target| {
if let Some((directed_info, _)) = chan_debug.as_directed_to(target) {
let amt = directed_info.effective_capacity().as_msat();
- let dir_liq = liq.as_directed(source, target, 0, amt, self.decay_params);
+ let dir_liq = liq.as_directed(source, target, amt, self.decay_params);
- let buckets = HistoricalMinMaxBuckets {
- min_liquidity_offset_history: &dir_liq.min_liquidity_offset_history,
- max_liquidity_offset_history: &dir_liq.max_liquidity_offset_history,
- };
- let (min_buckets, max_buckets, _) = buckets.get_decayed_buckets(now,
- *dir_liq.last_updated, self.decay_params.historical_no_updates_half_life);
+ let (min_buckets, max_buckets) = dir_liq.liquidity_history
+ .get_decayed_buckets(now, *dir_liq.last_updated,
+ self.decay_params.historical_no_updates_half_life)
+ .unwrap_or(([0; 32], [0; 32]));
log_debug!(self.logger, core::concat!(
"Liquidity from {} to {} via {} is in the range ({}, {}).\n",
- "\tHistorical min liquidity octile relative probabilities: {} {} {} {} {} {} {} {}\n",
- "\tHistorical max liquidity octile relative probabilities: {} {} {} {} {} {} {} {}"),
+ "\tHistorical min liquidity bucket relative probabilities:\n",
+ "\t\t{} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {}\n",
+ "\tHistorical max liquidity bucket relative probabilities:\n",
+ "\t\t{} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {}"),
source, target, scid, dir_liq.min_liquidity_msat(), dir_liq.max_liquidity_msat(),
- min_buckets[0], min_buckets[1], min_buckets[2], min_buckets[3],
- min_buckets[4], min_buckets[5], min_buckets[6], min_buckets[7],
+ min_buckets[ 0], min_buckets[ 1], min_buckets[ 2], min_buckets[ 3],
+ min_buckets[ 4], min_buckets[ 5], min_buckets[ 6], min_buckets[ 7],
+ min_buckets[ 8], min_buckets[ 9], min_buckets[10], min_buckets[11],
+ min_buckets[12], min_buckets[13], min_buckets[14], min_buckets[15],
+ min_buckets[16], min_buckets[17], min_buckets[18], min_buckets[19],
+ min_buckets[20], min_buckets[21], min_buckets[22], min_buckets[23],
+ min_buckets[24], min_buckets[25], min_buckets[26], min_buckets[27],
+ min_buckets[28], min_buckets[29], min_buckets[30], min_buckets[31],
// Note that the liquidity buckets are an offset from the edge, so we
// inverse the max order to get the probabilities from zero.
- max_buckets[7], max_buckets[6], max_buckets[5], max_buckets[4],
- max_buckets[3], max_buckets[2], max_buckets[1], max_buckets[0]);
+ max_buckets[31], max_buckets[30], max_buckets[29], max_buckets[28],
+ max_buckets[27], max_buckets[26], max_buckets[25], max_buckets[24],
+ max_buckets[23], max_buckets[22], max_buckets[21], max_buckets[20],
+ max_buckets[19], max_buckets[18], max_buckets[17], max_buckets[16],
+ max_buckets[15], max_buckets[14], max_buckets[13], max_buckets[12],
+ max_buckets[11], max_buckets[10], max_buckets[ 9], max_buckets[ 8],
+ max_buckets[ 7], max_buckets[ 6], max_buckets[ 5], max_buckets[ 4],
+ max_buckets[ 3], max_buckets[ 2], max_buckets[ 1], max_buckets[ 0]);
} else {
log_debug!(self.logger, "No amount known for SCID {} from {:?} to {:?}", scid, source, target);
}
if let Some(liq) = self.channel_liquidities.get(&scid) {
if let Some((directed_info, source)) = chan.as_directed_to(target) {
let amt = directed_info.effective_capacity().as_msat();
- let dir_liq = liq.as_directed(source, target, 0, amt, self.decay_params);
+ let dir_liq = liq.as_directed(source, target, amt, self.decay_params);
return Some((dir_liq.min_liquidity_msat(), dir_liq.max_liquidity_msat()));
}
}
/// Query the historical estimated minimum and maximum liquidity available for sending a
/// payment over the channel with `scid` towards the given `target` node.
///
- /// Returns two sets of 8 buckets. The first set describes the octiles for lower-bound
- /// liquidity estimates, the second set describes the octiles for upper-bound liquidity
- /// estimates. Each bucket describes the relative frequency at which we've seen a liquidity
- /// bound in the octile relative to the channel's total capacity, on an arbitrary scale.
- /// Because the values are slowly decayed, more recent data points are weighted more heavily
- /// than older datapoints.
+ /// Returns two sets of 32 buckets. The first set describes the lower-bound liquidity history,
+ /// the second set describes the upper-bound liquidity history. Each bucket describes the
+ /// relative frequency at which we've seen a liquidity bound in the bucket's range relative to
+ /// the channel's total capacity, on an arbitrary scale. Because the values are slowly decayed,
+ /// more recent data points are weighted more heavily than older datapoints.
+ ///
+ /// Note that the range of each bucket varies by its location to provide more granular results
+ /// at the edges of a channel's capacity, where it is more likely to sit.
///
- /// When scoring, the estimated probability that an upper-/lower-bound lies in a given octile
- /// relative to the channel's total capacity is calculated by dividing that bucket's value with
- /// the total of all buckets for the given bound.
+ /// When scoring, the estimated probability that an upper-/lower-bound lies in a given bucket
+ /// is calculated by dividing that bucket's value with the total value of all buckets.
///
- /// For example, a value of `[0, 0, 0, 0, 0, 0, 32]` indicates that we believe the probability
- /// of a bound being in the top octile to be 100%, and have never (recently) seen it in any
- /// other octiles. A value of `[31, 0, 0, 0, 0, 0, 0, 32]` indicates we've seen the bound being
- /// both in the top and bottom octile, and roughly with similar (recent) frequency.
+ /// For example, using a lower bucket count for illustrative purposes, a value of
+ /// `[0, 0, 0, ..., 0, 32]` indicates that we believe the probability of a bound being very
+ /// close to the channel's capacity to be 100%, and have never (recently) seen it in any other
+ /// bucket. A value of `[31, 0, 0, ..., 0, 0, 32]` indicates we've seen the bound being both
+ /// in the top and bottom bucket, and roughly with similar (recent) frequency.
///
/// Because the datapoints are decayed slowly over time, values will eventually return to
- /// `Some(([0; 8], [0; 8]))`.
+ /// `Some(([1; 32], [1; 32]))` and then to `None` once no datapoints remain.
+ ///
+ /// In order to fetch a single success probability from the buckets provided here, as used in
+ /// the scoring model, see [`Self::historical_estimated_payment_success_probability`].
pub fn historical_estimated_channel_liquidity_probabilities(&self, scid: u64, target: &NodeId)
- -> Option<([u16; 8], [u16; 8])> {
+ -> Option<([u16; 32], [u16; 32])> {
let graph = self.network_graph.read_only();
if let Some(chan) = graph.channels().get(&scid) {
if let Some(liq) = self.channel_liquidities.get(&scid) {
if let Some((directed_info, source)) = chan.as_directed_to(target) {
let amt = directed_info.effective_capacity().as_msat();
- let dir_liq = liq.as_directed(source, target, 0, amt, self.decay_params);
-
- let buckets = HistoricalMinMaxBuckets {
- min_liquidity_offset_history: &dir_liq.min_liquidity_offset_history,
- max_liquidity_offset_history: &dir_liq.max_liquidity_offset_history,
- };
- let (min_buckets, mut max_buckets, _) = buckets.get_decayed_buckets(T::now(),
- *dir_liq.last_updated, self.decay_params.historical_no_updates_half_life);
+ let dir_liq = liq.as_directed(source, target, amt, self.decay_params);
+
+ let (min_buckets, mut max_buckets) =
+ dir_liq.liquidity_history.get_decayed_buckets(
+ dir_liq.now, *dir_liq.last_updated,
+ self.decay_params.historical_no_updates_half_life
+ )?;
+
// Note that the liquidity buckets are an offset from the edge, so we inverse
// the max order to get the probabilities from zero.
max_buckets.reverse();
}
None
}
+
+ /// Query the probability of payment success sending the given `amount_msat` over the channel
+ /// with `scid` towards the given `target` node, based on the historical estimated liquidity
+ /// bounds.
+ ///
+ /// These are the same bounds as returned by
+ /// [`Self::historical_estimated_channel_liquidity_probabilities`] (but not those returned by
+ /// [`Self::estimated_channel_liquidity_range`]).
+ pub fn historical_estimated_payment_success_probability(
+ &self, scid: u64, target: &NodeId, amount_msat: u64, params: &ProbabilisticScoringFeeParameters)
+ -> Option<f64> {
+ let graph = self.network_graph.read_only();
+
+ if let Some(chan) = graph.channels().get(&scid) {
+ if let Some(liq) = self.channel_liquidities.get(&scid) {
+ if let Some((directed_info, source)) = chan.as_directed_to(target) {
+ let capacity_msat = directed_info.effective_capacity().as_msat();
+ let dir_liq = liq.as_directed(source, target, capacity_msat, self.decay_params);
+
+ return dir_liq.liquidity_history.calculate_success_probability_times_billion(
+ dir_liq.now, *dir_liq.last_updated,
+ self.decay_params.historical_no_updates_half_life, ¶ms, amount_msat,
+ capacity_msat
+ ).map(|p| p as f64 / (1024 * 1024 * 1024) as f64);
+ }
+ }
+ }
+ None
+ }
}
impl<T: Time> ChannelLiquidity<T> {
/// Returns a view of the channel liquidity directed from `source` to `target` assuming
/// `capacity_msat`.
fn as_directed(
- &self, source: &NodeId, target: &NodeId, inflight_htlc_msat: u64, capacity_msat: u64, decay_params: ProbabilisticScoringDecayParameters
+ &self, source: &NodeId, target: &NodeId, capacity_msat: u64, decay_params: ProbabilisticScoringDecayParameters
) -> DirectedChannelLiquidity<&u64, &HistoricalBucketRangeTracker, T, &T> {
let (min_liquidity_offset_msat, max_liquidity_offset_msat, min_liquidity_offset_history, max_liquidity_offset_history) =
if source < target {
DirectedChannelLiquidity {
min_liquidity_offset_msat,
max_liquidity_offset_msat,
- min_liquidity_offset_history,
- max_liquidity_offset_history,
- inflight_htlc_msat,
+ liquidity_history: HistoricalMinMaxBuckets {
+ min_liquidity_offset_history,
+ max_liquidity_offset_history,
+ },
capacity_msat,
last_updated: &self.last_updated,
now: T::now(),
/// Returns a mutable view of the channel liquidity directed from `source` to `target` assuming
/// `capacity_msat`.
fn as_directed_mut(
- &mut self, source: &NodeId, target: &NodeId, inflight_htlc_msat: u64, capacity_msat: u64, decay_params: ProbabilisticScoringDecayParameters
+ &mut self, source: &NodeId, target: &NodeId, capacity_msat: u64, decay_params: ProbabilisticScoringDecayParameters
) -> DirectedChannelLiquidity<&mut u64, &mut HistoricalBucketRangeTracker, T, &mut T> {
let (min_liquidity_offset_msat, max_liquidity_offset_msat, min_liquidity_offset_history, max_liquidity_offset_history) =
if source < target {
DirectedChannelLiquidity {
min_liquidity_offset_msat,
max_liquidity_offset_msat,
- min_liquidity_offset_history,
- max_liquidity_offset_history,
- inflight_htlc_msat,
+ liquidity_history: HistoricalMinMaxBuckets {
+ min_liquidity_offset_history,
+ max_liquidity_offset_history,
+ },
capacity_msat,
last_updated: &mut self.last_updated,
now: T::now(),
const AMOUNT_PENALTY_DIVISOR: u64 = 1 << 20;
const BASE_AMOUNT_PENALTY_DIVISOR: u64 = 1 << 30;
+/// Raises three `f64`s to the 3rd power, without `powi` because it requires `std` (dunno why).
+#[inline(always)]
+fn three_f64_pow_3(a: f64, b: f64, c: f64) -> (f64, f64, f64) {
+ (a * a * a, b * b * b, c * c * c)
+}
+
+/// Given liquidity bounds, calculates the success probability (in the form of a numerator and
+/// denominator) of an HTLC. This is a key assumption in our scoring models.
+///
+/// Must not return a numerator or denominator greater than 2^31 for arguments less than 2^31.
+///
+/// min_zero_implies_no_successes signals that a `min_liquidity_msat` of 0 means we've not
+/// (recently) seen an HTLC successfully complete over this channel.
+#[inline(always)]
+fn success_probability(
+ amount_msat: u64, min_liquidity_msat: u64, max_liquidity_msat: u64, capacity_msat: u64,
+ params: &ProbabilisticScoringFeeParameters, min_zero_implies_no_successes: bool,
+) -> (u64, u64) {
+ debug_assert!(min_liquidity_msat <= amount_msat);
+ debug_assert!(amount_msat < max_liquidity_msat);
+ debug_assert!(max_liquidity_msat <= capacity_msat);
+
+ let (numerator, mut denominator) =
+ if params.linear_success_probability {
+ (max_liquidity_msat - amount_msat,
+ (max_liquidity_msat - min_liquidity_msat).saturating_add(1))
+ } else {
+ let capacity = capacity_msat as f64;
+ let min = (min_liquidity_msat as f64) / capacity;
+ let max = (max_liquidity_msat as f64) / capacity;
+ let amount = (amount_msat as f64) / capacity;
+
+ // Assume the channel has a probability density function of (x - 0.5)^2 for values from
+ // 0 to 1 (where 1 is the channel's full capacity). The success probability given some
+ // liquidity bounds is thus the integral under the curve from the amount to maximum
+ // estimated liquidity, divided by the same integral from the minimum to the maximum
+ // estimated liquidity bounds.
+ //
+ // Because the integral from x to y is simply (y - 0.5)^3 - (x - 0.5)^3, we can
+ // calculate the cumulative density function between the min/max bounds trivially. Note
+ // that we don't bother to normalize the CDF to total to 1, as it will come out in the
+ // division of num / den.
+ let (max_pow, amt_pow, min_pow) = three_f64_pow_3(max - 0.5, amount - 0.5, min - 0.5);
+ let num = max_pow - amt_pow;
+ let den = max_pow - min_pow;
+
+ // Because our numerator and denominator max out at 0.5^3 we need to multiply them by
+ // quite a large factor to get something useful (ideally in the 2^30 range).
+ const BILLIONISH: f64 = 1024.0 * 1024.0 * 1024.0;
+ let numerator = (num * BILLIONISH) as u64 + 1;
+ let denominator = (den * BILLIONISH) as u64 + 1;
+ debug_assert!(numerator <= 1 << 30, "Got large numerator ({}) from float {}.", numerator, num);
+ debug_assert!(denominator <= 1 << 30, "Got large denominator ({}) from float {}.", denominator, den);
+ (numerator, denominator)
+ };
+
+ if min_zero_implies_no_successes && min_liquidity_msat == 0 &&
+ denominator < u64::max_value() / 21
+ {
+ // If we have no knowledge of the channel, scale probability down by ~75%
+ // Note that we prefer to increase the denominator rather than decrease the numerator as
+ // the denominator is more likely to be larger and thus provide greater precision. This is
+ // mostly an overoptimization but makes a large difference in tests.
+ denominator = denominator * 21 / 16
+ }
+
+ (numerator, denominator)
+}
+
impl<L: Deref<Target = u64>, BRT: Deref<Target = HistoricalBucketRangeTracker>, T: Time, U: Deref<Target = T>> DirectedChannelLiquidity< L, BRT, T, U> {
/// Returns a liquidity penalty for routing the given HTLC `amount_msat` through the channel in
/// this direction.
fn penalty_msat(&self, amount_msat: u64, score_params: &ProbabilisticScoringFeeParameters) -> u64 {
+ let available_capacity = self.capacity_msat;
let max_liquidity_msat = self.max_liquidity_msat();
let min_liquidity_msat = core::cmp::min(self.min_liquidity_msat(), max_liquidity_msat);
score_params.liquidity_penalty_amount_multiplier_msat)
.saturating_add(score_params.considered_impossible_penalty_msat)
} else {
- let numerator = (max_liquidity_msat - amount_msat).saturating_add(1);
- let denominator = (max_liquidity_msat - min_liquidity_msat).saturating_add(1);
- if amount_msat - min_liquidity_msat < denominator / PRECISION_LOWER_BOUND_DENOMINATOR {
+ let (numerator, denominator) = success_probability(amount_msat,
+ min_liquidity_msat, max_liquidity_msat, available_capacity, score_params, false);
+ if denominator - numerator < denominator / PRECISION_LOWER_BOUND_DENOMINATOR {
// If the failure probability is < 1.5625% (as 1 - numerator/denominator < 1/64),
// don't bother trying to use the log approximation as it gets too noisy to be
// particularly helpful, instead just round down to 0.
}
};
+ if amount_msat >= available_capacity {
+ // We're trying to send more than the capacity, use a max penalty.
+ res = res.saturating_add(Self::combined_penalty_msat(amount_msat,
+ NEGATIVE_LOG10_UPPER_BOUND * 2048,
+ score_params.historical_liquidity_penalty_multiplier_msat,
+ score_params.historical_liquidity_penalty_amount_multiplier_msat));
+ return res;
+ }
+
if score_params.historical_liquidity_penalty_multiplier_msat != 0 ||
score_params.historical_liquidity_penalty_amount_multiplier_msat != 0 {
- let payment_amt_64th_bucket = if amount_msat < u64::max_value() / 64 {
- amount_msat * 64 / self.capacity_msat.saturating_add(1)
- } else {
- // Only use 128-bit arithmetic when multiplication will overflow to avoid 128-bit
- // division. This branch should only be hit in fuzz testing since the amount would
- // need to be over 2.88 million BTC in practice.
- ((amount_msat as u128) * 64 / (self.capacity_msat as u128).saturating_add(1))
- .try_into().unwrap_or(65)
- };
- #[cfg(not(fuzzing))]
- debug_assert!(payment_amt_64th_bucket <= 64);
- if payment_amt_64th_bucket > 64 { return res; }
-
- let buckets = HistoricalMinMaxBuckets {
- min_liquidity_offset_history: &self.min_liquidity_offset_history,
- max_liquidity_offset_history: &self.max_liquidity_offset_history,
- };
- if let Some(cumulative_success_prob_times_billion) = buckets
+ if let Some(cumulative_success_prob_times_billion) = self.liquidity_history
.calculate_success_probability_times_billion(self.now, *self.last_updated,
- self.decay_params.historical_no_updates_half_life, payment_amt_64th_bucket as u8)
+ self.decay_params.historical_no_updates_half_life, score_params, amount_msat,
+ self.capacity_msat)
{
let historical_negative_log10_times_2048 = approx::negative_log10_times_2048(cumulative_success_prob_times_billion + 1, 1024 * 1024 * 1024);
res = res.saturating_add(Self::combined_penalty_msat(amount_msat,
// If we don't have any valid points (or, once decayed, we have less than a full
// point), redo the non-historical calculation with no liquidity bounds tracked and
// the historical penalty multipliers.
- let available_capacity = self.available_capacity();
- let numerator = available_capacity.saturating_sub(amount_msat).saturating_add(1);
- let denominator = available_capacity.saturating_add(1);
+ let (numerator, denominator) = success_probability(amount_msat, 0,
+ available_capacity, available_capacity, score_params, true);
let negative_log10_times_2048 =
approx::negative_log10_times_2048(numerator, denominator);
res = res.saturating_add(Self::combined_penalty_msat(amount_msat, negative_log10_times_2048,
/// Computes the liquidity penalty from the penalty multipliers.
#[inline(always)]
- fn combined_penalty_msat(amount_msat: u64, negative_log10_times_2048: u64,
+ fn combined_penalty_msat(amount_msat: u64, mut negative_log10_times_2048: u64,
liquidity_penalty_multiplier_msat: u64, liquidity_penalty_amount_multiplier_msat: u64,
) -> u64 {
- let liquidity_penalty_msat = {
- // Upper bound the liquidity penalty to ensure some channel is selected.
- let multiplier_msat = liquidity_penalty_multiplier_msat;
- let max_penalty_msat = multiplier_msat.saturating_mul(NEGATIVE_LOG10_UPPER_BOUND);
- (negative_log10_times_2048.saturating_mul(multiplier_msat) / 2048).min(max_penalty_msat)
- };
+ negative_log10_times_2048 =
+ negative_log10_times_2048.min(NEGATIVE_LOG10_UPPER_BOUND * 2048);
+
+ // Upper bound the liquidity penalty to ensure some channel is selected.
+ let liquidity_penalty_msat = negative_log10_times_2048
+ .saturating_mul(liquidity_penalty_multiplier_msat) / 2048;
let amount_penalty_msat = negative_log10_times_2048
.saturating_mul(liquidity_penalty_amount_multiplier_msat)
.saturating_mul(amount_msat) / 2048 / AMOUNT_PENALTY_DIVISOR;
}
/// Returns the lower bound of the channel liquidity balance in this direction.
+ #[inline(always)]
fn min_liquidity_msat(&self) -> u64 {
self.decayed_offset_msat(*self.min_liquidity_offset_msat)
}
/// Returns the upper bound of the channel liquidity balance in this direction.
+ #[inline(always)]
fn max_liquidity_msat(&self) -> u64 {
- self.available_capacity()
+ self.capacity_msat
.saturating_sub(self.decayed_offset_msat(*self.max_liquidity_offset_msat))
}
- /// Returns the capacity minus the in-flight HTLCs in this direction.
- fn available_capacity(&self) -> u64 {
- self.capacity_msat.saturating_sub(self.inflight_htlc_msat)
- }
-
fn decayed_offset_msat(&self, offset_msat: u64) -> u64 {
- self.now.duration_since(*self.last_updated).as_secs()
- .checked_div(self.decay_params.liquidity_offset_half_life.as_secs())
- .and_then(|decays| offset_msat.checked_shr(decays as u32))
- .unwrap_or(0)
+ let half_life = self.decay_params.liquidity_offset_half_life.as_secs();
+ if half_life != 0 {
+ // Decay the offset by the appropriate number of half lives. If half of the next half
+ // life has passed, approximate an additional three-quarter life to help smooth out the
+ // decay.
+ let elapsed_time = self.now.duration_since(*self.last_updated).as_secs();
+ let half_decays = elapsed_time / (half_life / 2);
+ let decays = half_decays / 2;
+ let decayed_offset_msat = offset_msat.checked_shr(decays as u32).unwrap_or(0);
+ if half_decays % 2 == 0 {
+ decayed_offset_msat
+ } else {
+ // 11_585 / 16_384 ~= core::f64::consts::FRAC_1_SQRT_2
+ // 16_384 == 2^14
+ (decayed_offset_msat as u128 * 11_585 / 16_384) as u64
+ }
+ } else {
+ 0
+ }
}
}
log_trace!(logger, "Max liquidity of {} is {} (already less than or equal to {})",
chan_descr, existing_max_msat, amount_msat);
}
- self.update_history_buckets();
+ self.update_history_buckets(0);
}
/// Adjusts the channel liquidity balance bounds when failing to route `amount_msat` downstream.
log_trace!(logger, "Min liquidity of {} is {} (already greater than or equal to {})",
chan_descr, existing_min_msat, amount_msat);
}
- self.update_history_buckets();
+ self.update_history_buckets(0);
}
/// Adjusts the channel liquidity balance bounds when successfully routing `amount_msat`.
let max_liquidity_msat = self.max_liquidity_msat().checked_sub(amount_msat).unwrap_or(0);
log_debug!(logger, "Subtracting {} from max liquidity of {} (setting it to {})", amount_msat, chan_descr, max_liquidity_msat);
self.set_max_liquidity_msat(max_liquidity_msat);
- self.update_history_buckets();
+ self.update_history_buckets(amount_msat);
}
- fn update_history_buckets(&mut self) {
+ /// Updates the history buckets for this channel. Because the history buckets track what we now
+ /// know about the channel's state *prior to our payment* (i.e. what we assume is "steady
+ /// state"), we allow the caller to set an offset applied to our liquidity bounds which
+ /// represents the amount of the successful payment we just made.
+ fn update_history_buckets(&mut self, bucket_offset_msat: u64) {
let half_lives = self.now.duration_since(*self.last_updated).as_secs()
.checked_div(self.decay_params.historical_no_updates_half_life.as_secs())
.map(|v| v.try_into().unwrap_or(u32::max_value())).unwrap_or(u32::max_value());
- self.min_liquidity_offset_history.time_decay_data(half_lives);
- self.max_liquidity_offset_history.time_decay_data(half_lives);
+ self.liquidity_history.min_liquidity_offset_history.time_decay_data(half_lives);
+ self.liquidity_history.max_liquidity_offset_history.time_decay_data(half_lives);
let min_liquidity_offset_msat = self.decayed_offset_msat(*self.min_liquidity_offset_msat);
- self.min_liquidity_offset_history.track_datapoint(
- min_liquidity_offset_msat, self.capacity_msat
+ self.liquidity_history.min_liquidity_offset_history.track_datapoint(
+ min_liquidity_offset_msat + bucket_offset_msat, self.capacity_msat
);
let max_liquidity_offset_msat = self.decayed_offset_msat(*self.max_liquidity_offset_msat);
- self.max_liquidity_offset_history.track_datapoint(
- max_liquidity_offset_msat, self.capacity_msat
+ self.liquidity_history.max_liquidity_offset_history.track_datapoint(
+ max_liquidity_offset_msat.saturating_sub(bucket_offset_msat), self.capacity_msat
);
}
}
}
-impl<G: Deref<Target = NetworkGraph<L>>, L: Deref, T: Time> Score for ProbabilisticScorerUsingTime<G, L, T> where L::Target: Logger {
+impl<G: Deref<Target = NetworkGraph<L>>, L: Deref, T: Time> ScoreLookUp for ProbabilisticScorerUsingTime<G, L, T> where L::Target: Logger {
type ScoreParams = ProbabilisticScoringFeeParameters;
fn channel_penalty_msat(
&self, short_channel_id: u64, source: &NodeId, target: &NodeId, usage: ChannelUsage, score_params: &ProbabilisticScoringFeeParameters
_ => {},
}
- let amount_msat = usage.amount_msat;
+ let amount_msat = usage.amount_msat.saturating_add(usage.inflight_htlc_msat);
let capacity_msat = usage.effective_capacity.as_msat();
- let inflight_htlc_msat = usage.inflight_htlc_msat;
self.channel_liquidities
.get(&short_channel_id)
.unwrap_or(&ChannelLiquidity::new())
- .as_directed(source, target, inflight_htlc_msat, capacity_msat, self.decay_params)
+ .as_directed(source, target, capacity_msat, self.decay_params)
.penalty_msat(amount_msat, score_params)
.saturating_add(anti_probing_penalty_msat)
.saturating_add(base_penalty_msat)
}
+}
+impl<G: Deref<Target = NetworkGraph<L>>, L: Deref, T: Time> ScoreUpdate for ProbabilisticScorerUsingTime<G, L, T> where L::Target: Logger {
fn payment_path_failed(&mut self, path: &Path, short_channel_id: u64) {
let amount_msat = path.final_value_msat();
log_trace!(self.logger, "Scoring path through to SCID {} as having failed at {} msat", short_channel_id, amount_msat);
self.channel_liquidities
.entry(hop.short_channel_id)
.or_insert_with(ChannelLiquidity::new)
- .as_directed_mut(source, &target, 0, capacity_msat, self.decay_params)
+ .as_directed_mut(source, &target, capacity_msat, self.decay_params)
.failed_at_channel(amount_msat, format_args!("SCID {}, towards {:?}", hop.short_channel_id, target), &self.logger);
} else {
self.channel_liquidities
.entry(hop.short_channel_id)
.or_insert_with(ChannelLiquidity::new)
- .as_directed_mut(source, &target, 0, capacity_msat, self.decay_params)
+ .as_directed_mut(source, &target, capacity_msat, self.decay_params)
.failed_downstream(amount_msat, format_args!("SCID {}, towards {:?}", hop.short_channel_id, target), &self.logger);
}
} else {
self.channel_liquidities
.entry(hop.short_channel_id)
.or_insert_with(ChannelLiquidity::new)
- .as_directed_mut(source, &target, 0, capacity_msat, self.decay_params)
+ .as_directed_mut(source, &target, capacity_msat, self.decay_params)
.successful(amount_msat, format_args!("SCID {}, towards {:?}", hop.short_channel_id, target), &self.logger);
} else {
log_debug!(self.logger, "Not able to learn for channel with SCID {} as we do not have graph info for it (likely a route-hint last-hop).",
}
}
+mod bucketed_history {
+ use super::*;
+
+ // Because liquidity is often skewed heavily in one direction, we store historical state
+ // distribution in buckets of different size. For backwards compatibility, buckets of size 1/8th
+ // must fit evenly into the buckets here.
+ //
+ // The smallest bucket is 2^-14th of the channel, for each of our 32 buckets here we define the
+ // width of the bucket in 2^14'ths of the channel. This increases exponentially until we reach
+ // a full 16th of the channel's capacity, which is reapeated a few times for backwards
+ // compatibility. The four middle buckets represent full octiles of the channel's capacity.
+ //
+ // For a 1 BTC channel, this let's us differentiate between failures in the bottom 6k sats, or
+ // between the 12,000th sat and 24,000th sat, while only needing to store and operate on 32
+ // buckets in total.
+
+ const BUCKET_START_POS: [u16; 33] = [
+ 0, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 3072, 4096, 6144, 8192, 10240, 12288,
+ 13312, 14336, 15360, 15872, 16128, 16256, 16320, 16352, 16368, 16376, 16380, 16382, 16383, 16384,
+ ];
+
+ const LEGACY_TO_BUCKET_RANGE: [(u8, u8); 8] = [
+ (0, 12), (12, 14), (14, 15), (15, 16), (16, 17), (17, 18), (18, 20), (20, 32)
+ ];
+
+ const POSITION_TICKS: u16 = 1 << 14;
+
+ fn pos_to_bucket(pos: u16) -> usize {
+ for bucket in 0..32 {
+ if pos < BUCKET_START_POS[bucket + 1] {
+ return bucket;
+ }
+ }
+ debug_assert!(false);
+ return 32;
+ }
+
+ #[cfg(test)]
+ #[test]
+ fn check_bucket_maps() {
+ const BUCKET_WIDTH_IN_16384S: [u16; 32] = [
+ 1, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 1024, 1024, 2048, 2048,
+ 2048, 2048, 1024, 1024, 1024, 512, 256, 128, 64, 32, 16, 8, 4, 2, 1, 1];
+
+ let mut min_size_iter = 0;
+ let mut legacy_bucket_iter = 0;
+ for (bucket, width) in BUCKET_WIDTH_IN_16384S.iter().enumerate() {
+ assert_eq!(BUCKET_START_POS[bucket], min_size_iter);
+ for i in 0..*width {
+ assert_eq!(pos_to_bucket(min_size_iter + i) as usize, bucket);
+ }
+ min_size_iter += *width;
+ if min_size_iter % (POSITION_TICKS / 8) == 0 {
+ assert_eq!(LEGACY_TO_BUCKET_RANGE[legacy_bucket_iter].1 as usize, bucket + 1);
+ if legacy_bucket_iter + 1 < 8 {
+ assert_eq!(LEGACY_TO_BUCKET_RANGE[legacy_bucket_iter + 1].0 as usize, bucket + 1);
+ }
+ legacy_bucket_iter += 1;
+ }
+ }
+ assert_eq!(BUCKET_START_POS[32], POSITION_TICKS);
+ assert_eq!(min_size_iter, POSITION_TICKS);
+ }
+
+ #[inline]
+ fn amount_to_pos(amount_msat: u64, capacity_msat: u64) -> u16 {
+ let pos = if amount_msat < u64::max_value() / (POSITION_TICKS as u64) {
+ (amount_msat * (POSITION_TICKS as u64) / capacity_msat.saturating_add(1))
+ .try_into().unwrap_or(POSITION_TICKS)
+ } else {
+ // Only use 128-bit arithmetic when multiplication will overflow to avoid 128-bit
+ // division. This branch should only be hit in fuzz testing since the amount would
+ // need to be over 2.88 million BTC in practice.
+ ((amount_msat as u128) * (POSITION_TICKS as u128)
+ / (capacity_msat as u128).saturating_add(1))
+ .try_into().unwrap_or(POSITION_TICKS)
+ };
+ // If we are running in a client that doesn't validate gossip, its possible for a channel's
+ // capacity to change due to a `channel_update` message which, if received while a payment
+ // is in-flight, could cause this to fail. Thus, we only assert in test.
+ #[cfg(test)]
+ debug_assert!(pos < POSITION_TICKS);
+ pos
+ }
+
+ /// Prior to LDK 0.0.117 we used eight buckets which were split evenly across the either
+ /// octiles. This was changed to use 32 buckets for accuracy reasons in 0.0.117, however we
+ /// support reading the legacy values here for backwards compatibility.
+ pub(super) struct LegacyHistoricalBucketRangeTracker {
+ buckets: [u16; 8],
+ }
+
+ impl LegacyHistoricalBucketRangeTracker {
+ pub(crate) fn into_current(&self) -> HistoricalBucketRangeTracker {
+ let mut buckets = [0; 32];
+ for (idx, legacy_bucket) in self.buckets.iter().enumerate() {
+ let mut new_val = *legacy_bucket;
+ let (start, end) = LEGACY_TO_BUCKET_RANGE[idx];
+ new_val /= (end - start) as u16;
+ for i in start..end {
+ buckets[i as usize] = new_val;
+ }
+ }
+ HistoricalBucketRangeTracker { buckets }
+ }
+ }
+
+ /// Tracks the historical state of a distribution as a weighted average of how much time was spent
+ /// in each of 32 buckets.
+ #[derive(Clone, Copy)]
+ pub(super) struct HistoricalBucketRangeTracker {
+ buckets: [u16; 32],
+ }
+
+ /// Buckets are stored in fixed point numbers with a 5 bit fractional part. Thus, the value
+ /// "one" is 32, or this constant.
+ pub const BUCKET_FIXED_POINT_ONE: u16 = 32;
+
+ impl HistoricalBucketRangeTracker {
+ pub(super) fn new() -> Self { Self { buckets: [0; 32] } }
+ pub(super) fn track_datapoint(&mut self, liquidity_offset_msat: u64, capacity_msat: u64) {
+ // We have 32 leaky buckets for min and max liquidity. Each bucket tracks the amount of time
+ // we spend in each bucket as a 16-bit fixed-point number with a 5 bit fractional part.
+ //
+ // Each time we update our liquidity estimate, we add 32 (1.0 in our fixed-point system) to
+ // the buckets for the current min and max liquidity offset positions.
+ //
+ // We then decay each bucket by multiplying by 2047/2048 (avoiding dividing by a
+ // non-power-of-two). This ensures we can't actually overflow the u16 - when we get to
+ // 63,457 adding 32 and decaying by 2047/2048 leaves us back at 63,457.
+ //
+ // In total, this allows us to track data for the last 8,000 or so payments across a given
+ // channel.
+ //
+ // These constants are a balance - we try to fit in 2 bytes per bucket to reduce overhead,
+ // and need to balance having more bits in the decimal part (to ensure decay isn't too
+ // non-linear) with having too few bits in the mantissa, causing us to not store very many
+ // datapoints.
+ //
+ // The constants were picked experimentally, selecting a decay amount that restricts us
+ // from overflowing buckets without having to cap them manually.
+
+ let pos: u16 = amount_to_pos(liquidity_offset_msat, capacity_msat);
+ if pos < POSITION_TICKS {
+ for e in self.buckets.iter_mut() {
+ *e = ((*e as u32) * 2047 / 2048) as u16;
+ }
+ let bucket = pos_to_bucket(pos);
+ self.buckets[bucket] = self.buckets[bucket].saturating_add(BUCKET_FIXED_POINT_ONE);
+ }
+ }
+ /// Decay all buckets by the given number of half-lives. Used to more aggressively remove old
+ /// datapoints as we receive newer information.
+ #[inline]
+ pub(super) fn time_decay_data(&mut self, half_lives: u32) {
+ for e in self.buckets.iter_mut() {
+ *e = e.checked_shr(half_lives).unwrap_or(0);
+ }
+ }
+ }
+
+ impl_writeable_tlv_based!(HistoricalBucketRangeTracker, { (0, buckets, required) });
+ impl_writeable_tlv_based!(LegacyHistoricalBucketRangeTracker, { (0, buckets, required) });
+
+ /// A set of buckets representing the history of where we've seen the minimum- and maximum-
+ /// liquidity bounds for a given channel.
+ pub(super) struct HistoricalMinMaxBuckets<D: Deref<Target = HistoricalBucketRangeTracker>> {
+ /// Buckets tracking where and how often we've seen the minimum liquidity bound for a
+ /// channel.
+ pub(super) min_liquidity_offset_history: D,
+ /// Buckets tracking where and how often we've seen the maximum liquidity bound for a
+ /// channel.
+ pub(super) max_liquidity_offset_history: D,
+ }
+
+ impl<D: Deref<Target = HistoricalBucketRangeTracker>> HistoricalMinMaxBuckets<D> {
+ pub(super) fn get_decayed_buckets<T: Time>(&self, now: T, last_updated: T, half_life: Duration)
+ -> Option<([u16; 32], [u16; 32])> {
+ let (_, required_decays) = self.get_total_valid_points(now, last_updated, half_life)?;
+
+ let mut min_buckets = *self.min_liquidity_offset_history;
+ min_buckets.time_decay_data(required_decays);
+ let mut max_buckets = *self.max_liquidity_offset_history;
+ max_buckets.time_decay_data(required_decays);
+ Some((min_buckets.buckets, max_buckets.buckets))
+ }
+ #[inline]
+ pub(super) fn get_total_valid_points<T: Time>(&self, now: T, last_updated: T, half_life: Duration)
+ -> Option<(u64, u32)> {
+ let required_decays = now.duration_since(last_updated).as_secs()
+ .checked_div(half_life.as_secs())
+ .map_or(u32::max_value(), |decays| cmp::min(decays, u32::max_value() as u64) as u32);
+
+ let mut total_valid_points_tracked = 0;
+ for (min_idx, min_bucket) in self.min_liquidity_offset_history.buckets.iter().enumerate() {
+ for max_bucket in self.max_liquidity_offset_history.buckets.iter().take(32 - min_idx) {
+ total_valid_points_tracked += (*min_bucket as u64) * (*max_bucket as u64);
+ }
+ }
+
+ // If the total valid points is smaller than 1.0 (i.e. 32 in our fixed-point scheme),
+ // treat it as if we were fully decayed.
+ const FULLY_DECAYED: u16 = BUCKET_FIXED_POINT_ONE * BUCKET_FIXED_POINT_ONE;
+ if total_valid_points_tracked.checked_shr(required_decays).unwrap_or(0) < FULLY_DECAYED.into() {
+ return None;
+ }
+
+ Some((total_valid_points_tracked, required_decays))
+ }
+
+ #[inline]
+ pub(super) fn calculate_success_probability_times_billion<T: Time>(
+ &self, now: T, last_updated: T, half_life: Duration,
+ params: &ProbabilisticScoringFeeParameters, amount_msat: u64, capacity_msat: u64
+ ) -> Option<u64> {
+ // If historical penalties are enabled, we try to calculate a probability of success
+ // given our historical distribution of min- and max-liquidity bounds in a channel.
+ // To do so, we walk the set of historical liquidity bucket (min, max) combinations
+ // (where min_idx < max_idx, as having a minimum above our maximum is an invalid
+ // state). For each pair, we calculate the probability as if the bucket's corresponding
+ // min- and max- liquidity bounds were our current liquidity bounds and then multiply
+ // that probability by the weight of the selected buckets.
+ let payment_pos = amount_to_pos(amount_msat, capacity_msat);
+ if payment_pos >= POSITION_TICKS { return None; }
+
+ // Check if all our buckets are zero, once decayed and treat it as if we had no data. We
+ // don't actually use the decayed buckets, though, as that would lose precision.
+ let (total_valid_points_tracked, _)
+ = self.get_total_valid_points(now, last_updated, half_life)?;
+
+ let mut cumulative_success_prob_times_billion = 0;
+ // Special-case the 0th min bucket - it generally means we failed a payment, so only
+ // consider the highest (i.e. largest-offset-from-max-capacity) max bucket for all
+ // points against the 0th min bucket. This avoids the case where we fail to route
+ // increasingly lower values over a channel, but treat each failure as a separate
+ // datapoint, many of which may have relatively high maximum-available-liquidity
+ // values, which will result in us thinking we have some nontrivial probability of
+ // routing up to that amount.
+ if self.min_liquidity_offset_history.buckets[0] != 0 {
+ let mut highest_max_bucket_with_points = 0; // The highest max-bucket with any data
+ let mut total_max_points = 0; // Total points in max-buckets to consider
+ for (max_idx, max_bucket) in self.max_liquidity_offset_history.buckets.iter().enumerate() {
+ if *max_bucket >= BUCKET_FIXED_POINT_ONE {
+ highest_max_bucket_with_points = cmp::max(highest_max_bucket_with_points, max_idx);
+ }
+ total_max_points += *max_bucket as u64;
+ }
+ let max_bucket_end_pos = BUCKET_START_POS[32 - highest_max_bucket_with_points] - 1;
+ if payment_pos < max_bucket_end_pos {
+ let (numerator, denominator) = success_probability(payment_pos as u64, 0,
+ max_bucket_end_pos as u64, POSITION_TICKS as u64 - 1, params, true);
+ let bucket_prob_times_billion =
+ (self.min_liquidity_offset_history.buckets[0] as u64) * total_max_points
+ * 1024 * 1024 * 1024 / total_valid_points_tracked;
+ cumulative_success_prob_times_billion += bucket_prob_times_billion *
+ numerator / denominator;
+ }
+ }
+
+ for (min_idx, min_bucket) in self.min_liquidity_offset_history.buckets.iter().enumerate().skip(1) {
+ let min_bucket_start_pos = BUCKET_START_POS[min_idx];
+ for (max_idx, max_bucket) in self.max_liquidity_offset_history.buckets.iter().enumerate().take(32 - min_idx) {
+ let max_bucket_end_pos = BUCKET_START_POS[32 - max_idx] - 1;
+ // Note that this multiply can only barely not overflow - two 16 bit ints plus
+ // 30 bits is 62 bits.
+ let bucket_prob_times_billion = (*min_bucket as u64) * (*max_bucket as u64)
+ * 1024 * 1024 * 1024 / total_valid_points_tracked;
+ if payment_pos >= max_bucket_end_pos {
+ // Success probability 0, the payment amount may be above the max liquidity
+ break;
+ } else if payment_pos < min_bucket_start_pos {
+ cumulative_success_prob_times_billion += bucket_prob_times_billion;
+ } else {
+ let (numerator, denominator) = success_probability(payment_pos as u64,
+ min_bucket_start_pos as u64, max_bucket_end_pos as u64,
+ POSITION_TICKS as u64 - 1, params, true);
+ cumulative_success_prob_times_billion += bucket_prob_times_billion *
+ numerator / denominator;
+ }
+ }
+ }
+
+ Some(cumulative_success_prob_times_billion)
+ }
+ }
+}
+use bucketed_history::{LegacyHistoricalBucketRangeTracker, HistoricalBucketRangeTracker, HistoricalMinMaxBuckets};
+
impl<G: Deref<Target = NetworkGraph<L>>, L: Deref, T: Time> Writeable for ProbabilisticScorerUsingTime<G, L, T> where L::Target: Logger {
#[inline]
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
let duration_since_epoch = T::duration_since_epoch() - self.last_updated.elapsed();
write_tlv_fields!(w, {
(0, self.min_liquidity_offset_msat, required),
- (1, Some(self.min_liquidity_offset_history), option),
+ // 1 was the min_liquidity_offset_history in octile form
(2, self.max_liquidity_offset_msat, required),
- (3, Some(self.max_liquidity_offset_history), option),
+ // 3 was the max_liquidity_offset_history in octile form
(4, duration_since_epoch, required),
+ (5, Some(self.min_liquidity_offset_history), option),
+ (7, Some(self.max_liquidity_offset_history), option),
});
Ok(())
}
fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
let mut min_liquidity_offset_msat = 0;
let mut max_liquidity_offset_msat = 0;
- let mut min_liquidity_offset_history = Some(HistoricalBucketRangeTracker::new());
- let mut max_liquidity_offset_history = Some(HistoricalBucketRangeTracker::new());
+ let mut legacy_min_liq_offset_history: Option<LegacyHistoricalBucketRangeTracker> = None;
+ let mut legacy_max_liq_offset_history: Option<LegacyHistoricalBucketRangeTracker> = None;
+ let mut min_liquidity_offset_history: Option<HistoricalBucketRangeTracker> = None;
+ let mut max_liquidity_offset_history: Option<HistoricalBucketRangeTracker> = None;
let mut duration_since_epoch = Duration::from_secs(0);
read_tlv_fields!(r, {
(0, min_liquidity_offset_msat, required),
- (1, min_liquidity_offset_history, option),
+ (1, legacy_min_liq_offset_history, option),
(2, max_liquidity_offset_msat, required),
- (3, max_liquidity_offset_history, option),
+ (3, legacy_max_liq_offset_history, option),
(4, duration_since_epoch, required),
+ (5, min_liquidity_offset_history, option),
+ (7, max_liquidity_offset_history, option),
});
// On rust prior to 1.60 `Instant::duration_since` will panic if time goes backwards.
// We write `last_updated` as wallclock time even though its ultimately an `Instant` (which
let last_updated = if wall_clock_now > duration_since_epoch {
now - (wall_clock_now - duration_since_epoch)
} else { now };
+ if min_liquidity_offset_history.is_none() {
+ if let Some(legacy_buckets) = legacy_min_liq_offset_history {
+ min_liquidity_offset_history = Some(legacy_buckets.into_current());
+ } else {
+ min_liquidity_offset_history = Some(HistoricalBucketRangeTracker::new());
+ }
+ }
+ if max_liquidity_offset_history.is_none() {
+ if let Some(legacy_buckets) = legacy_max_liq_offset_history {
+ max_liquidity_offset_history = Some(legacy_buckets.into_current());
+ } else {
+ max_liquidity_offset_history = Some(HistoricalBucketRangeTracker::new());
+ }
+ }
Ok(Self {
min_liquidity_offset_msat,
max_liquidity_offset_msat,
use crate::ln::msgs::{ChannelAnnouncement, ChannelUpdate, UnsignedChannelAnnouncement, UnsignedChannelUpdate};
use crate::routing::gossip::{EffectiveCapacity, NetworkGraph, NodeId};
use crate::routing::router::{BlindedTail, Path, RouteHop};
- use crate::routing::scoring::{ChannelUsage, Score};
+ use crate::routing::scoring::{ChannelUsage, ScoreLookUp, ScoreUpdate};
use crate::util::ser::{ReadableArgs, Writeable};
use crate::util::test_utils::{self, TestLogger};
let chain_source: Option<&crate::util::test_utils::TestChainSource> = None;
network_graph.update_channel_from_announcement(
&signed_announcement, &chain_source).unwrap();
- update_channel(network_graph, short_channel_id, node_1_key, 0, 1_000);
- update_channel(network_graph, short_channel_id, node_2_key, 1, 0);
+ update_channel(network_graph, short_channel_id, node_1_key, 0, 1_000, 100);
+ update_channel(network_graph, short_channel_id, node_2_key, 1, 0, 100);
}
fn update_channel(
network_graph: &mut NetworkGraph<&TestLogger>, short_channel_id: u64, node_key: SecretKey,
- flags: u8, htlc_maximum_msat: u64
+ flags: u8, htlc_maximum_msat: u64, timestamp: u32,
) {
let genesis_hash = genesis_block(Network::Testnet).header.block_hash();
let secp_ctx = Secp256k1::new();
let unsigned_update = UnsignedChannelUpdate {
chain_hash: genesis_hash,
short_channel_id,
- timestamp: 100,
+ timestamp,
flags,
cltv_expiry_delta: 18,
htlc_minimum_msat: 0,
channel_features: channelmanager::provided_channel_features(&config),
fee_msat,
cltv_expiry_delta: 18,
+ maybe_announced_channel: true,
}
}
// Update minimum liquidity.
let liquidity = scorer.channel_liquidities.get(&42).unwrap()
- .as_directed(&source, &target, 0, 1_000, decay_params);
+ .as_directed(&source, &target, 1_000, decay_params);
assert_eq!(liquidity.min_liquidity_msat(), 100);
assert_eq!(liquidity.max_liquidity_msat(), 300);
let liquidity = scorer.channel_liquidities.get(&42).unwrap()
- .as_directed(&target, &source, 0, 1_000, decay_params);
+ .as_directed(&target, &source, 1_000, decay_params);
assert_eq!(liquidity.min_liquidity_msat(), 700);
assert_eq!(liquidity.max_liquidity_msat(), 900);
scorer.channel_liquidities.get_mut(&42).unwrap()
- .as_directed_mut(&source, &target, 0, 1_000, decay_params)
+ .as_directed_mut(&source, &target, 1_000, decay_params)
.set_min_liquidity_msat(200);
let liquidity = scorer.channel_liquidities.get(&42).unwrap()
- .as_directed(&source, &target, 0, 1_000, decay_params);
+ .as_directed(&source, &target, 1_000, decay_params);
assert_eq!(liquidity.min_liquidity_msat(), 200);
assert_eq!(liquidity.max_liquidity_msat(), 300);
let liquidity = scorer.channel_liquidities.get(&42).unwrap()
- .as_directed(&target, &source, 0, 1_000, decay_params);
+ .as_directed(&target, &source, 1_000, decay_params);
assert_eq!(liquidity.min_liquidity_msat(), 700);
assert_eq!(liquidity.max_liquidity_msat(), 800);
// Update maximum liquidity.
let liquidity = scorer.channel_liquidities.get(&43).unwrap()
- .as_directed(&target, &recipient, 0, 1_000, decay_params);
+ .as_directed(&target, &recipient, 1_000, decay_params);
assert_eq!(liquidity.min_liquidity_msat(), 700);
assert_eq!(liquidity.max_liquidity_msat(), 900);
let liquidity = scorer.channel_liquidities.get(&43).unwrap()
- .as_directed(&recipient, &target, 0, 1_000, decay_params);
+ .as_directed(&recipient, &target, 1_000, decay_params);
assert_eq!(liquidity.min_liquidity_msat(), 100);
assert_eq!(liquidity.max_liquidity_msat(), 300);
scorer.channel_liquidities.get_mut(&43).unwrap()
- .as_directed_mut(&target, &recipient, 0, 1_000, decay_params)
+ .as_directed_mut(&target, &recipient, 1_000, decay_params)
.set_max_liquidity_msat(200);
let liquidity = scorer.channel_liquidities.get(&43).unwrap()
- .as_directed(&target, &recipient, 0, 1_000, decay_params);
+ .as_directed(&target, &recipient, 1_000, decay_params);
assert_eq!(liquidity.min_liquidity_msat(), 0);
assert_eq!(liquidity.max_liquidity_msat(), 200);
let liquidity = scorer.channel_liquidities.get(&43).unwrap()
- .as_directed(&recipient, &target, 0, 1_000, decay_params);
+ .as_directed(&recipient, &target, 1_000, decay_params);
assert_eq!(liquidity.min_liquidity_msat(), 800);
assert_eq!(liquidity.max_liquidity_msat(), 1000);
}
// Check initial bounds.
let liquidity = scorer.channel_liquidities.get(&42).unwrap()
- .as_directed(&source, &target, 0, 1_000, decay_params);
+ .as_directed(&source, &target, 1_000, decay_params);
assert_eq!(liquidity.min_liquidity_msat(), 400);
assert_eq!(liquidity.max_liquidity_msat(), 800);
let liquidity = scorer.channel_liquidities.get(&42).unwrap()
- .as_directed(&target, &source, 0, 1_000, decay_params);
+ .as_directed(&target, &source, 1_000, decay_params);
assert_eq!(liquidity.min_liquidity_msat(), 200);
assert_eq!(liquidity.max_liquidity_msat(), 600);
// Reset from source to target.
scorer.channel_liquidities.get_mut(&42).unwrap()
- .as_directed_mut(&source, &target, 0, 1_000, decay_params)
+ .as_directed_mut(&source, &target, 1_000, decay_params)
.set_min_liquidity_msat(900);
let liquidity = scorer.channel_liquidities.get(&42).unwrap()
- .as_directed(&source, &target, 0, 1_000, decay_params);
+ .as_directed(&source, &target, 1_000, decay_params);
assert_eq!(liquidity.min_liquidity_msat(), 900);
assert_eq!(liquidity.max_liquidity_msat(), 1_000);
let liquidity = scorer.channel_liquidities.get(&42).unwrap()
- .as_directed(&target, &source, 0, 1_000, decay_params);
+ .as_directed(&target, &source, 1_000, decay_params);
assert_eq!(liquidity.min_liquidity_msat(), 0);
assert_eq!(liquidity.max_liquidity_msat(), 100);
// Reset from target to source.
scorer.channel_liquidities.get_mut(&42).unwrap()
- .as_directed_mut(&target, &source, 0, 1_000, decay_params)
+ .as_directed_mut(&target, &source, 1_000, decay_params)
.set_min_liquidity_msat(400);
let liquidity = scorer.channel_liquidities.get(&42).unwrap()
- .as_directed(&source, &target, 0, 1_000, decay_params);
+ .as_directed(&source, &target, 1_000, decay_params);
assert_eq!(liquidity.min_liquidity_msat(), 0);
assert_eq!(liquidity.max_liquidity_msat(), 600);
let liquidity = scorer.channel_liquidities.get(&42).unwrap()
- .as_directed(&target, &source, 0, 1_000, decay_params);
+ .as_directed(&target, &source, 1_000, decay_params);
assert_eq!(liquidity.min_liquidity_msat(), 400);
assert_eq!(liquidity.max_liquidity_msat(), 1_000);
}
// Check initial bounds.
let liquidity = scorer.channel_liquidities.get(&42).unwrap()
- .as_directed(&source, &target, 0, 1_000, decay_params);
+ .as_directed(&source, &target, 1_000, decay_params);
assert_eq!(liquidity.min_liquidity_msat(), 400);
assert_eq!(liquidity.max_liquidity_msat(), 800);
let liquidity = scorer.channel_liquidities.get(&42).unwrap()
- .as_directed(&target, &source, 0, 1_000, decay_params);
+ .as_directed(&target, &source, 1_000, decay_params);
assert_eq!(liquidity.min_liquidity_msat(), 200);
assert_eq!(liquidity.max_liquidity_msat(), 600);
// Reset from source to target.
scorer.channel_liquidities.get_mut(&42).unwrap()
- .as_directed_mut(&source, &target, 0, 1_000, decay_params)
+ .as_directed_mut(&source, &target, 1_000, decay_params)
.set_max_liquidity_msat(300);
let liquidity = scorer.channel_liquidities.get(&42).unwrap()
- .as_directed(&source, &target, 0, 1_000, decay_params);
+ .as_directed(&source, &target, 1_000, decay_params);
assert_eq!(liquidity.min_liquidity_msat(), 0);
assert_eq!(liquidity.max_liquidity_msat(), 300);
let liquidity = scorer.channel_liquidities.get(&42).unwrap()
- .as_directed(&target, &source, 0, 1_000, decay_params);
+ .as_directed(&target, &source, 1_000, decay_params);
assert_eq!(liquidity.min_liquidity_msat(), 700);
assert_eq!(liquidity.max_liquidity_msat(), 1_000);
// Reset from target to source.
scorer.channel_liquidities.get_mut(&42).unwrap()
- .as_directed_mut(&target, &source, 0, 1_000, decay_params)
+ .as_directed_mut(&target, &source, 1_000, decay_params)
.set_max_liquidity_msat(600);
let liquidity = scorer.channel_liquidities.get(&42).unwrap()
- .as_directed(&source, &target, 0, 1_000, decay_params);
+ .as_directed(&source, &target, 1_000, decay_params);
assert_eq!(liquidity.min_liquidity_msat(), 400);
assert_eq!(liquidity.max_liquidity_msat(), 1_000);
let liquidity = scorer.channel_liquidities.get(&42).unwrap()
- .as_directed(&target, &source, 0, 1_000, decay_params);
+ .as_directed(&target, &source, 1_000, decay_params);
assert_eq!(liquidity.min_liquidity_msat(), 0);
assert_eq!(liquidity.max_liquidity_msat(), 600);
}
scorer.payment_path_failed(&payment_path_for_amount(768), 42);
scorer.payment_path_failed(&payment_path_for_amount(128), 43);
+ // Initial penalties
let usage = ChannelUsage { amount_msat: 128, ..usage };
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), 0);
let usage = ChannelUsage { amount_msat: 256, ..usage };
let usage = ChannelUsage { amount_msat: 896, ..usage };
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), u64::max_value());
- SinceEpoch::advance(Duration::from_secs(9));
+ // No decay
+ SinceEpoch::advance(Duration::from_secs(4));
let usage = ChannelUsage { amount_msat: 128, ..usage };
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), 0);
let usage = ChannelUsage { amount_msat: 256, ..usage };
let usage = ChannelUsage { amount_msat: 896, ..usage };
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), u64::max_value());
+ // Half decay (i.e., three-quarter life)
SinceEpoch::advance(Duration::from_secs(1));
+ let usage = ChannelUsage { amount_msat: 128, ..usage };
+ assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), 22);
+ let usage = ChannelUsage { amount_msat: 256, ..usage };
+ assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), 106);
+ let usage = ChannelUsage { amount_msat: 768, ..usage };
+ assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), 921);
+ let usage = ChannelUsage { amount_msat: 896, ..usage };
+ assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), u64::max_value());
+
+ // One decay (i.e., half life)
+ SinceEpoch::advance(Duration::from_secs(5));
let usage = ChannelUsage { amount_msat: 64, ..usage };
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), 0);
let usage = ChannelUsage { amount_msat: 128, ..usage };
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), 300);
SinceEpoch::advance(Duration::from_secs(10));
- assert_eq!(deserialized_scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), 365);
+ assert_eq!(deserialized_scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), 370);
}
#[test]
inflight_htlc_msat: 0,
effective_capacity: EffectiveCapacity::Total { capacity_msat: 950_000_000, htlc_maximum_msat: 1_000 },
};
- assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), 4375);
+ assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), 11497);
let usage = ChannelUsage {
effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_950_000_000, htlc_maximum_msat: 1_000 }, ..usage
};
- assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), 2739);
+ assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), 7408);
let usage = ChannelUsage {
effective_capacity: EffectiveCapacity::Total { capacity_msat: 2_950_000_000, htlc_maximum_msat: 1_000 }, ..usage
};
- assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), 2236);
+ assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), 6151);
let usage = ChannelUsage {
effective_capacity: EffectiveCapacity::Total { capacity_msat: 3_950_000_000, htlc_maximum_msat: 1_000 }, ..usage
};
- assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), 1983);
+ assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), 5427);
let usage = ChannelUsage {
effective_capacity: EffectiveCapacity::Total { capacity_msat: 4_950_000_000, htlc_maximum_msat: 1_000 }, ..usage
};
- assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), 1637);
+ assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), 4955);
let usage = ChannelUsage {
effective_capacity: EffectiveCapacity::Total { capacity_msat: 5_950_000_000, htlc_maximum_msat: 1_000 }, ..usage
};
- assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), 1606);
+ assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), 4736);
let usage = ChannelUsage {
effective_capacity: EffectiveCapacity::Total { capacity_msat: 6_950_000_000, htlc_maximum_msat: 1_000 }, ..usage
};
- assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), 1331);
+ assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), 4484);
let usage = ChannelUsage {
effective_capacity: EffectiveCapacity::Total { capacity_msat: 7_450_000_000, htlc_maximum_msat: 1_000 }, ..usage
};
- assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), 1387);
+ assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), 4484);
let usage = ChannelUsage {
effective_capacity: EffectiveCapacity::Total { capacity_msat: 7_950_000_000, htlc_maximum_msat: 1_000 }, ..usage
};
- assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), 1379);
+ assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), 4263);
let usage = ChannelUsage {
effective_capacity: EffectiveCapacity::Total { capacity_msat: 8_950_000_000, htlc_maximum_msat: 1_000 }, ..usage
};
- assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), 1363);
+ assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), 4263);
let usage = ChannelUsage {
effective_capacity: EffectiveCapacity::Total { capacity_msat: 9_950_000_000, htlc_maximum_msat: 1_000 }, ..usage
};
- assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), 1355);
+ assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), 4044);
}
#[test]
inflight_htlc_msat: 0,
effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_024, htlc_maximum_msat: 1_024 },
};
+ let usage_1 = ChannelUsage {
+ amount_msat: 1,
+ inflight_htlc_msat: 0,
+ effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_024, htlc_maximum_msat: 1_024 },
+ };
+
// With no historical data the normal liquidity penalty calculation is used.
- assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), 47);
+ assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), 168);
assert_eq!(scorer.historical_estimated_channel_liquidity_probabilities(42, &target),
None);
+ assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, 42, ¶ms),
+ None);
scorer.payment_path_failed(&payment_path_for_amount(1), 42);
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), 2048);
- // The "it failed" increment is 32, where the probability should lie fully in the first
- // octile.
+ assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage_1, ¶ms), 249);
+ // The "it failed" increment is 32, where the probability should lie several buckets into
+ // the first octile.
assert_eq!(scorer.historical_estimated_channel_liquidity_probabilities(42, &target),
- Some(([32, 0, 0, 0, 0, 0, 0, 0], [32, 0, 0, 0, 0, 0, 0, 0])));
+ Some(([32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])));
+ assert!(scorer.historical_estimated_payment_success_probability(42, &target, 1, ¶ms)
+ .unwrap() > 0.35);
+ assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, 500, ¶ms),
+ Some(0.0));
// Even after we tell the scorer we definitely have enough available liquidity, it will
// still remember that there was some failure in the past, and assign a non-0 penalty.
scorer.payment_path_failed(&payment_path_for_amount(1000), 43);
- assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), 198);
- // The first octile should be decayed just slightly and the last octile has a new point.
+ assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), 105);
+ // The first points should be decayed just slightly and the last bucket has a new point.
assert_eq!(scorer.historical_estimated_channel_liquidity_probabilities(42, &target),
- Some(([31, 0, 0, 0, 0, 0, 0, 32], [31, 0, 0, 0, 0, 0, 0, 32])));
+ Some(([31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32])));
+
+ // The exact success probability is a bit complicated and involves integer rounding, so we
+ // simply check bounds here.
+ let five_hundred_prob =
+ scorer.historical_estimated_payment_success_probability(42, &target, 500, ¶ms).unwrap();
+ assert!(five_hundred_prob > 0.59);
+ assert!(five_hundred_prob < 0.60);
+ let one_prob =
+ scorer.historical_estimated_payment_success_probability(42, &target, 1, ¶ms).unwrap();
+ assert!(one_prob < 0.85);
+ assert!(one_prob > 0.84);
// Advance the time forward 16 half-lives (which the docs claim will ensure all data is
// gone), and check that we're back to where we started.
SinceEpoch::advance(Duration::from_secs(10 * 16));
- assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), 47);
+ assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), 168);
// Once fully decayed we still have data, but its all-0s. In the future we may remove the
// data entirely instead.
assert_eq!(scorer.historical_estimated_channel_liquidity_probabilities(42, &target),
- Some(([0; 8], [0; 8])));
+ None);
+ assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, 1, ¶ms), None);
- let usage = ChannelUsage {
+ let mut usage = ChannelUsage {
amount_msat: 100,
inflight_htlc_msat: 1024,
effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_024, htlc_maximum_msat: 1_024 },
};
scorer.payment_path_failed(&payment_path_for_amount(1), 42);
- assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), 409);
+ assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), 2050);
+ usage.inflight_htlc_msat = 0;
+ assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), 866);
let usage = ChannelUsage {
amount_msat: 1,
scorer.payment_path_failed(&path, 43);
let liquidity = scorer.channel_liquidities.get(&42).unwrap()
- .as_directed(&source, &target, 0, 1_000, decay_params);
+ .as_directed(&source, &target, 1_000, decay_params);
assert_eq!(liquidity.min_liquidity_msat(), 256);
assert_eq!(liquidity.max_liquidity_msat(), 768);
}
+
+ #[test]
+ fn realistic_historical_failures() {
+ // The motivation for the unequal sized buckets came largely from attempting to pay 10k
+ // sats over a one bitcoin channel. This tests that case explicitly, ensuring that we score
+ // properly.
+ let logger = TestLogger::new();
+ let mut network_graph = network_graph(&logger);
+ let params = ProbabilisticScoringFeeParameters {
+ historical_liquidity_penalty_multiplier_msat: 1024,
+ historical_liquidity_penalty_amount_multiplier_msat: 1024,
+ ..ProbabilisticScoringFeeParameters::zero_penalty()
+ };
+ let decay_params = ProbabilisticScoringDecayParameters {
+ liquidity_offset_half_life: Duration::from_secs(60 * 60),
+ historical_no_updates_half_life: Duration::from_secs(10),
+ ..ProbabilisticScoringDecayParameters::default()
+ };
+
+ let capacity_msat = 100_000_000_000;
+ update_channel(&mut network_graph, 42, source_privkey(), 0, capacity_msat, 200);
+ update_channel(&mut network_graph, 42, target_privkey(), 1, capacity_msat, 200);
+
+ let mut scorer = ProbabilisticScorer::new(decay_params, &network_graph, &logger);
+ let source = source_node_id();
+ let target = target_node_id();
+
+ let mut amount_msat = 10_000_000;
+ let usage = ChannelUsage {
+ amount_msat,
+ inflight_htlc_msat: 0,
+ effective_capacity: EffectiveCapacity::Total { capacity_msat, htlc_maximum_msat: capacity_msat },
+ };
+ // With no historical data the normal liquidity penalty calculation is used, which results
+ // in a success probability of ~75%.
+ assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms), 1269);
+ assert_eq!(scorer.historical_estimated_channel_liquidity_probabilities(42, &target),
+ None);
+ assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, 42, ¶ms),
+ None);
+
+ // Fail to pay once, and then check the buckets and penalty.
+ scorer.payment_path_failed(&payment_path_for_amount(amount_msat), 42);
+ // The penalty should be the maximum penalty, as the payment we're scoring is now in the
+ // same bucket which is the only maximum datapoint.
+ assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage, ¶ms),
+ 2048 + 2048 * amount_msat / super::AMOUNT_PENALTY_DIVISOR);
+ // The "it failed" increment is 32, which we should apply to the first upper-bound (between
+ // 6k sats and 12k sats).
+ assert_eq!(scorer.historical_estimated_channel_liquidity_probabilities(42, &target),
+ Some(([32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])));
+ // The success probability estimate itself should be zero.
+ assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, amount_msat, ¶ms),
+ Some(0.0));
+
+ // Now test again with the amount in the bottom bucket.
+ amount_msat /= 2;
+ // The new amount is entirely within the only minimum bucket with score, so the probability
+ // we assign is 1/2.
+ assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, amount_msat, ¶ms),
+ Some(0.5));
+
+ // ...but once we see a failure, we consider the payment to be substantially less likely,
+ // even though not a probability of zero as we still look at the second max bucket which
+ // now shows 31.
+ scorer.payment_path_failed(&payment_path_for_amount(amount_msat), 42);
+ assert_eq!(scorer.historical_estimated_channel_liquidity_probabilities(42, &target),
+ Some(([63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [32, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])));
+ assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, amount_msat, ¶ms),
+ Some(0.0));
+ }
}
use bitcoin::hashes::sha256d::Hash as Sha256dHash;
use bitcoin::hash_types::WPubkeyHash;
-use bitcoin::secp256k1::{SecretKey, PublicKey, Scalar};
-use bitcoin::secp256k1::{Secp256k1, ecdsa::Signature, Signing};
+use bitcoin::secp256k1::{KeyPair, PublicKey, Scalar, Secp256k1, SecretKey, Signing};
use bitcoin::secp256k1::ecdh::SharedSecret;
-use bitcoin::secp256k1::ecdsa::RecoverableSignature;
+use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature};
+use bitcoin::secp256k1::schnorr;
use bitcoin::{PackedLockTime, secp256k1, Sequence, Witness};
use crate::util::transaction_utils;
use crate::ln::chan_utils::{HTLCOutputInCommitment, make_funding_redeemscript, ChannelPublicKeys, HolderCommitmentTransaction, ChannelTransactionParameters, CommitmentTransaction, ClosingTransaction};
use crate::ln::msgs::{UnsignedChannelAnnouncement, UnsignedGossipMessage};
use crate::ln::script::ShutdownScript;
+use crate::offers::invoice::UnsignedBolt12Invoice;
+use crate::offers::invoice_request::UnsignedInvoiceRequest;
use crate::prelude::*;
use core::convert::TryInto;
use crate::util::chacha20::ChaCha20;
use crate::util::invoice::construct_invoice_preimage;
+pub(crate) mod type_resolver;
+
/// Used as initial key material, to be expanded into multiple secret keys (but not to be used
/// directly). This is used within LDK to encrypt/decrypt inbound payment data.
///
/// Information about a spendable output to a P2WSH script.
///
/// See [`SpendableOutputDescriptor::DelayedPaymentOutput`] for more details on how to spend this.
-#[derive(Clone, Debug, PartialEq, Eq)]
+#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct DelayedPaymentOutputDescriptor {
/// The outpoint which is spendable.
pub outpoint: OutPoint,
/// Information about a spendable output to our "payment key".
///
/// See [`SpendableOutputDescriptor::StaticPaymentOutput`] for more details on how to spend this.
-#[derive(Clone, Debug, PartialEq, Eq)]
+#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct StaticPaymentOutputDescriptor {
/// The outpoint which is spendable.
pub outpoint: OutPoint,
/// at that `txid`/`index`, and any keys or other information required to sign.
///
/// [`SpendableOutputs`]: crate::events::Event::SpendableOutputs
-#[derive(Clone, Debug, PartialEq, Eq)]
+#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub enum SpendableOutputDescriptor {
/// An output to a script which was provided via [`SignerProvider`] directly, either from
/// [`get_destination_script`] or [`get_shutdown_scriptpubkey`], thus you should already
/// Errors if the [`Recipient`] variant is not supported by the implementation.
fn sign_invoice(&self, hrp_bytes: &[u8], invoice_data: &[u5], recipient: Recipient) -> Result<RecoverableSignature, ()>;
+ /// Signs the [`TaggedHash`] of a BOLT 12 invoice request.
+ ///
+ /// May be called by a function passed to [`UnsignedInvoiceRequest::sign`] where
+ /// `invoice_request` is the callee.
+ ///
+ /// Implementors may check that the `invoice_request` is expected rather than blindly signing
+ /// the tagged hash. An `Ok` result should sign `invoice_request.tagged_hash().as_digest()` with
+ /// the node's signing key or an ephemeral key to preserve privacy, whichever is associated with
+ /// [`UnsignedInvoiceRequest::payer_id`].
+ ///
+ /// [`TaggedHash`]: crate::offers::merkle::TaggedHash
+ fn sign_bolt12_invoice_request(
+ &self, invoice_request: &UnsignedInvoiceRequest
+ ) -> Result<schnorr::Signature, ()>;
+
+ /// Signs the [`TaggedHash`] of a BOLT 12 invoice.
+ ///
+ /// May be called by a function passed to [`UnsignedBolt12Invoice::sign`] where `invoice` is the
+ /// callee.
+ ///
+ /// Implementors may check that the `invoice` is expected rather than blindly signing the tagged
+ /// hash. An `Ok` result should sign `invoice.tagged_hash().as_digest()` with the node's signing
+ /// key or an ephemeral key to preserve privacy, whichever is associated with
+ /// [`UnsignedBolt12Invoice::signing_pubkey`].
+ ///
+ /// [`TaggedHash`]: crate::offers::merkle::TaggedHash
+ fn sign_bolt12_invoice(
+ &self, invoice: &UnsignedBolt12Invoice
+ ) -> Result<schnorr::Signature, ()>;
+
/// Sign a gossip message.
///
/// Note that if this fails, LDK may panic and the message will not be broadcast to the network
&self, htlc_tx: &Transaction, input: usize, htlc_descriptor: &HTLCDescriptor,
secp_ctx: &Secp256k1<secp256k1::All>
) -> Result<Signature, ()> {
- let per_commitment_point = self.get_per_commitment_point(
- htlc_descriptor.per_commitment_number, &secp_ctx
- );
- let witness_script = htlc_descriptor.witness_script(&per_commitment_point, secp_ctx);
+ let witness_script = htlc_descriptor.witness_script(secp_ctx);
let sighash = &sighash::SighashCache::new(&*htlc_tx).segwit_signature_hash(
input, &witness_script, htlc_descriptor.htlc.amount_msat / 1000, EcdsaSighashType::All
).map_err(|_| ())?;
let our_htlc_private_key = chan_utils::derive_private_key(
- &secp_ctx, &per_commitment_point, &self.htlc_base_key
+ &secp_ctx, &htlc_descriptor.per_commitment_point, &self.htlc_base_key
);
Ok(sign_with_aux_rand(&secp_ctx, &hash_to_message!(sighash), &our_htlc_private_key, &self))
}
///
/// May panic if the [`SpendableOutputDescriptor`]s were not generated by channels which used
/// this [`KeysManager`] or one of the [`InMemorySigner`] created by this [`KeysManager`].
- pub fn sign_spendable_outputs_psbt<C: Signing>(&self, descriptors: &[&SpendableOutputDescriptor], psbt: &mut PartiallySignedTransaction, secp_ctx: &Secp256k1<C>) -> Result<(), ()> {
+ pub fn sign_spendable_outputs_psbt<C: Signing>(&self, descriptors: &[&SpendableOutputDescriptor], mut psbt: PartiallySignedTransaction, secp_ctx: &Secp256k1<C>) -> Result<PartiallySignedTransaction, ()> {
let mut keys_cache: Option<(InMemorySigner, [u8; 32])> = None;
for outp in descriptors {
match outp {
}
}
- Ok(())
+ Ok(psbt)
}
/// Creates a [`Transaction`] which spends the given descriptors to the given outputs, plus an
/// this [`KeysManager`] or one of the [`InMemorySigner`] created by this [`KeysManager`].
pub fn spend_spendable_outputs<C: Signing>(&self, descriptors: &[&SpendableOutputDescriptor], outputs: Vec<TxOut>, change_destination_script: Script, feerate_sat_per_1000_weight: u32, locktime: Option<PackedLockTime>, secp_ctx: &Secp256k1<C>) -> Result<Transaction, ()> {
let (mut psbt, expected_max_weight) = SpendableOutputDescriptor::create_spendable_outputs_psbt(descriptors, outputs, change_destination_script, feerate_sat_per_1000_weight, locktime)?;
- self.sign_spendable_outputs_psbt(descriptors, &mut psbt, secp_ctx)?;
+ psbt = self.sign_spendable_outputs_psbt(descriptors, psbt, secp_ctx)?;
let spend_tx = psbt.extract_tx();
Ok(self.secp_ctx.sign_ecdsa_recoverable(&hash_to_message!(&Sha256::hash(&preimage)), secret))
}
+ fn sign_bolt12_invoice_request(
+ &self, invoice_request: &UnsignedInvoiceRequest
+ ) -> Result<schnorr::Signature, ()> {
+ let message = invoice_request.tagged_hash().as_digest();
+ let keys = KeyPair::from_secret_key(&self.secp_ctx, &self.node_secret);
+ let aux_rand = self.get_secure_random_bytes();
+ Ok(self.secp_ctx.sign_schnorr_with_aux_rand(message, &keys, &aux_rand))
+ }
+
+ fn sign_bolt12_invoice(
+ &self, invoice: &UnsignedBolt12Invoice
+ ) -> Result<schnorr::Signature, ()> {
+ let message = invoice.tagged_hash().as_digest();
+ let keys = KeyPair::from_secret_key(&self.secp_ctx, &self.node_secret);
+ let aux_rand = self.get_secure_random_bytes();
+ Ok(self.secp_ctx.sign_schnorr_with_aux_rand(message, &keys, &aux_rand))
+ }
+
fn sign_gossip_message(&self, msg: UnsignedGossipMessage) -> Result<Signature, ()> {
let msg_hash = hash_to_message!(&Sha256dHash::hash(&msg.encode()[..])[..]);
Ok(self.secp_ctx.sign_ecdsa(&msg_hash, &self.node_secret))
Ok(self.inner.secp_ctx.sign_ecdsa_recoverable(&hash_to_message!(&Sha256::hash(&preimage)), secret))
}
+ fn sign_bolt12_invoice_request(
+ &self, invoice_request: &UnsignedInvoiceRequest
+ ) -> Result<schnorr::Signature, ()> {
+ self.inner.sign_bolt12_invoice_request(invoice_request)
+ }
+
+ fn sign_bolt12_invoice(
+ &self, invoice: &UnsignedBolt12Invoice
+ ) -> Result<schnorr::Signature, ()> {
+ self.inner.sign_bolt12_invoice(invoice)
+ }
+
fn sign_gossip_message(&self, msg: UnsignedGossipMessage) -> Result<Signature, ()> {
self.inner.sign_gossip_message(msg)
}
--- /dev/null
+use crate::sign::{ChannelSigner, EcdsaChannelSigner};
+
+pub(crate) enum ChannelSignerType<ECS: EcdsaChannelSigner> {
+ // in practice, this will only ever be an EcdsaChannelSigner (specifically, Writeable)
+ Ecdsa(ECS)
+}
+
+impl<ECS: EcdsaChannelSigner> ChannelSignerType<ECS>{
+ pub(crate) fn as_ref(&self) -> &dyn ChannelSigner {
+ match self {
+ ChannelSignerType::Ecdsa(ecs) => ecs
+ }
+ }
+
+ pub(crate) fn as_mut(&mut self) -> &mut dyn ChannelSigner {
+ match self {
+ ChannelSignerType::Ecdsa(ecs) => ecs
+ }
+ }
+
+ pub(crate) fn as_ecdsa(&self) -> Option<&ECS> {
+ match self {
+ ChannelSignerType::Ecdsa(ecs) => Some(ecs)
+ }
+ }
+
+ pub(crate) fn as_mut_ecdsa(&mut self) -> Option<&mut ECS> {
+ match self {
+ ChannelSignerType::Ecdsa(ecs) => Some(ecs)
+ }
+ }
+}
--- /dev/null
+// This is a modification of base32 encoding to support the zbase32 alphabet.
+// The original piece of software can be found at https://crates.io/crates/base32(v0.4.0)
+// The original portions of this software are Copyright (c) 2015 The base32 Developers
+
+// This file is licensed under either of
+// Apache License, Version 2.0, (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0) or
+// MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT) at your option.
+
+
+use crate::prelude::*;
+
+/// RFC4648 encoding table
+const RFC4648_ALPHABET: &'static [u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
+
+/// Zbase encoding alphabet
+const ZBASE_ALPHABET: &'static [u8] = b"ybndrfg8ejkmcpqxot1uwisza345h769";
+
+/// RFC4648 decoding table
+const RFC4648_INV_ALPHABET: [i8; 43] = [
+ -1, -1, 26, 27, 28, 29, 30, 31, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8,
+ 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
+];
+
+/// Zbase decoding table
+const ZBASE_INV_ALPHABET: [i8; 43] = [
+ -1, 18, -1, 25, 26, 27, 30, 29, 7, 31, -1, -1, -1, -1, -1, -1, -1, 24, 1, 12, 3, 8, 5, 6, 28,
+ 21, 9, 10, -1, 11, 2, 16, 13, 14, 4, 22, 17, 19, -1, 20, 15, 0, 23,
+];
+
+/// Alphabet used for encoding and decoding.
+#[derive(Copy, Clone)]
+pub enum Alphabet {
+ /// RFC4648 encoding.
+ RFC4648 {
+ /// Whether to use padding.
+ padding: bool
+ },
+ /// Zbase32 encoding.
+ ZBase32
+}
+
+impl Alphabet {
+ /// Encode bytes into a base32 string.
+ pub fn encode(&self, data: &[u8]) -> String {
+ // output_length is calculated as follows:
+ // / 5 divides the data length by the number of bits per chunk (5),
+ // * 8 multiplies the result by the number of characters per chunk (8).
+ // + 4 rounds up to the nearest character.
+ let output_length = (data.len() * 8 + 4) / 5;
+ let mut ret = match self {
+ Self::RFC4648 { padding } => {
+ let mut ret = Self::encode_data(data, RFC4648_ALPHABET);
+ if *padding {
+ let len = ret.len();
+ for i in output_length..len {
+ ret[i] = b'=';
+ }
+
+ return String::from_utf8(ret).expect("Invalid UTF-8");
+ }
+ ret
+ },
+ Self::ZBase32 => {
+ Self::encode_data(data, ZBASE_ALPHABET)
+ },
+ };
+ ret.truncate(output_length);
+
+ #[cfg(fuzzing)]
+ assert_eq!(ret.capacity(), (data.len() + 4) / 5 * 8);
+
+ String::from_utf8(ret).expect("Invalid UTF-8")
+ }
+
+ /// Decode a base32 string into a byte vector.
+ pub fn decode(&self, data: &str) -> Result<Vec<u8>, ()> {
+ let data = data.as_bytes();
+ let (data, alphabet) = match self {
+ Self::RFC4648 { padding } => {
+ let mut unpadded_data_length = data.len();
+ if *padding {
+ if data.len() % 8 != 0 { return Err(()); }
+ data.iter().rev().take(6).for_each(|&c| {
+ if c == b'=' {
+ unpadded_data_length -= 1;
+ }
+ });
+ }
+ (&data[..unpadded_data_length], RFC4648_INV_ALPHABET)
+ },
+ Self::ZBase32 => {
+ (data, ZBASE_INV_ALPHABET)
+ }
+ };
+ // If the string has more characters than are required to alphabet_encode the number of bytes
+ // decodable, treat the string as invalid.
+ match data.len() % 8 { 1|3|6 => return Err(()), _ => {} }
+ Ok(Self::decode_data(data, alphabet)?)
+ }
+
+ /// Encode a byte slice into a base32 string.
+ fn encode_data(data: &[u8], alphabet: &'static [u8]) -> Vec<u8> {
+ // cap is calculated as follows:
+ // / 5 divides the data length by the number of bits per chunk (5),
+ // * 8 multiplies the result by the number of characters per chunk (8).
+ // + 4 rounds up to the nearest character.
+ let cap = (data.len() + 4) / 5 * 8;
+ let mut ret = Vec::with_capacity(cap);
+ for chunk in data.chunks(5) {
+ let mut buf = [0u8; 5];
+ for (i, &b) in chunk.iter().enumerate() {
+ buf[i] = b;
+ }
+ ret.push(alphabet[((buf[0] & 0xF8) >> 3) as usize]);
+ ret.push(alphabet[(((buf[0] & 0x07) << 2) | ((buf[1] & 0xC0) >> 6)) as usize]);
+ ret.push(alphabet[((buf[1] & 0x3E) >> 1) as usize]);
+ ret.push(alphabet[(((buf[1] & 0x01) << 4) | ((buf[2] & 0xF0) >> 4)) as usize]);
+ ret.push(alphabet[(((buf[2] & 0x0F) << 1) | (buf[3] >> 7)) as usize]);
+ ret.push(alphabet[((buf[3] & 0x7C) >> 2) as usize]);
+ ret.push(alphabet[(((buf[3] & 0x03) << 3) | ((buf[4] & 0xE0) >> 5)) as usize]);
+ ret.push(alphabet[(buf[4] & 0x1F) as usize]);
+ }
+ #[cfg(fuzzing)]
+ assert_eq!(ret.capacity(), cap);
+
+ ret
+ }
+
+ fn decode_data(data: &[u8], alphabet: [i8; 43]) -> Result<Vec<u8>, ()> {
+ // cap is calculated as follows:
+ // / 8 divides the data length by the number of characters per chunk (8),
+ // * 5 multiplies the result by the number of bits per chunk (5),
+ // + 7 rounds up to the nearest byte.
+ let cap = (data.len() + 7) / 8 * 5;
+ let mut ret = Vec::with_capacity(cap);
+ for chunk in data.chunks(8) {
+ let mut buf = [0u8; 8];
+ for (i, &c) in chunk.iter().enumerate() {
+ match alphabet.get(c.to_ascii_uppercase().wrapping_sub(b'0') as usize) {
+ Some(&-1) | None => return Err(()),
+ Some(&value) => buf[i] = value as u8,
+ };
+ }
+ ret.push((buf[0] << 3) | (buf[1] >> 2));
+ ret.push((buf[1] << 6) | (buf[2] << 1) | (buf[3] >> 4));
+ ret.push((buf[3] << 4) | (buf[4] >> 1));
+ ret.push((buf[4] << 7) | (buf[5] << 2) | (buf[6] >> 3));
+ ret.push((buf[6] << 5) | buf[7]);
+ }
+ let output_length = data.len() * 5 / 8;
+ for c in ret.drain(output_length..) {
+ if c != 0 {
+ // If the original string had any bits set at positions outside of the encoded data,
+ // treat the string as invalid.
+ return Err(());
+ }
+ }
+
+ // Check that our capacity calculation doesn't under-shoot in fuzzing
+ #[cfg(fuzzing)]
+ assert_eq!(ret.capacity(), cap);
+ Ok(ret)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ const ZBASE32_TEST_DATA: &[(&str, &[u8])] = &[
+ ("", &[]),
+ ("yy", &[0x00]),
+ ("oy", &[0x80]),
+ ("tqrey", &[0x8b, 0x88, 0x80]),
+ ("6n9hq", &[0xf0, 0xbf, 0xc7]),
+ ("4t7ye", &[0xd4, 0x7a, 0x04]),
+ ("6im5sdy", &[0xf5, 0x57, 0xbb, 0x0c]),
+ ("ybndrfg8ejkmcpqxot1uwisza345h769", &[0x00, 0x44, 0x32, 0x14, 0xc7, 0x42, 0x54, 0xb6,
+ 0x35, 0xcf, 0x84, 0x65, 0x3a, 0x56, 0xd7, 0xc6,
+ 0x75, 0xbe, 0x77, 0xdf])
+ ];
+
+ #[test]
+ fn test_zbase32_encode() {
+ for &(zbase32, data) in ZBASE32_TEST_DATA {
+ assert_eq!(Alphabet::ZBase32.encode(data), zbase32);
+ }
+ }
+
+ #[test]
+ fn test_zbase32_decode() {
+ for &(zbase32, data) in ZBASE32_TEST_DATA {
+ assert_eq!(Alphabet::ZBase32.decode(zbase32).unwrap(), data);
+ }
+ }
+
+ #[test]
+ fn test_decode_wrong() {
+ const WRONG_DATA: &[&str] = &["00", "l1", "?", "="];
+ for &data in WRONG_DATA {
+ match Alphabet::ZBase32.decode(data) {
+ Ok(_) => assert!(false, "Data shouldn't be decodable"),
+ Err(_) => assert!(true),
+ }
+ }
+ }
+
+ const RFC4648_NON_PADDED_TEST_VECTORS: &[(&[u8], &[u8])] = &[
+ (&[0xF8, 0x3E, 0x7F, 0x83, 0xE7], b"7A7H7A7H"),
+ (&[0x77, 0xC1, 0xF7, 0x7C, 0x1F], b"O7A7O7A7"),
+ (&[0xF8, 0x3E, 0x7F, 0x83, 0xE7], b"7A7H7A7H"),
+ (&[0x77, 0xC1, 0xF7, 0x7C, 0x1F], b"O7A7O7A7"),
+ ];
+
+ const RFC4648_TEST_VECTORS: &[(&[u8], &str)] = &[
+ (b"", ""),
+ (b"f", "MY======"),
+ (b"fo", "MZXQ===="),
+ (b"foo", "MZXW6==="),
+ (b"foob", "MZXW6YQ="),
+ (b"fooba", "MZXW6YTB"),
+ (b"foobar", "MZXW6YTBOI======"),
+ (&[0xF8, 0x3E, 0x7F, 0x83], "7A7H7AY="),
+ ];
+
+ #[test]
+ fn test_rfc4648_encode() {
+ for (input, encoded) in RFC4648_TEST_VECTORS {
+ assert_eq!(&Alphabet::RFC4648 { padding: true }.encode(input), encoded);
+ }
+
+ for (input, encoded) in RFC4648_NON_PADDED_TEST_VECTORS {
+ assert_eq!(&Alphabet::RFC4648 { padding: false }.encode(input).as_bytes(), encoded);
+ }
+ }
+
+ #[test]
+ fn test_rfc4648_decode() {
+ for (input, encoded) in RFC4648_TEST_VECTORS {
+ let res = &Alphabet::RFC4648 { padding: true }.decode(encoded).unwrap();
+ assert_eq!(&res[..], &input[..]);
+ }
+
+ for (input, encoded) in RFC4648_NON_PADDED_TEST_VECTORS {
+ let res = &Alphabet::RFC4648 { padding: false }.decode(std::str::from_utf8(encoded).unwrap()).unwrap();
+ assert_eq!(&res[..], &input[..]);
+ }
+ }
+
+ #[test]
+ fn padding() {
+ let num_padding = [0, 6, 4, 3, 1];
+ for i in 1..6 {
+ let encoded = Alphabet::RFC4648 { padding: true }.encode(
+ (0..(i as u8)).collect::<Vec<u8>>().as_ref()
+ );
+ assert_eq!(encoded.len(), 8);
+ for j in 0..(num_padding[i % 5]) {
+ assert_eq!(encoded.as_bytes()[encoded.len() - j - 1], b'=');
+ }
+ for j in 0..(8 - num_padding[i % 5]) {
+ assert!(encoded.as_bytes()[j] != b'=');
+ }
+ }
+ }
+
+ #[test]
+ fn test_decode_rfc4648_errors() {
+ assert!(Alphabet::RFC4648 { padding: false }.decode("abc2def===").is_err()); // Invalid char because padding is disabled
+ assert!(Alphabet::RFC4648 { padding: true }.decode("abc2def===").is_err()); // Invalid length
+ assert!(Alphabet::RFC4648 { padding: true }.decode("MZX=6YTB").is_err()); // Invalid char
+ }
+}
chacha_bytes
}
+ /// Encrypts `src` into `dest` using a single block from a ChaCha stream. Passing `dest` as
+ /// `src` in a second call will decrypt it.
+ pub fn encrypt_single_block(
+ key: &[u8; 32], nonce: &[u8; 16], dest: &mut [u8], src: &[u8]
+ ) {
+ debug_assert_eq!(dest.len(), src.len());
+ debug_assert!(dest.len() <= 32);
+
+ let block = ChaCha20::get_single_block(key, nonce);
+ for i in 0..dest.len() {
+ dest[i] = block[i] ^ src[i];
+ }
+ }
+
+ /// Same as `encrypt_single_block` only operates on a fixed-size input in-place.
+ pub fn encrypt_single_block_in_place(
+ key: &[u8; 32], nonce: &[u8; 16], bytes: &mut [u8; 32]
+ ) {
+ let block = ChaCha20::get_single_block(key, nonce);
+ for i in 0..bytes.len() {
+ bytes[i] = block[i] ^ bytes[i];
+ }
+ }
+
fn expand(key: &[u8], nonce: &[u8]) -> ChaChaState {
let constant = match key.len() {
16 => b"expand 16-byte k",
[0; 32]
}
+ pub fn encrypt_single_block(
+ _key: &[u8; 32], _nonce: &[u8; 16], dest: &mut [u8], src: &[u8]
+ ) {
+ debug_assert_eq!(dest.len(), src.len());
+ debug_assert!(dest.len() <= 32);
+ }
+
+ pub fn encrypt_single_block_in_place(
+ _key: &[u8; 32], _nonce: &[u8; 16], _bytes: &mut [u8; 32]
+ ) {}
+
pub fn process(&mut self, input: &[u8], output: &mut [u8]) {
output.copy_from_slice(input);
}
assert_eq!(ChaCha20::get_single_block(&key, &nonce_16bytes), block_bytes);
}
+
+ #[test]
+ fn encrypt_single_block() {
+ let key = [
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+ 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+ ];
+ let nonce = [
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+ ];
+ let bytes = [1; 32];
+
+ let mut encrypted_bytes = [0; 32];
+ ChaCha20::encrypt_single_block(&key, &nonce, &mut encrypted_bytes, &bytes);
+
+ let mut decrypted_bytes = [0; 32];
+ ChaCha20::encrypt_single_block(&key, &nonce, &mut decrypted_bytes, &encrypted_bytes);
+
+ assert_eq!(bytes, decrypted_bytes);
+ }
+
+ #[test]
+ fn encrypt_single_block_in_place() {
+ let key = [
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+ 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+ ];
+ let nonce = [
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+ ];
+ let unencrypted_bytes = [1; 32];
+ let mut bytes = unencrypted_bytes;
+
+ ChaCha20::encrypt_single_block_in_place(&key, &nonce, &mut bytes);
+ assert_ne!(bytes, unencrypted_bytes);
+
+ ChaCha20::encrypt_single_block_in_place(&key, &nonce, &mut bytes);
+ assert_eq!(bytes, unencrypted_bytes);
+ }
}
field3: Vec<u8>,
}
impl_writeable_tlv_based!(TestWriteable, {
- (1, field1, vec_type),
- (2, field2, vec_type),
- (3, field3, vec_type),
+ (1, field1, required_vec),
+ (2, field2, required_vec),
+ (3, field3, required_vec),
});
#[test]
}
}
+/// Options for how to set the max dust HTLC exposure allowed on a channel. See
+/// [`ChannelConfig::max_dust_htlc_exposure`] for details.
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum MaxDustHTLCExposure {
+ /// This sets a fixed limit on the total dust exposure in millisatoshis. Setting this too low
+ /// may prevent the sending or receipt of low-value HTLCs on high-traffic nodes, however this
+ /// limit is very important to prevent stealing of large amounts of dust HTLCs by miners
+ /// through [fee griefing
+ /// attacks](https://lists.linuxfoundation.org/pipermail/lightning-dev/2020-May/002714.html).
+ ///
+ /// Note that if the feerate increases significantly, without a manual increase
+ /// to this maximum the channel may be unable to send/receive HTLCs between the maximum dust
+ /// exposure and the new minimum value for HTLCs to be economically viable to claim.
+ FixedLimitMsat(u64),
+ /// This sets a multiplier on the estimated high priority feerate (sats/KW, as obtained from
+ /// [`FeeEstimator`]) to determine the maximum allowed dust exposure. If this variant is used
+ /// then the maximum dust exposure in millisatoshis is calculated as:
+ /// `high_priority_feerate_per_kw * value`. For example, with our default value
+ /// `FeeRateMultiplier(5000)`:
+ ///
+ /// - For the minimum fee rate of 1 sat/vByte (250 sat/KW, although the minimum
+ /// defaults to 253 sats/KW for rounding, see [`FeeEstimator`]), the max dust exposure would
+ /// be 253 * 5000 = 1,265,000 msats.
+ /// - For a fee rate of 30 sat/vByte (7500 sat/KW), the max dust exposure would be
+ /// 7500 * 5000 = 37,500,000 msats.
+ ///
+ /// This allows the maximum dust exposure to automatically scale with fee rate changes.
+ ///
+ /// Note, if you're using a third-party fee estimator, this may leave you more exposed to a
+ /// fee griefing attack, where your fee estimator may purposely overestimate the fee rate,
+ /// causing you to accept more dust HTLCs than you would otherwise.
+ ///
+ /// This variant is primarily meant to serve pre-anchor channels, as HTLC fees being included
+ /// on HTLC outputs means your channel may be subject to more dust exposure in the event of
+ /// increases in fee rate.
+ ///
+ /// # Backwards Compatibility
+ /// This variant only became available in LDK 0.0.116, so if you downgrade to a prior version
+ /// by default this will be set to a [`Self::FixedLimitMsat`] of 5,000,000 msat.
+ ///
+ /// [`FeeEstimator`]: crate::chain::chaininterface::FeeEstimator
+ FeeRateMultiplier(u64),
+}
+
+impl_writeable_tlv_based_enum!(MaxDustHTLCExposure, ;
+ (1, FixedLimitMsat),
+ (3, FeeRateMultiplier),
+);
+
/// Options which apply on a per-channel basis and may change at runtime or based on negotiation
/// with our counterparty.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
/// channel negotiated throughout the channel open process, along with the fees required to have
/// a broadcastable HTLC spending transaction. When a channel supports anchor outputs
/// (specifically the zero fee HTLC transaction variant), this threshold no longer takes into
- /// account the HTLC transaction fee as it is zero.
+ /// account the HTLC transaction fee as it is zero. Because of this, you may want to set this
+ /// value to a fixed limit for channels using anchor outputs, while the fee rate multiplier
+ /// variant is primarily intended for use with pre-anchor channels.
///
- /// This limit is applied for sent, forwarded, and received HTLCs and limits the total
- /// exposure across all three types per-channel. Setting this too low may prevent the
- /// sending or receipt of low-value HTLCs on high-traffic nodes, and this limit is very
- /// important to prevent stealing of dust HTLCs by miners.
+ /// The selected limit is applied for sent, forwarded, and received HTLCs and limits the total
+ /// exposure across all three types per-channel.
///
- /// Default value: 5_000_000 msat.
- pub max_dust_htlc_exposure_msat: u64,
+ /// Default value: [`MaxDustHTLCExposure::FeeRateMultiplier`] with a multiplier of 5000.
+ pub max_dust_htlc_exposure: MaxDustHTLCExposure,
/// The additional fee we're willing to pay to avoid waiting for the counterparty's
/// `to_self_delay` to reclaim funds.
///
self.cltv_expiry_delta = cltv_expiry_delta;
}
if let Some(max_dust_htlc_exposure_msat) = update.max_dust_htlc_exposure_msat {
- self.max_dust_htlc_exposure_msat = max_dust_htlc_exposure_msat;
+ self.max_dust_htlc_exposure = max_dust_htlc_exposure_msat;
}
if let Some(force_close_avoidance_max_fee_satoshis) = update.force_close_avoidance_max_fee_satoshis {
self.force_close_avoidance_max_fee_satoshis = force_close_avoidance_max_fee_satoshis;
forwarding_fee_proportional_millionths: 0,
forwarding_fee_base_msat: 1000,
cltv_expiry_delta: 6 * 12, // 6 blocks/hour * 12 hours
- max_dust_htlc_exposure_msat: 5_000_000,
+ max_dust_htlc_exposure: MaxDustHTLCExposure::FeeRateMultiplier(5000),
force_close_avoidance_max_fee_satoshis: 1000,
accept_underpaying_htlcs: false,
}
}
}
-impl_writeable_tlv_based!(ChannelConfig, {
- (0, forwarding_fee_proportional_millionths, required),
- (1, accept_underpaying_htlcs, (default_value, false)),
- (2, forwarding_fee_base_msat, required),
- (4, cltv_expiry_delta, required),
- (6, max_dust_htlc_exposure_msat, required),
- // ChannelConfig serialized this field with a required type of 8 prior to the introduction of
- // LegacyChannelConfig. To make sure that serialization is not compatible with this one, we use
- // the next required type of 10, which if seen by the old serialization will always fail.
- (10, force_close_avoidance_max_fee_satoshis, required),
-});
+impl crate::util::ser::Writeable for ChannelConfig {
+ fn write<W: crate::util::ser::Writer>(&self, writer: &mut W) -> Result<(), crate::io::Error> {
+ let max_dust_htlc_exposure_msat_fixed_limit = match self.max_dust_htlc_exposure {
+ MaxDustHTLCExposure::FixedLimitMsat(limit) => limit,
+ MaxDustHTLCExposure::FeeRateMultiplier(_) => 5_000_000,
+ };
+ write_tlv_fields!(writer, {
+ (0, self.forwarding_fee_proportional_millionths, required),
+ (1, self.accept_underpaying_htlcs, (default_value, false)),
+ (2, self.forwarding_fee_base_msat, required),
+ (3, self.max_dust_htlc_exposure, required),
+ (4, self.cltv_expiry_delta, required),
+ (6, max_dust_htlc_exposure_msat_fixed_limit, required),
+ // ChannelConfig serialized this field with a required type of 8 prior to the introduction of
+ // LegacyChannelConfig. To make sure that serialization is not compatible with this one, we use
+ // the next required type of 10, which if seen by the old serialization will always fail.
+ (10, self.force_close_avoidance_max_fee_satoshis, required),
+ });
+ Ok(())
+ }
+}
+
+impl crate::util::ser::Readable for ChannelConfig {
+ fn read<R: crate::io::Read>(reader: &mut R) -> Result<Self, crate::ln::msgs::DecodeError> {
+ let mut forwarding_fee_proportional_millionths = 0;
+ let mut accept_underpaying_htlcs = false;
+ let mut forwarding_fee_base_msat = 1000;
+ let mut cltv_expiry_delta = 6 * 12;
+ let mut max_dust_htlc_exposure_msat = None;
+ let mut max_dust_htlc_exposure_enum = None;
+ let mut force_close_avoidance_max_fee_satoshis = 1000;
+ read_tlv_fields!(reader, {
+ (0, forwarding_fee_proportional_millionths, required),
+ (1, accept_underpaying_htlcs, (default_value, false)),
+ (2, forwarding_fee_base_msat, required),
+ (3, max_dust_htlc_exposure_enum, option),
+ (4, cltv_expiry_delta, required),
+ // Has always been written, but became optionally read in 0.0.116
+ (6, max_dust_htlc_exposure_msat, option),
+ (10, force_close_avoidance_max_fee_satoshis, required),
+ });
+ let max_dust_htlc_fixed_limit = max_dust_htlc_exposure_msat.unwrap_or(5_000_000);
+ let max_dust_htlc_exposure_msat = max_dust_htlc_exposure_enum
+ .unwrap_or(MaxDustHTLCExposure::FixedLimitMsat(max_dust_htlc_fixed_limit));
+ Ok(Self {
+ forwarding_fee_proportional_millionths,
+ accept_underpaying_htlcs,
+ forwarding_fee_base_msat,
+ cltv_expiry_delta,
+ max_dust_htlc_exposure: max_dust_htlc_exposure_msat,
+ force_close_avoidance_max_fee_satoshis,
+ })
+ }
+}
/// A parallel struct to [`ChannelConfig`] to define partial updates.
#[allow(missing_docs)]
pub forwarding_fee_proportional_millionths: Option<u32>,
pub forwarding_fee_base_msat: Option<u32>,
pub cltv_expiry_delta: Option<u16>,
- pub max_dust_htlc_exposure_msat: Option<u64>,
+ pub max_dust_htlc_exposure_msat: Option<MaxDustHTLCExposure>,
pub force_close_avoidance_max_fee_satoshis: Option<u64>,
}
forwarding_fee_proportional_millionths: Some(config.forwarding_fee_proportional_millionths),
forwarding_fee_base_msat: Some(config.forwarding_fee_base_msat),
cltv_expiry_delta: Some(config.cltv_expiry_delta),
- max_dust_htlc_exposure_msat: Some(config.max_dust_htlc_exposure_msat),
+ max_dust_htlc_exposure_msat: Some(config.max_dust_htlc_exposure),
force_close_avoidance_max_fee_satoshis: Some(config.force_close_avoidance_max_fee_satoshis),
}
}
impl crate::util::ser::Writeable for LegacyChannelConfig {
fn write<W: crate::util::ser::Writer>(&self, writer: &mut W) -> Result<(), crate::io::Error> {
+ let max_dust_htlc_exposure_msat_fixed_limit = match self.options.max_dust_htlc_exposure {
+ MaxDustHTLCExposure::FixedLimitMsat(limit) => limit,
+ MaxDustHTLCExposure::FeeRateMultiplier(_) => 5_000_000,
+ };
write_tlv_fields!(writer, {
(0, self.options.forwarding_fee_proportional_millionths, required),
- (1, self.options.max_dust_htlc_exposure_msat, (default_value, 5_000_000)),
+ (1, max_dust_htlc_exposure_msat_fixed_limit, required),
(2, self.options.cltv_expiry_delta, required),
(3, self.options.force_close_avoidance_max_fee_satoshis, (default_value, 1000)),
(4, self.announced_channel, required),
+ (5, self.options.max_dust_htlc_exposure, required),
(6, self.commit_upfront_shutdown_pubkey, required),
(8, self.options.forwarding_fee_base_msat, required),
});
impl crate::util::ser::Readable for LegacyChannelConfig {
fn read<R: crate::io::Read>(reader: &mut R) -> Result<Self, crate::ln::msgs::DecodeError> {
let mut forwarding_fee_proportional_millionths = 0;
- let mut max_dust_htlc_exposure_msat = 5_000_000;
+ let mut max_dust_htlc_exposure_msat_fixed_limit = None;
let mut cltv_expiry_delta = 0;
let mut force_close_avoidance_max_fee_satoshis = 1000;
let mut announced_channel = false;
let mut commit_upfront_shutdown_pubkey = false;
let mut forwarding_fee_base_msat = 0;
+ let mut max_dust_htlc_exposure_enum = None;
read_tlv_fields!(reader, {
(0, forwarding_fee_proportional_millionths, required),
- (1, max_dust_htlc_exposure_msat, (default_value, 5_000_000u64)),
+ // Has always been written, but became optionally read in 0.0.116
+ (1, max_dust_htlc_exposure_msat_fixed_limit, option),
(2, cltv_expiry_delta, required),
(3, force_close_avoidance_max_fee_satoshis, (default_value, 1000u64)),
(4, announced_channel, required),
+ (5, max_dust_htlc_exposure_enum, option),
(6, commit_upfront_shutdown_pubkey, required),
(8, forwarding_fee_base_msat, required),
});
+ let max_dust_htlc_exposure_msat_fixed_limit =
+ max_dust_htlc_exposure_msat_fixed_limit.unwrap_or(5_000_000);
+ let max_dust_htlc_exposure_msat = max_dust_htlc_exposure_enum
+ .unwrap_or(MaxDustHTLCExposure::FixedLimitMsat(max_dust_htlc_exposure_msat_fixed_limit));
Ok(Self {
options: ChannelConfig {
forwarding_fee_proportional_millionths,
- max_dust_htlc_exposure_msat,
+ max_dust_htlc_exposure: max_dust_htlc_exposure_msat,
cltv_expiry_delta,
force_close_avoidance_max_fee_satoshis,
forwarding_fee_base_msat,
let (k1, k2, _) = hkdf_extract_expand!($salt, $ikm);
(k1, k2)
}};
- ($salt: expr, $ikm: expr, 4) => {{
+ ($salt: expr, $ikm: expr, 5) => {{
let (k1, k2, prk) = hkdf_extract_expand!($salt, $ikm);
let mut hmac = HmacEngine::<Sha256>::new(&prk[..]);
let mut hmac = HmacEngine::<Sha256>::new(&prk[..]);
hmac.input(&k3);
hmac.input(&[4; 1]);
- (k1, k2, k3, Hmac::from_engine(hmac).into_inner())
+ let k4 = Hmac::from_engine(hmac).into_inner();
+
+ let mut hmac = HmacEngine::<Sha256>::new(&prk[..]);
+ hmac.input(&k4);
+ hmac.input(&[5; 1]);
+ let k5 = Hmac::from_engine(hmac).into_inner();
+
+ (k1, k2, k3, k4, k5)
}}
}
hkdf_extract_expand!(salt, ikm, 2)
}
-pub fn hkdf_extract_expand_4x(salt: &[u8], ikm: &[u8]) -> ([u8; 32], [u8; 32], [u8; 32], [u8; 32]) {
- hkdf_extract_expand!(salt, ikm, 4)
+pub fn hkdf_extract_expand_5x(salt: &[u8], ikm: &[u8]) -> ([u8; 32], [u8; 32], [u8; 32], [u8; 32], [u8; 32]) {
+ hkdf_extract_expand!(salt, ikm, 5)
}
#[inline]
+++ /dev/null
-// This file is Copyright its original authors, visible in version control
-// history.
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-
-use crate::ln::channel::{ANCHOR_OUTPUT_VALUE_SATOSHI, MIN_CHAN_DUST_LIMIT_SATOSHIS};
-use crate::ln::chan_utils::{HTLCOutputInCommitment, ChannelPublicKeys, HolderCommitmentTransaction, CommitmentTransaction, ChannelTransactionParameters, TrustedCommitmentTransaction, ClosingTransaction};
-use crate::ln::{chan_utils, msgs, PaymentPreimage};
-use crate::sign::{WriteableEcdsaChannelSigner, InMemorySigner, ChannelSigner, EcdsaChannelSigner};
-
-use crate::prelude::*;
-use core::cmp;
-use crate::sync::{Mutex, Arc};
-#[cfg(test)] use crate::sync::MutexGuard;
-
-use bitcoin::blockdata::transaction::{Transaction, EcdsaSighashType};
-use bitcoin::util::sighash;
-
-use bitcoin::secp256k1;
-use bitcoin::secp256k1::{SecretKey, PublicKey};
-use bitcoin::secp256k1::{Secp256k1, ecdsa::Signature};
-use crate::events::bump_transaction::HTLCDescriptor;
-use crate::util::ser::{Writeable, Writer};
-use crate::io::Error;
-use crate::ln::features::ChannelTypeFeatures;
-
-/// Initial value for revoked commitment downward counter
-pub const INITIAL_REVOKED_COMMITMENT_NUMBER: u64 = 1 << 48;
-
-/// An implementation of Sign that enforces some policy checks. The current checks
-/// are an incomplete set. They include:
-///
-/// - When signing, the holder transaction has not been revoked
-/// - When revoking, the holder transaction has not been signed
-/// - The holder commitment number is monotonic and without gaps
-/// - The revoked holder commitment number is monotonic and without gaps
-/// - There is at least one unrevoked holder transaction at all times
-/// - The counterparty commitment number is monotonic and without gaps
-/// - The pre-derived keys and pre-built transaction in CommitmentTransaction were correctly built
-///
-/// Eventually we will probably want to expose a variant of this which would essentially
-/// be what you'd want to run on a hardware wallet.
-///
-/// Note that counterparty signatures on the holder transaction are not checked, but it should
-/// be in a complete implementation.
-///
-/// Note that before we do so we should ensure its serialization format has backwards- and
-/// forwards-compatibility prefix/suffixes!
-#[derive(Clone)]
-pub struct EnforcingSigner {
- pub inner: InMemorySigner,
- /// Channel state used for policy enforcement
- pub state: Arc<Mutex<EnforcementState>>,
- pub disable_revocation_policy_check: bool,
-}
-
-impl PartialEq for EnforcingSigner {
- fn eq(&self, o: &Self) -> bool {
- Arc::ptr_eq(&self.state, &o.state)
- }
-}
-
-impl EnforcingSigner {
- /// Construct an EnforcingSigner
- pub fn new(inner: InMemorySigner) -> Self {
- let state = Arc::new(Mutex::new(EnforcementState::new()));
- Self {
- inner,
- state,
- disable_revocation_policy_check: false
- }
- }
-
- /// Construct an EnforcingSigner with externally managed storage
- ///
- /// Since there are multiple copies of this struct for each channel, some coordination is needed
- /// so that all copies are aware of enforcement state. A pointer to this state is provided
- /// here, usually by an implementation of KeysInterface.
- pub fn new_with_revoked(inner: InMemorySigner, state: Arc<Mutex<EnforcementState>>, disable_revocation_policy_check: bool) -> Self {
- Self {
- inner,
- state,
- disable_revocation_policy_check
- }
- }
-
- pub fn channel_type_features(&self) -> &ChannelTypeFeatures { self.inner.channel_type_features() }
-
- #[cfg(test)]
- pub fn get_enforcement_state(&self) -> MutexGuard<EnforcementState> {
- self.state.lock().unwrap()
- }
-}
-
-impl ChannelSigner for EnforcingSigner {
- fn get_per_commitment_point(&self, idx: u64, secp_ctx: &Secp256k1<secp256k1::All>) -> PublicKey {
- self.inner.get_per_commitment_point(idx, secp_ctx)
- }
-
- fn release_commitment_secret(&self, idx: u64) -> [u8; 32] {
- {
- let mut state = self.state.lock().unwrap();
- assert!(idx == state.last_holder_revoked_commitment || idx == state.last_holder_revoked_commitment - 1, "can only revoke the current or next unrevoked commitment - trying {}, last revoked {}", idx, state.last_holder_revoked_commitment);
- assert!(idx > state.last_holder_commitment, "cannot revoke the last holder commitment - attempted to revoke {} last commitment {}", idx, state.last_holder_commitment);
- state.last_holder_revoked_commitment = idx;
- }
- self.inner.release_commitment_secret(idx)
- }
-
- fn validate_holder_commitment(&self, holder_tx: &HolderCommitmentTransaction, _preimages: Vec<PaymentPreimage>) -> Result<(), ()> {
- let mut state = self.state.lock().unwrap();
- let idx = holder_tx.commitment_number();
- assert!(idx == state.last_holder_commitment || idx == state.last_holder_commitment - 1, "expecting to validate the current or next holder commitment - trying {}, current {}", idx, state.last_holder_commitment);
- state.last_holder_commitment = idx;
- Ok(())
- }
-
- fn pubkeys(&self) -> &ChannelPublicKeys { self.inner.pubkeys() }
-
- fn channel_keys_id(&self) -> [u8; 32] { self.inner.channel_keys_id() }
-
- fn provide_channel_parameters(&mut self, channel_parameters: &ChannelTransactionParameters) {
- self.inner.provide_channel_parameters(channel_parameters)
- }
-}
-
-impl EcdsaChannelSigner for EnforcingSigner {
- fn sign_counterparty_commitment(&self, commitment_tx: &CommitmentTransaction, preimages: Vec<PaymentPreimage>, secp_ctx: &Secp256k1<secp256k1::All>) -> Result<(Signature, Vec<Signature>), ()> {
- self.verify_counterparty_commitment_tx(commitment_tx, secp_ctx);
-
- {
- let mut state = self.state.lock().unwrap();
- let actual_commitment_number = commitment_tx.commitment_number();
- let last_commitment_number = state.last_counterparty_commitment;
- // These commitment numbers are backwards counting. We expect either the same as the previously encountered,
- // or the next one.
- assert!(last_commitment_number == actual_commitment_number || last_commitment_number - 1 == actual_commitment_number, "{} doesn't come after {}", actual_commitment_number, last_commitment_number);
- // Ensure that the counterparty doesn't get more than two broadcastable commitments -
- // the last and the one we are trying to sign
- assert!(actual_commitment_number >= state.last_counterparty_revoked_commitment - 2, "cannot sign a commitment if second to last wasn't revoked - signing {} revoked {}", actual_commitment_number, state.last_counterparty_revoked_commitment);
- state.last_counterparty_commitment = cmp::min(last_commitment_number, actual_commitment_number)
- }
-
- Ok(self.inner.sign_counterparty_commitment(commitment_tx, preimages, secp_ctx).unwrap())
- }
-
- fn validate_counterparty_revocation(&self, idx: u64, _secret: &SecretKey) -> Result<(), ()> {
- let mut state = self.state.lock().unwrap();
- assert!(idx == state.last_counterparty_revoked_commitment || idx == state.last_counterparty_revoked_commitment - 1, "expecting to validate the current or next counterparty revocation - trying {}, current {}", idx, state.last_counterparty_revoked_commitment);
- state.last_counterparty_revoked_commitment = idx;
- Ok(())
- }
-
- fn sign_holder_commitment_and_htlcs(&self, commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1<secp256k1::All>) -> Result<(Signature, Vec<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();
-
- 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 {
- 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, 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())
- }
-
- #[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 sign_justice_revoked_output(&self, justice_tx: &Transaction, input: usize, amount: u64, per_commitment_key: &SecretKey, secp_ctx: &Secp256k1<secp256k1::All>) -> Result<Signature, ()> {
- Ok(self.inner.sign_justice_revoked_output(justice_tx, input, amount, per_commitment_key, secp_ctx).unwrap())
- }
-
- fn sign_justice_revoked_htlc(&self, justice_tx: &Transaction, input: usize, amount: u64, per_commitment_key: &SecretKey, htlc: &HTLCOutputInCommitment, secp_ctx: &Secp256k1<secp256k1::All>) -> Result<Signature, ()> {
- Ok(self.inner.sign_justice_revoked_htlc(justice_tx, input, amount, per_commitment_key, htlc, secp_ctx).unwrap())
- }
-
- fn sign_holder_htlc_transaction(
- &self, htlc_tx: &Transaction, input: usize, htlc_descriptor: &HTLCDescriptor,
- secp_ctx: &Secp256k1<secp256k1::All>
- ) -> Result<Signature, ()> {
- let per_commitment_point = self.get_per_commitment_point(htlc_descriptor.per_commitment_number, secp_ctx);
- assert_eq!(htlc_tx.input[input], htlc_descriptor.unsigned_tx_input());
- assert_eq!(htlc_tx.output[input], htlc_descriptor.tx_output(&per_commitment_point, secp_ctx));
- Ok(self.inner.sign_holder_htlc_transaction(htlc_tx, input, htlc_descriptor, secp_ctx).unwrap())
- }
-
- fn sign_counterparty_htlc_transaction(&self, htlc_tx: &Transaction, input: usize, amount: u64, per_commitment_point: &PublicKey, htlc: &HTLCOutputInCommitment, secp_ctx: &Secp256k1<secp256k1::All>) -> Result<Signature, ()> {
- Ok(self.inner.sign_counterparty_htlc_transaction(htlc_tx, input, amount, per_commitment_point, htlc, secp_ctx).unwrap())
- }
-
- fn sign_closing_transaction(&self, closing_tx: &ClosingTransaction, secp_ctx: &Secp256k1<secp256k1::All>) -> Result<Signature, ()> {
- closing_tx.verify(self.inner.funding_outpoint().into_bitcoin_outpoint())
- .expect("derived different closing transaction");
- Ok(self.inner.sign_closing_transaction(closing_tx, secp_ctx).unwrap())
- }
-
- fn sign_holder_anchor_input(
- &self, anchor_tx: &Transaction, input: usize, secp_ctx: &Secp256k1<secp256k1::All>,
- ) -> Result<Signature, ()> {
- debug_assert!(MIN_CHAN_DUST_LIMIT_SATOSHIS > ANCHOR_OUTPUT_VALUE_SATOSHI);
- // As long as our minimum dust limit is enforced and is greater than our anchor output
- // value, an anchor output can only have an index within [0, 1].
- assert!(anchor_tx.input[input].previous_output.vout == 0 || anchor_tx.input[input].previous_output.vout == 1);
- self.inner.sign_holder_anchor_input(anchor_tx, input, secp_ctx)
- }
-
- fn sign_channel_announcement_with_funding_key(
- &self, msg: &msgs::UnsignedChannelAnnouncement, secp_ctx: &Secp256k1<secp256k1::All>
- ) -> Result<Signature, ()> {
- self.inner.sign_channel_announcement_with_funding_key(msg, secp_ctx)
- }
-}
-
-impl WriteableEcdsaChannelSigner for EnforcingSigner {}
-
-impl Writeable for EnforcingSigner {
- fn write<W: Writer>(&self, writer: &mut W) -> Result<(), Error> {
- // EnforcingSigner has two fields - `inner` ([`InMemorySigner`]) and `state`
- // ([`EnforcementState`]). `inner` is serialized here and deserialized by
- // [`SignerProvider::read_chan_signer`]. `state` is managed by [`SignerProvider`]
- // and will be serialized as needed by the implementation of that trait.
- self.inner.write(writer)?;
- Ok(())
- }
-}
-
-impl EnforcingSigner {
- fn verify_counterparty_commitment_tx<'a, T: secp256k1::Signing + secp256k1::Verification>(&self, commitment_tx: &'a CommitmentTransaction, secp_ctx: &Secp256k1<T>) -> TrustedCommitmentTransaction<'a> {
- commitment_tx.verify(&self.inner.get_channel_parameters().as_counterparty_broadcastable(),
- self.inner.counterparty_pubkeys(), self.inner.pubkeys(), secp_ctx)
- .expect("derived different per-tx keys or built transaction")
- }
-
- fn verify_holder_commitment_tx<'a, T: secp256k1::Signing + secp256k1::Verification>(&self, commitment_tx: &'a CommitmentTransaction, secp_ctx: &Secp256k1<T>) -> TrustedCommitmentTransaction<'a> {
- commitment_tx.verify(&self.inner.get_channel_parameters().as_holder_broadcastable(),
- self.inner.pubkeys(), self.inner.counterparty_pubkeys(), secp_ctx)
- .expect("derived different per-tx keys or built transaction")
- }
-}
-
-/// The state used by [`EnforcingSigner`] in order to enforce policy checks
-///
-/// This structure is maintained by KeysInterface since we may have multiple copies of
-/// the signer and they must coordinate their state.
-#[derive(Clone)]
-pub struct EnforcementState {
- /// The last counterparty commitment number we signed, backwards counting
- pub last_counterparty_commitment: u64,
- /// The last counterparty commitment they revoked, backwards counting
- pub last_counterparty_revoked_commitment: u64,
- /// The last holder commitment number we revoked, backwards counting
- pub last_holder_revoked_commitment: u64,
- /// The last validated holder commitment number, backwards counting
- pub last_holder_commitment: u64,
-}
-
-impl EnforcementState {
- /// Enforcement state for a new channel
- pub fn new() -> Self {
- EnforcementState {
- last_counterparty_commitment: INITIAL_REVOKED_COMMITMENT_NUMBER,
- last_counterparty_revoked_commitment: INITIAL_REVOKED_COMMITMENT_NUMBER,
- last_holder_revoked_commitment: INITIAL_REVOKED_COMMITMENT_NUMBER,
- last_holder_commitment: INITIAL_REVOKED_COMMITMENT_NUMBER,
- }
- }
-}
}
}
+/// Wrapper for logging `Iterator`s.
+///
+/// This is not exported to bindings users as fmt can't be used in C
+#[doc(hidden)]
+pub struct DebugIter<T: fmt::Display, I: core::iter::Iterator<Item = T> + Clone>(pub I);
+impl<T: fmt::Display, I: core::iter::Iterator<Item = T> + Clone> fmt::Display for DebugIter<T, I> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ write!(f, "[")?;
+ let mut iter = self.0.clone();
+ if let Some(item) = iter.next() {
+ write!(f, "{}", item)?;
+ }
+ while let Some(item) = iter.next() {
+ write!(f, ", {}", item)?;
+ }
+ write!(f, "]")?;
+ Ok(())
+ }
+}
+
#[cfg(test)]
mod tests {
use crate::util::logger::{Logger, Level};
use crate::routing::router::Route;
use crate::ln::chan_utils::HTLCClaim;
-use crate::util::logger::DebugBytes;
+
+macro_rules! log_iter {
+ ($obj: expr) => {
+ $crate::util::logger::DebugIter($obj)
+ }
+}
/// Logs a pubkey in hex format.
#[macro_export]
pub(crate) struct DebugFundingChannelId<'a>(pub &'a Txid, pub u16);
impl<'a> core::fmt::Display for DebugFundingChannelId<'a> {
fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
- for i in (OutPoint { txid: self.0.clone(), index: self.1 }).to_channel_id().iter() {
- write!(f, "{:02x}", i)?;
- }
- Ok(())
+ (OutPoint { txid: self.0.clone(), index: self.1 }).to_channel_id().fmt(f)
}
}
macro_rules! log_funding_channel_id {
pub(crate) struct DebugFundingInfo<'a, T: 'a>(pub &'a (OutPoint, T));
impl<'a, T> core::fmt::Display for DebugFundingInfo<'a, T> {
fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
- DebugBytes(&(self.0).0.to_channel_id()[..]).fmt(f)
+ (self.0).0.to_channel_id().fmt(f)
}
}
macro_rules! log_funding_info {
//! <https://api.lightning.community/#signmessage>
use crate::prelude::*;
-use crate::util::zbase32;
+use crate::util::base32;
use bitcoin::hashes::{sha256d, Hash};
use bitcoin::secp256k1::ecdsa::{RecoverableSignature, RecoveryId};
use bitcoin::secp256k1::{Error, Message, PublicKey, Secp256k1, SecretKey};
static LN_MESSAGE_PREFIX: &[u8] = b"Lightning Signed Message:";
fn sigrec_encode(sig_rec: RecoverableSignature) -> Vec<u8> {
- let (rid, rsig) = sig_rec.serialize_compact();
- let prefix = rid.to_i32() as u8 + 31;
+ let (rid, rsig) = sig_rec.serialize_compact();
+ let prefix = rid.to_i32() as u8 + 31;
- [&[prefix], &rsig[..]].concat()
+ [&[prefix], &rsig[..]].concat()
}
fn sigrec_decode(sig_rec: Vec<u8>) -> Result<RecoverableSignature, Error> {
- // Signature must be 64 + 1 bytes long (compact signature + recovery id)
- if sig_rec.len() != 65 {
- return Err(Error::InvalidSignature);
- }
-
- let rsig = &sig_rec[1..];
- let rid = sig_rec[0] as i32 - 31;
-
- match RecoveryId::from_i32(rid) {
- Ok(x) => RecoverableSignature::from_compact(rsig, x),
- Err(e) => Err(e)
- }
+ // Signature must be 64 + 1 bytes long (compact signature + recovery id)
+ if sig_rec.len() != 65 {
+ return Err(Error::InvalidSignature);
+ }
+
+ let rsig = &sig_rec[1..];
+ let rid = sig_rec[0] as i32 - 31;
+
+ match RecoveryId::from_i32(rid) {
+ Ok(x) => RecoverableSignature::from_compact(rsig, x),
+ Err(e) => Err(e)
+ }
}
/// Creates a digital signature of a message given a SecretKey, like the node's secret.
/// A receiver knowing the PublicKey (e.g. the node's id) and the message can be sure that the signature was generated by the caller.
/// Signatures are EC recoverable, meaning that given the message and the signature the PublicKey of the signer can be extracted.
pub fn sign(msg: &[u8], sk: &SecretKey) -> Result<String, Error> {
- let secp_ctx = Secp256k1::signing_only();
- let msg_hash = sha256d::Hash::hash(&[LN_MESSAGE_PREFIX, msg].concat());
+ let secp_ctx = Secp256k1::signing_only();
+ let msg_hash = sha256d::Hash::hash(&[LN_MESSAGE_PREFIX, msg].concat());
- let sig = secp_ctx.sign_ecdsa_recoverable(&Message::from_slice(&msg_hash)?, sk);
- Ok(zbase32::encode(&sigrec_encode(sig)))
+ let sig = secp_ctx.sign_ecdsa_recoverable(&Message::from_slice(&msg_hash)?, sk);
+ Ok(base32::Alphabet::ZBase32.encode(&sigrec_encode(sig)))
}
/// Recovers the PublicKey of the signer of the message given the message and the signature.
pub fn recover_pk(msg: &[u8], sig: &str) -> Result<PublicKey, Error> {
- let secp_ctx = Secp256k1::verification_only();
- let msg_hash = sha256d::Hash::hash(&[LN_MESSAGE_PREFIX, msg].concat());
-
- match zbase32::decode(&sig) {
- Ok(sig_rec) => {
- match sigrec_decode(sig_rec) {
- Ok(sig) => secp_ctx.recover_ecdsa(&Message::from_slice(&msg_hash)?, &sig),
- Err(e) => Err(e)
- }
- },
- Err(_) => Err(Error::InvalidSignature)
- }
+ let secp_ctx = Secp256k1::verification_only();
+ let msg_hash = sha256d::Hash::hash(&[LN_MESSAGE_PREFIX, msg].concat());
+
+ match base32::Alphabet::ZBase32.decode(&sig) {
+ Ok(sig_rec) => {
+ match sigrec_decode(sig_rec) {
+ Ok(sig) => secp_ctx.recover_ecdsa(&Message::from_slice(&msg_hash)?, &sig),
+ Err(e) => Err(e)
+ }
+ },
+ Err(_) => Err(Error::InvalidSignature)
+ }
}
/// Verifies a message was signed by a PrivateKey that derives to a given PublicKey, given a message, a signature,
/// and the PublicKey.
pub fn verify(msg: &[u8], sig: &str, pk: &PublicKey) -> bool {
- match recover_pk(msg, sig) {
- Ok(x) => x == *pk,
- Err(_) => false
- }
+ match recover_pk(msg, sig) {
+ Ok(x) => x == *pk,
+ Err(_) => false
+ }
}
#[cfg(test)]
mod test {
- use core::str::FromStr;
- use crate::util::message_signing::{sign, recover_pk, verify};
- use bitcoin::secp256k1::ONE_KEY;
- use bitcoin::secp256k1::{PublicKey, Secp256k1};
-
- #[test]
- fn test_sign() {
- let message = "test message";
- let zbase32_sig = sign(message.as_bytes(), &ONE_KEY);
-
- assert_eq!(zbase32_sig.unwrap(), "d9tibmnic9t5y41hg7hkakdcra94akas9ku3rmmj4ag9mritc8ok4p5qzefs78c9pqfhpuftqqzhydbdwfg7u6w6wdxcqpqn4sj4e73e")
- }
-
- #[test]
- fn test_recover_pk() {
- let message = "test message";
- let sig = "d9tibmnic9t5y41hg7hkakdcra94akas9ku3rmmj4ag9mritc8ok4p5qzefs78c9pqfhpuftqqzhydbdwfg7u6w6wdxcqpqn4sj4e73e";
- let pk = recover_pk(message.as_bytes(), sig);
-
- assert_eq!(pk.unwrap(), PublicKey::from_secret_key(&Secp256k1::signing_only(), &ONE_KEY))
- }
-
- #[test]
- fn test_verify() {
- let message = "another message";
- let sig = sign(message.as_bytes(), &ONE_KEY).unwrap();
- let pk = PublicKey::from_secret_key(&Secp256k1::signing_only(), &ONE_KEY);
-
- assert!(verify(message.as_bytes(), &sig, &pk))
- }
-
- #[test]
- fn test_verify_ground_truth_ish() {
- // There are no standard tests vectors for Sign/Verify, using the same tests vectors as c-lightning to see if they are compatible.
- // Taken from https://github.com/ElementsProject/lightning/blob/1275af6fbb02460c8eb2f00990bb0ef9179ce8f3/tests/test_misc.py#L1925-L1938
-
- let corpus = [
- ["@bitconner",
- "is this compatible?",
- "rbgfioj114mh48d8egqx8o9qxqw4fmhe8jbeeabdioxnjk8z3t1ma1hu1fiswpakgucwwzwo6ofycffbsqusqdimugbh41n1g698hr9t",
- "02b80cabdf82638aac86948e4c06e82064f547768dcef977677b9ea931ea75bab5"],
- ["@duck1123",
- "hi",
- "rnrphcjswusbacjnmmmrynh9pqip7sy5cx695h6mfu64iac6qmcmsd8xnsyczwmpqp9shqkth3h4jmkgyqu5z47jfn1q7gpxtaqpx4xg",
- "02de60d194e1ca5947b59fe8e2efd6aadeabfb67f2e89e13ae1a799c1e08e4a43b"],
- ["@jochemin",
- "hi",
- "ry8bbsopmduhxy3dr5d9ekfeabdpimfx95kagdem7914wtca79jwamtbw4rxh69hg7n6x9ty8cqk33knbxaqftgxsfsaeprxkn1k48p3",
- "022b8ece90ee891cbcdac0c1cc6af46b73c47212d8defbce80265ac81a6b794931"],
- ];
-
- for c in &corpus {
- assert!(verify(c[1].as_bytes(), c[2], &PublicKey::from_str(c[3]).unwrap()))
- }
- }
+ use core::str::FromStr;
+ use crate::util::message_signing::{sign, recover_pk, verify};
+ use bitcoin::secp256k1::ONE_KEY;
+ use bitcoin::secp256k1::{PublicKey, Secp256k1};
+
+ #[test]
+ fn test_sign() {
+ let message = "test message";
+ let zbase32_sig = sign(message.as_bytes(), &ONE_KEY);
+
+ assert_eq!(zbase32_sig.unwrap(), "d9tibmnic9t5y41hg7hkakdcra94akas9ku3rmmj4ag9mritc8ok4p5qzefs78c9pqfhpuftqqzhydbdwfg7u6w6wdxcqpqn4sj4e73e")
+ }
+
+ #[test]
+ fn test_recover_pk() {
+ let message = "test message";
+ let sig = "d9tibmnic9t5y41hg7hkakdcra94akas9ku3rmmj4ag9mritc8ok4p5qzefs78c9pqfhpuftqqzhydbdwfg7u6w6wdxcqpqn4sj4e73e";
+ let pk = recover_pk(message.as_bytes(), sig);
+
+ assert_eq!(pk.unwrap(), PublicKey::from_secret_key(&Secp256k1::signing_only(), &ONE_KEY))
+ }
+
+ #[test]
+ fn test_verify() {
+ let message = "another message";
+ let sig = sign(message.as_bytes(), &ONE_KEY).unwrap();
+ let pk = PublicKey::from_secret_key(&Secp256k1::signing_only(), &ONE_KEY);
+
+ assert!(verify(message.as_bytes(), &sig, &pk))
+ }
+
+ #[test]
+ fn test_verify_ground_truth_ish() {
+ // There are no standard tests vectors for Sign/Verify, using the same tests vectors as c-lightning to see if they are compatible.
+ // Taken from https://github.com/ElementsProject/lightning/blob/1275af6fbb02460c8eb2f00990bb0ef9179ce8f3/tests/test_misc.py#L1925-L1938
+
+ let corpus = [
+ ["@bitconner",
+ "is this compatible?",
+ "rbgfioj114mh48d8egqx8o9qxqw4fmhe8jbeeabdioxnjk8z3t1ma1hu1fiswpakgucwwzwo6ofycffbsqusqdimugbh41n1g698hr9t",
+ "02b80cabdf82638aac86948e4c06e82064f547768dcef977677b9ea931ea75bab5"],
+ ["@duck1123",
+ "hi",
+ "rnrphcjswusbacjnmmmrynh9pqip7sy5cx695h6mfu64iac6qmcmsd8xnsyczwmpqp9shqkth3h4jmkgyqu5z47jfn1q7gpxtaqpx4xg",
+ "02de60d194e1ca5947b59fe8e2efd6aadeabfb67f2e89e13ae1a799c1e08e4a43b"],
+ ["@jochemin",
+ "hi",
+ "ry8bbsopmduhxy3dr5d9ekfeabdpimfx95kagdem7914wtca79jwamtbw4rxh69hg7n6x9ty8cqk33knbxaqftgxsfsaeprxkn1k48p3",
+ "022b8ece90ee891cbcdac0c1cc6af46b73c47212d8defbce80265ac81a6b794931"],
+ ];
+
+ for c in &corpus {
+ assert!(verify(c[1].as_bytes(), c[2], &PublicKey::from_str(c[3]).unwrap()))
+ }
+ }
}
+
pub mod persist;
pub mod string;
pub mod wakers;
+#[cfg(fuzzing)]
+pub mod base32;
+#[cfg(not(fuzzing))]
+pub(crate) mod base32;
pub(crate) mod atomic_counter;
pub(crate) mod byte_utils;
pub(crate) mod chacha20;
-#[cfg(fuzzing)]
-pub mod zbase32;
-#[cfg(not(fuzzing))]
-pub(crate) mod zbase32;
#[cfg(not(fuzzing))]
pub(crate) mod poly1305;
pub(crate) mod chacha20poly1305rfc;
/// impls of traits that add exra enforcement on the way they're called. Useful for detecting state
/// machine errors and used in fuzz targets and tests.
#[cfg(any(test, feature = "_test_utils"))]
-pub mod enforcing_trait_impls;
+pub mod test_channel_signer;
// You may not use this file except in accordance with one or both of these
// licenses.
-//! This module contains a simple key-value store trait KVStorePersister that
+//! This module contains a simple key-value store trait [`KVStore`] that
//! allows one to implement the persistence for [`ChannelManager`], [`NetworkGraph`],
//! and [`ChannelMonitor`] all in one place.
+use core::cmp;
+use core::convert::{TryFrom, TryInto};
use core::ops::Deref;
-use bitcoin::hashes::hex::ToHex;
-use crate::io;
-use crate::routing::scoring::WriteableScore;
+use bitcoin::hashes::hex::{FromHex, ToHex};
+use bitcoin::{BlockHash, Txid};
+
+use crate::{io, log_error};
+use crate::alloc::string::ToString;
+use crate::prelude::{Vec, String};
use crate::chain;
use crate::chain::chaininterface::{BroadcasterInterface, FeeEstimator};
use crate::chain::chainmonitor::{Persist, MonitorUpdateId};
use crate::sign::{EntropySource, NodeSigner, WriteableEcdsaChannelSigner, SignerProvider};
use crate::chain::transaction::OutPoint;
-use crate::chain::channelmonitor::{ChannelMonitor, ChannelMonitorUpdate};
+use crate::chain::channelmonitor::{ChannelMonitor, ChannelMonitorUpdate, CLOSED_CHANNEL_UPDATE_ID};
use crate::ln::channelmanager::ChannelManager;
use crate::routing::router::Router;
use crate::routing::gossip::NetworkGraph;
-use super::{logger::Logger, ser::Writeable};
-
-/// Trait for a key-value store for persisting some writeable object at some key
-/// Implementing `KVStorePersister` provides auto-implementations for [`Persister`]
-/// and [`Persist`] traits. It uses "manager", "network_graph",
-/// and "monitors/{funding_txo_id}_{funding_txo_index}" for keys.
-pub trait KVStorePersister {
- /// Persist the given writeable using the provided key
- fn persist<W: Writeable>(&self, key: &str, object: &W) -> io::Result<()>;
+use crate::routing::scoring::WriteableScore;
+use crate::util::logger::Logger;
+use crate::util::ser::{Readable, ReadableArgs, Writeable};
+
+/// The alphabet of characters allowed for namespaces and keys.
+pub const KVSTORE_NAMESPACE_KEY_ALPHABET: &str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-";
+
+/// The maximum number of characters namespaces and keys may have.
+pub const KVSTORE_NAMESPACE_KEY_MAX_LEN: usize = 120;
+
+/// The namespace under which the [`ChannelManager`] will be persisted.
+pub const CHANNEL_MANAGER_PERSISTENCE_NAMESPACE: &str = "";
+/// The sub-namespace under which the [`ChannelManager`] will be persisted.
+pub const CHANNEL_MANAGER_PERSISTENCE_SUB_NAMESPACE: &str = "";
+/// The key under which the [`ChannelManager`] will be persisted.
+pub const CHANNEL_MANAGER_PERSISTENCE_KEY: &str = "manager";
+
+/// The namespace under which [`ChannelMonitor`]s will be persisted.
+pub const CHANNEL_MONITOR_PERSISTENCE_NAMESPACE: &str = "monitors";
+/// The sub-namespace under which [`ChannelMonitor`]s will be persisted.
+pub const CHANNEL_MONITOR_PERSISTENCE_SUB_NAMESPACE: &str = "";
+/// The namespace under which [`ChannelMonitorUpdate`]s will be persisted.
+pub const CHANNEL_MONITOR_UPDATE_PERSISTENCE_NAMESPACE: &str = "monitor_updates";
+
+/// The namespace under which the [`NetworkGraph`] will be persisted.
+pub const NETWORK_GRAPH_PERSISTENCE_NAMESPACE: &str = "";
+/// The sub-namespace under which the [`NetworkGraph`] will be persisted.
+pub const NETWORK_GRAPH_PERSISTENCE_SUB_NAMESPACE: &str = "";
+/// The key under which the [`NetworkGraph`] will be persisted.
+pub const NETWORK_GRAPH_PERSISTENCE_KEY: &str = "network_graph";
+
+/// The namespace under which the [`WriteableScore`] will be persisted.
+pub const SCORER_PERSISTENCE_NAMESPACE: &str = "";
+/// The sub-namespace under which the [`WriteableScore`] will be persisted.
+pub const SCORER_PERSISTENCE_SUB_NAMESPACE: &str = "";
+/// The key under which the [`WriteableScore`] will be persisted.
+pub const SCORER_PERSISTENCE_KEY: &str = "scorer";
+
+/// A sentinel value to be prepended to monitors persisted by the [`MonitorUpdatingPersister`].
+///
+/// This serves to prevent someone from accidentally loading such monitors (which may need
+/// updates applied to be current) with another implementation.
+pub const MONITOR_UPDATING_PERSISTER_PREPEND_SENTINEL: &[u8] = &[0xFF; 2];
+
+/// Provides an interface that allows storage and retrieval of persisted values that are associated
+/// with given keys.
+///
+/// In order to avoid collisions the key space is segmented based on the given `namespace`s and
+/// `sub_namespace`s. Implementations of this trait are free to handle them in different ways, as
+/// long as per-namespace key uniqueness is asserted.
+///
+/// Keys and namespaces are required to be valid ASCII strings in the range of
+/// [`KVSTORE_NAMESPACE_KEY_ALPHABET`] and no longer than [`KVSTORE_NAMESPACE_KEY_MAX_LEN`]. Empty
+/// namespaces and sub-namespaces (`""`) are assumed to be a valid, however, if `namespace` is
+/// empty, `sub_namespace` is required to be empty, too. This means that concerns should always be
+/// separated by namespace first, before sub-namespaces are used. While the number of namespaces
+/// will be relatively small and is determined at compile time, there may be many sub-namespaces
+/// per namespace. Note that per-namespace uniqueness needs to also hold for keys *and*
+/// namespaces/sub-namespaces in any given namespace/sub-namespace, i.e., conflicts between keys
+/// and equally named namespaces/sub-namespaces must be avoided.
+///
+/// **Note:** Users migrating custom persistence backends from the pre-v0.0.117 `KVStorePersister`
+/// interface can use a concatenation of `[{namespace}/[{sub_namespace}/]]{key}` to recover a `key` compatible with the
+/// data model previously assumed by `KVStorePersister::persist`.
+pub trait KVStore {
+ /// Returns the data stored for the given `namespace`, `sub_namespace`, and `key`.
+ ///
+ /// Returns an [`ErrorKind::NotFound`] if the given `key` could not be found in the given
+ /// `namespace` and `sub_namespace`.
+ ///
+ /// [`ErrorKind::NotFound`]: io::ErrorKind::NotFound
+ fn read(&self, namespace: &str, sub_namespace: &str, key: &str) -> io::Result<Vec<u8>>;
+ /// Persists the given data under the given `key`.
+ ///
+ /// Will create the given `namespace` and `sub_namespace` if not already present in the store.
+ fn write(&self, namespace: &str, sub_namespace: &str, key: &str, buf: &[u8]) -> io::Result<()>;
+ /// Removes any data that had previously been persisted under the given `key`.
+ ///
+ /// If the `lazy` flag is set to `true`, the backend implementation might choose to lazily
+ /// remove the given `key` at some point in time after the method returns, e.g., as part of an
+ /// eventual batch deletion of multiple keys. As a consequence, subsequent calls to
+ /// [`KVStore::list`] might include the removed key until the changes are actually persisted.
+ ///
+ /// Note that while setting the `lazy` flag reduces the I/O burden of multiple subsequent
+ /// `remove` calls, it also influences the atomicity guarantees as lazy `remove`s could
+ /// potentially get lost on crash after the method returns. Therefore, this flag should only be
+ /// set for `remove` operations that can be safely replayed at a later time.
+ ///
+ /// Returns successfully if no data will be stored for the given `namespace`, `sub_namespace`, and
+ /// `key`, independently of whether it was present before its invokation or not.
+ fn remove(&self, namespace: &str, sub_namespace: &str, key: &str, lazy: bool) -> io::Result<()>;
+ /// Returns a list of keys that are stored under the given `sub_namespace` in `namespace`.
+ ///
+ /// Returns the keys in arbitrary order, so users requiring a particular order need to sort the
+ /// returned keys. Returns an empty list if `namespace` or `sub_namespace` is unknown.
+ fn list(&self, namespace: &str, sub_namespace: &str) -> io::Result<Vec<String>>;
}
/// Trait that handles persisting a [`ChannelManager`], [`NetworkGraph`], and [`WriteableScore`] to disk.
fn persist_scorer(&self, scorer: &S) -> Result<(), io::Error>;
}
-impl<'a, A: KVStorePersister, M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref, S: WriteableScore<'a>> Persister<'a, M, T, ES, NS, SP, F, R, L, S> for A
+
+impl<'a, A: KVStore, M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref, S: WriteableScore<'a>> Persister<'a, M, T, ES, NS, SP, F, R, L, S> for A
where M::Target: 'static + chain::Watch<<SP::Target as SignerProvider>::Signer>,
T::Target: 'static + BroadcasterInterface,
ES::Target: 'static + EntropySource,
R::Target: 'static + Router,
L::Target: 'static + Logger,
{
- /// Persist the given ['ChannelManager'] to disk with the name "manager", returning an error if persistence failed.
+ /// Persist the given [`ChannelManager`] to disk, returning an error if persistence failed.
fn persist_manager(&self, channel_manager: &ChannelManager<M, T, ES, NS, SP, F, R, L>) -> Result<(), io::Error> {
- self.persist("manager", channel_manager)
+ self.write(CHANNEL_MANAGER_PERSISTENCE_NAMESPACE,
+ CHANNEL_MANAGER_PERSISTENCE_SUB_NAMESPACE,
+ CHANNEL_MANAGER_PERSISTENCE_KEY,
+ &channel_manager.encode())
}
- /// Persist the given [`NetworkGraph`] to disk with the name "network_graph", returning an error if persistence failed.
+ /// Persist the given [`NetworkGraph`] to disk, returning an error if persistence failed.
fn persist_graph(&self, network_graph: &NetworkGraph<L>) -> Result<(), io::Error> {
- self.persist("network_graph", network_graph)
+ self.write(NETWORK_GRAPH_PERSISTENCE_NAMESPACE,
+ NETWORK_GRAPH_PERSISTENCE_SUB_NAMESPACE,
+ NETWORK_GRAPH_PERSISTENCE_KEY,
+ &network_graph.encode())
}
- /// Persist the given [`WriteableScore`] to disk with name "scorer", returning an error if persistence failed.
+ /// Persist the given [`WriteableScore`] to disk, returning an error if persistence failed.
fn persist_scorer(&self, scorer: &S) -> Result<(), io::Error> {
- self.persist("scorer", &scorer)
+ self.write(SCORER_PERSISTENCE_NAMESPACE,
+ SCORER_PERSISTENCE_SUB_NAMESPACE,
+ SCORER_PERSISTENCE_KEY,
+ &scorer.encode())
}
}
-impl<ChannelSigner: WriteableEcdsaChannelSigner, K: KVStorePersister> Persist<ChannelSigner> for K {
+impl<ChannelSigner: WriteableEcdsaChannelSigner, K: KVStore> Persist<ChannelSigner> for K {
// TODO: We really need a way for the persister to inform the user that its time to crash/shut
// down once these start returning failure.
- // A PermanentFailure implies we should probably just shut down the node since we're
- // force-closing channels without even broadcasting!
+ // Then we should return InProgress rather than UnrecoverableError, implying we should probably
+ // just shut down the node since we're not retrying persistence!
fn persist_new_channel(&self, funding_txo: OutPoint, monitor: &ChannelMonitor<ChannelSigner>, _update_id: MonitorUpdateId) -> chain::ChannelMonitorUpdateStatus {
- let key = format!("monitors/{}_{}", funding_txo.txid.to_hex(), funding_txo.index);
- match self.persist(&key, monitor) {
+ let key = format!("{}_{}", funding_txo.txid.to_hex(), funding_txo.index);
+ match self.write(
+ CHANNEL_MONITOR_PERSISTENCE_NAMESPACE,
+ CHANNEL_MONITOR_PERSISTENCE_SUB_NAMESPACE,
+ &key, &monitor.encode())
+ {
Ok(()) => chain::ChannelMonitorUpdateStatus::Completed,
- Err(_) => chain::ChannelMonitorUpdateStatus::PermanentFailure,
+ Err(_) => chain::ChannelMonitorUpdateStatus::UnrecoverableError
}
}
fn update_persisted_channel(&self, funding_txo: OutPoint, _update: Option<&ChannelMonitorUpdate>, monitor: &ChannelMonitor<ChannelSigner>, _update_id: MonitorUpdateId) -> chain::ChannelMonitorUpdateStatus {
- let key = format!("monitors/{}_{}", funding_txo.txid.to_hex(), funding_txo.index);
- match self.persist(&key, monitor) {
+ let key = format!("{}_{}", funding_txo.txid.to_hex(), funding_txo.index);
+ match self.write(
+ CHANNEL_MONITOR_PERSISTENCE_NAMESPACE,
+ CHANNEL_MONITOR_PERSISTENCE_SUB_NAMESPACE,
+ &key, &monitor.encode())
+ {
Ok(()) => chain::ChannelMonitorUpdateStatus::Completed,
- Err(_) => chain::ChannelMonitorUpdateStatus::PermanentFailure,
+ Err(_) => chain::ChannelMonitorUpdateStatus::UnrecoverableError
+ }
+ }
+}
+
+/// Read previously persisted [`ChannelMonitor`]s from the store.
+pub fn read_channel_monitors<K: Deref, ES: Deref, SP: Deref>(
+ kv_store: K, entropy_source: ES, signer_provider: SP,
+) -> Result<Vec<(BlockHash, ChannelMonitor<<SP::Target as SignerProvider>::Signer>)>, io::Error>
+where
+ K::Target: KVStore,
+ ES::Target: EntropySource + Sized,
+ SP::Target: SignerProvider + Sized,
+{
+ let mut res = Vec::new();
+
+ for stored_key in kv_store.list(
+ CHANNEL_MONITOR_PERSISTENCE_NAMESPACE, CHANNEL_MONITOR_PERSISTENCE_SUB_NAMESPACE)?
+ {
+ if stored_key.len() < 66 {
+ return Err(io::Error::new(
+ io::ErrorKind::InvalidData,
+ "Stored key has invalid length"));
+ }
+
+ let txid = Txid::from_hex(stored_key.split_at(64).0).map_err(|_| {
+ io::Error::new(io::ErrorKind::InvalidData, "Invalid tx ID in stored key")
+ })?;
+
+ let index: u16 = stored_key.split_at(65).1.parse().map_err(|_| {
+ io::Error::new(io::ErrorKind::InvalidData, "Invalid tx index in stored key")
+ })?;
+
+ match <(BlockHash, ChannelMonitor<<SP::Target as SignerProvider>::Signer>)>::read(
+ &mut io::Cursor::new(
+ kv_store.read(CHANNEL_MONITOR_PERSISTENCE_NAMESPACE, CHANNEL_MONITOR_PERSISTENCE_SUB_NAMESPACE, &stored_key)?),
+ (&*entropy_source, &*signer_provider),
+ ) {
+ Ok((block_hash, channel_monitor)) => {
+ if channel_monitor.get_funding_txo().0.txid != txid
+ || channel_monitor.get_funding_txo().0.index != index
+ {
+ return Err(io::Error::new(
+ io::ErrorKind::InvalidData,
+ "ChannelMonitor was stored under the wrong key",
+ ));
+ }
+ res.push((block_hash, channel_monitor));
+ }
+ Err(_) => {
+ return Err(io::Error::new(
+ io::ErrorKind::InvalidData,
+ "Failed to read ChannelMonitor"
+ ))
+ }
+ }
+ }
+ Ok(res)
+}
+
+/// Implements [`Persist`] in a way that writes and reads both [`ChannelMonitor`]s and
+/// [`ChannelMonitorUpdate`]s.
+///
+/// # Overview
+///
+/// The main benefit this provides over the [`KVStore`]'s [`Persist`] implementation is decreased
+/// I/O bandwidth and storage churn, at the expense of more IOPS (including listing, reading, and
+/// deleting) and complexity. This is because it writes channel monitor differential updates,
+/// whereas the other (default) implementation rewrites the entire monitor on each update. For
+/// routing nodes, updates can happen many times per second to a channel, and monitors can be tens
+/// of megabytes (or more). Updates can be as small as a few hundred bytes.
+///
+/// Note that monitors written with `MonitorUpdatingPersister` are _not_ backward-compatible with
+/// the default [`KVStore`]'s [`Persist`] implementation. They have a prepended byte sequence,
+/// [`MONITOR_UPDATING_PERSISTER_PREPEND_SENTINEL`], applied to prevent deserialization with other
+/// persisters. This is because monitors written by this struct _may_ have unapplied updates. In
+/// order to downgrade, you must ensure that all updates are applied to the monitor, and remove the
+/// sentinel bytes.
+///
+/// # Storing monitors
+///
+/// Monitors are stored by implementing the [`Persist`] trait, which has two functions:
+///
+/// - [`Persist::persist_new_channel`], which persists whole [`ChannelMonitor`]s.
+/// - [`Persist::update_persisted_channel`], which persists only a [`ChannelMonitorUpdate`]
+///
+/// Whole [`ChannelMonitor`]s are stored in the [`CHANNEL_MONITOR_PERSISTENCE_NAMESPACE`], using the
+/// familiar encoding of an [`OutPoint`] (for example, `[SOME-64-CHAR-HEX-STRING]_1`).
+///
+/// Each [`ChannelMonitorUpdate`] is stored in a dynamic sub-namespace, as follows:
+///
+/// - namespace: [`CHANNEL_MONITOR_UPDATE_PERSISTENCE_NAMESPACE`]
+/// - sub-namespace: [the monitor's encoded outpoint name]
+///
+/// Under that sub-namespace, each update is stored with a number string, like `21`, which
+/// represents its `update_id` value.
+///
+/// For example, consider this channel, named for its transaction ID and index, or [`OutPoint`]:
+///
+/// - Transaction ID: `deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef`
+/// - Index: `1`
+///
+/// Full channel monitors would be stored at a single key:
+///
+/// `[CHANNEL_MONITOR_PERSISTENCE_NAMESPACE]/deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef_1`
+///
+/// Updates would be stored as follows (with `/` delimiting namespace/sub-namespace/key):
+///
+/// ```text
+/// [CHANNEL_MONITOR_UPDATE_PERSISTENCE_NAMESPACE]/deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef_1/1
+/// [CHANNEL_MONITOR_UPDATE_PERSISTENCE_NAMESPACE]/deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef_1/2
+/// [CHANNEL_MONITOR_UPDATE_PERSISTENCE_NAMESPACE]/deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef_1/3
+/// ```
+/// ... and so on.
+///
+/// # Reading channel state from storage
+///
+/// Channel state can be reconstructed by calling
+/// [`MonitorUpdatingPersister::read_all_channel_monitors_with_updates`]. Alternatively, users can
+/// list channel monitors themselves and load channels individually using
+/// [`MonitorUpdatingPersister::read_channel_monitor_with_updates`].
+///
+/// ## EXTREMELY IMPORTANT
+///
+/// It is extremely important that your [`KVStore::read`] implementation uses the
+/// [`io::ErrorKind::NotFound`] variant correctly: that is, when a file is not found, and _only_ in
+/// that circumstance (not when there is really a permissions error, for example). This is because
+/// neither channel monitor reading function lists updates. Instead, either reads the monitor, and
+/// using its stored `update_id`, synthesizes update storage keys, and tries them in sequence until
+/// one is not found. All _other_ errors will be bubbled up in the function's [`Result`].
+///
+/// # Pruning stale channel updates
+///
+/// Stale updates are pruned when a full monitor is written. The old monitor is first read, and if
+/// that succeeds, updates in the range between the old and new monitors are deleted. The `lazy`
+/// flag is used on the [`KVStore::remove`] method, so there are no guarantees that the deletions
+/// will complete. However, stale updates are not a problem for data integrity, since updates are
+/// only read that are higher than the stored [`ChannelMonitor`]'s `update_id`.
+///
+/// If you have many stale updates stored (such as after a crash with pending lazy deletes), and
+/// would like to get rid of them, consider using the
+/// [`MonitorUpdatingPersister::cleanup_stale_updates`] function.
+pub struct MonitorUpdatingPersister<K: Deref, L: Deref, ES: Deref, SP: Deref>
+where
+ K::Target: KVStore,
+ L::Target: Logger,
+ ES::Target: EntropySource + Sized,
+ SP::Target: SignerProvider + Sized,
+{
+ kv_store: K,
+ logger: L,
+ maximum_pending_updates: u64,
+ entropy_source: ES,
+ signer_provider: SP,
+}
+
+#[allow(dead_code)]
+impl<K: Deref, L: Deref, ES: Deref, SP: Deref>
+ MonitorUpdatingPersister<K, L, ES, SP>
+where
+ K::Target: KVStore,
+ L::Target: Logger,
+ ES::Target: EntropySource + Sized,
+ SP::Target: SignerProvider + Sized,
+{
+ /// Constructs a new [`MonitorUpdatingPersister`].
+ ///
+ /// The `maximum_pending_updates` parameter controls how many updates may be stored before a
+ /// [`MonitorUpdatingPersister`] consolidates updates by writing a full monitor. Note that
+ /// consolidation will frequently occur with fewer updates than what you set here; this number
+ /// is merely the maximum that may be stored. When setting this value, consider that for higher
+ /// values of `maximum_pending_updates`:
+ ///
+ /// - [`MonitorUpdatingPersister`] will tend to write more [`ChannelMonitorUpdate`]s than
+ /// [`ChannelMonitor`]s, approaching one [`ChannelMonitor`] write for every
+ /// `maximum_pending_updates` [`ChannelMonitorUpdate`]s.
+ /// - [`MonitorUpdatingPersister`] will issue deletes differently. Lazy deletes will come in
+ /// "waves" for each [`ChannelMonitor`] write. A larger `maximum_pending_updates` means bigger,
+ /// less frequent "waves."
+ /// - [`MonitorUpdatingPersister`] will potentially have more listing to do if you need to run
+ /// [`MonitorUpdatingPersister::cleanup_stale_updates`].
+ pub fn new(
+ kv_store: K, logger: L, maximum_pending_updates: u64, entropy_source: ES,
+ signer_provider: SP,
+ ) -> Self
+ where
+ ES::Target: EntropySource + Sized,
+ SP::Target: SignerProvider + Sized,
+ {
+ MonitorUpdatingPersister {
+ kv_store,
+ logger,
+ maximum_pending_updates,
+ entropy_source,
+ signer_provider,
+ }
+ }
+
+ /// Reads all stored channel monitors, along with any stored updates for them.
+ ///
+ /// It is extremely important that your [`KVStore::read`] implementation uses the
+ /// [`io::ErrorKind::NotFound`] variant correctly. For more information, please see the
+ /// documentation for [`MonitorUpdatingPersister`].
+ pub fn read_all_channel_monitors_with_updates<B: Deref, F: Deref + Clone>(
+ &self, broadcaster: B, fee_estimator: F,
+ ) -> Result<Vec<(BlockHash, ChannelMonitor<<SP::Target as SignerProvider>::Signer>)>, io::Error>
+ where
+ ES::Target: EntropySource + Sized,
+ SP::Target: SignerProvider + Sized,
+ B::Target: BroadcasterInterface,
+ F::Target: FeeEstimator,
+ {
+ let monitor_list = self.kv_store.list(
+ CHANNEL_MONITOR_PERSISTENCE_NAMESPACE,
+ CHANNEL_MONITOR_PERSISTENCE_SUB_NAMESPACE,
+ )?;
+ let mut res = Vec::with_capacity(monitor_list.len());
+ for monitor_key in monitor_list {
+ res.push(self.read_channel_monitor_with_updates(
+ &broadcaster,
+ fee_estimator.clone(),
+ monitor_key,
+ )?)
+ }
+ Ok(res)
+ }
+
+ /// Read a single channel monitor, along with any stored updates for it.
+ ///
+ /// It is extremely important that your [`KVStore::read`] implementation uses the
+ /// [`io::ErrorKind::NotFound`] variant correctly. For more information, please see the
+ /// documentation for [`MonitorUpdatingPersister`].
+ ///
+ /// For `monitor_key`, channel storage keys be the channel's transaction ID and index, or
+ /// [`OutPoint`], with an underscore `_` between them. For example, given:
+ ///
+ /// - Transaction ID: `deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef`
+ /// - Index: `1`
+ ///
+ /// The correct `monitor_key` would be:
+ /// `deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef_1`
+ ///
+ /// Loading a large number of monitors will be faster if done in parallel. You can use this
+ /// function to accomplish this. Take care to limit the number of parallel readers.
+ pub fn read_channel_monitor_with_updates<B: Deref, F: Deref + Clone>(
+ &self, broadcaster: &B, fee_estimator: F, monitor_key: String,
+ ) -> Result<(BlockHash, ChannelMonitor<<SP::Target as SignerProvider>::Signer>), io::Error>
+ where
+ ES::Target: EntropySource + Sized,
+ SP::Target: SignerProvider + Sized,
+ B::Target: BroadcasterInterface,
+ F::Target: FeeEstimator,
+ {
+ let monitor_name = MonitorName::new(monitor_key)?;
+ let (block_hash, monitor) = self.read_monitor(&monitor_name)?;
+ let mut current_update_id = monitor.get_latest_update_id();
+ loop {
+ current_update_id = match current_update_id.checked_add(1) {
+ Some(next_update_id) => next_update_id,
+ None => break,
+ };
+ let update_name = UpdateName::from(current_update_id);
+ let update = match self.read_monitor_update(&monitor_name, &update_name) {
+ Ok(update) => update,
+ Err(err) if err.kind() == io::ErrorKind::NotFound => {
+ // We can't find any more updates, so we are done.
+ break;
+ }
+ Err(err) => return Err(err),
+ };
+
+ monitor.update_monitor(&update, broadcaster, fee_estimator.clone(), &self.logger)
+ .map_err(|e| {
+ log_error!(
+ self.logger,
+ "Monitor update failed. monitor: {} update: {} reason: {:?}",
+ monitor_name.as_str(),
+ update_name.as_str(),
+ e
+ );
+ io::Error::new(io::ErrorKind::Other, "Monitor update failed")
+ })?;
+ }
+ Ok((block_hash, monitor))
+ }
+
+ /// Read a channel monitor.
+ fn read_monitor(
+ &self, monitor_name: &MonitorName,
+ ) -> Result<(BlockHash, ChannelMonitor<<SP::Target as SignerProvider>::Signer>), io::Error> {
+ let outpoint: OutPoint = monitor_name.try_into()?;
+ let mut monitor_cursor = io::Cursor::new(self.kv_store.read(
+ CHANNEL_MONITOR_PERSISTENCE_NAMESPACE,
+ CHANNEL_MONITOR_PERSISTENCE_SUB_NAMESPACE,
+ monitor_name.as_str(),
+ )?);
+ // Discard the sentinel bytes if found.
+ if monitor_cursor.get_ref().starts_with(MONITOR_UPDATING_PERSISTER_PREPEND_SENTINEL) {
+ monitor_cursor.set_position(MONITOR_UPDATING_PERSISTER_PREPEND_SENTINEL.len() as u64);
+ }
+ match <(BlockHash, ChannelMonitor<<SP::Target as SignerProvider>::Signer>)>::read(
+ &mut monitor_cursor,
+ (&*self.entropy_source, &*self.signer_provider),
+ ) {
+ Ok((blockhash, channel_monitor)) => {
+ if channel_monitor.get_funding_txo().0.txid != outpoint.txid
+ || channel_monitor.get_funding_txo().0.index != outpoint.index
+ {
+ log_error!(
+ self.logger,
+ "ChannelMonitor {} was stored under the wrong key!",
+ monitor_name.as_str()
+ );
+ Err(io::Error::new(
+ io::ErrorKind::InvalidData,
+ "ChannelMonitor was stored under the wrong key",
+ ))
+ } else {
+ Ok((blockhash, channel_monitor))
+ }
+ }
+ Err(e) => {
+ log_error!(
+ self.logger,
+ "Failed to read ChannelMonitor {}, reason: {}",
+ monitor_name.as_str(),
+ e,
+ );
+ Err(io::Error::new(io::ErrorKind::InvalidData, "Failed to read ChannelMonitor"))
+ }
+ }
+ }
+
+ /// Read a channel monitor update.
+ fn read_monitor_update(
+ &self, monitor_name: &MonitorName, update_name: &UpdateName,
+ ) -> Result<ChannelMonitorUpdate, io::Error> {
+ let update_bytes = self.kv_store.read(
+ CHANNEL_MONITOR_UPDATE_PERSISTENCE_NAMESPACE,
+ monitor_name.as_str(),
+ update_name.as_str(),
+ )?;
+ ChannelMonitorUpdate::read(&mut io::Cursor::new(update_bytes)).map_err(|e| {
+ log_error!(
+ self.logger,
+ "Failed to read ChannelMonitorUpdate {}/{}/{}, reason: {}",
+ CHANNEL_MONITOR_UPDATE_PERSISTENCE_NAMESPACE,
+ monitor_name.as_str(),
+ update_name.as_str(),
+ e,
+ );
+ io::Error::new(io::ErrorKind::InvalidData, "Failed to read ChannelMonitorUpdate")
+ })
+ }
+
+ /// Cleans up stale updates for all monitors.
+ ///
+ /// This function works by first listing all monitors, and then for each of them, listing all
+ /// updates. The updates that have an `update_id` less than or equal to than the stored monitor
+ /// are deleted. The deletion can either be lazy or non-lazy based on the `lazy` flag; this will
+ /// be passed to [`KVStore::remove`].
+ pub fn cleanup_stale_updates(&self, lazy: bool) -> Result<(), io::Error> {
+ let monitor_keys = self.kv_store.list(
+ CHANNEL_MONITOR_PERSISTENCE_NAMESPACE,
+ CHANNEL_MONITOR_PERSISTENCE_SUB_NAMESPACE,
+ )?;
+ for monitor_key in monitor_keys {
+ let monitor_name = MonitorName::new(monitor_key)?;
+ let (_, current_monitor) = self.read_monitor(&monitor_name)?;
+ let updates = self
+ .kv_store
+ .list(CHANNEL_MONITOR_UPDATE_PERSISTENCE_NAMESPACE, monitor_name.as_str())?;
+ for update in updates {
+ let update_name = UpdateName::new(update)?;
+ // if the update_id is lower than the stored monitor, delete
+ if update_name.0 <= current_monitor.get_latest_update_id() {
+ self.kv_store.remove(
+ CHANNEL_MONITOR_UPDATE_PERSISTENCE_NAMESPACE,
+ monitor_name.as_str(),
+ update_name.as_str(),
+ lazy,
+ )?;
+ }
+ }
+ }
+ Ok(())
+ }
+}
+
+impl<ChannelSigner: WriteableEcdsaChannelSigner, K: Deref, L: Deref, ES: Deref, SP: Deref>
+ Persist<ChannelSigner> for MonitorUpdatingPersister<K, L, ES, SP>
+where
+ K::Target: KVStore,
+ L::Target: Logger,
+ ES::Target: EntropySource + Sized,
+ SP::Target: SignerProvider + Sized,
+{
+ /// Persists a new channel. This means writing the entire monitor to the
+ /// parametrized [`KVStore`].
+ fn persist_new_channel(
+ &self, funding_txo: OutPoint, monitor: &ChannelMonitor<ChannelSigner>,
+ _monitor_update_call_id: MonitorUpdateId,
+ ) -> chain::ChannelMonitorUpdateStatus {
+ // Determine the proper key for this monitor
+ let monitor_name = MonitorName::from(funding_txo);
+ let maybe_old_monitor = self.read_monitor(&monitor_name);
+ match maybe_old_monitor {
+ Ok((_, ref old_monitor)) => {
+ // Check that this key isn't already storing a monitor with a higher update_id
+ // (collision)
+ if old_monitor.get_latest_update_id() > monitor.get_latest_update_id() {
+ log_error!(
+ self.logger,
+ "Tried to write a monitor at the same outpoint {} with a higher update_id!",
+ monitor_name.as_str()
+ );
+ return chain::ChannelMonitorUpdateStatus::UnrecoverableError;
+ }
+ }
+ // This means the channel monitor is new.
+ Err(ref e) if e.kind() == io::ErrorKind::NotFound => {}
+ _ => return chain::ChannelMonitorUpdateStatus::UnrecoverableError,
+ }
+ // Serialize and write the new monitor
+ let mut monitor_bytes = Vec::with_capacity(
+ MONITOR_UPDATING_PERSISTER_PREPEND_SENTINEL.len() + monitor.serialized_length(),
+ );
+ monitor_bytes.extend_from_slice(MONITOR_UPDATING_PERSISTER_PREPEND_SENTINEL);
+ monitor.write(&mut monitor_bytes).unwrap();
+ match self.kv_store.write(
+ CHANNEL_MONITOR_PERSISTENCE_NAMESPACE,
+ CHANNEL_MONITOR_PERSISTENCE_SUB_NAMESPACE,
+ monitor_name.as_str(),
+ &monitor_bytes,
+ ) {
+ Ok(_) => {
+ // Assess cleanup. Typically, we'll clean up only between the last two known full
+ // monitors.
+ if let Ok((_, old_monitor)) = maybe_old_monitor {
+ let start = old_monitor.get_latest_update_id();
+ let end = if monitor.get_latest_update_id() == CLOSED_CHANNEL_UPDATE_ID {
+ // We don't want to clean the rest of u64, so just do possible pending
+ // updates. Note that we never write updates at
+ // `CLOSED_CHANNEL_UPDATE_ID`.
+ cmp::min(
+ start.saturating_add(self.maximum_pending_updates),
+ CLOSED_CHANNEL_UPDATE_ID - 1,
+ )
+ } else {
+ monitor.get_latest_update_id().saturating_sub(1)
+ };
+ // We should bother cleaning up only if there's at least one update
+ // expected.
+ for update_id in start..=end {
+ let update_name = UpdateName::from(update_id);
+ #[cfg(debug_assertions)]
+ {
+ if let Ok(update) =
+ self.read_monitor_update(&monitor_name, &update_name)
+ {
+ // Assert that we are reading what we think we are.
+ debug_assert_eq!(update.update_id, update_name.0);
+ } else if update_id != start && monitor.get_latest_update_id() != CLOSED_CHANNEL_UPDATE_ID
+ {
+ // We're deleting something we should know doesn't exist.
+ panic!(
+ "failed to read monitor update {}",
+ update_name.as_str()
+ );
+ }
+ // On closed channels, we will unavoidably try to read
+ // non-existent updates since we have to guess at the range of
+ // stale updates, so do nothing.
+ }
+ if let Err(e) = self.kv_store.remove(
+ CHANNEL_MONITOR_UPDATE_PERSISTENCE_NAMESPACE,
+ monitor_name.as_str(),
+ update_name.as_str(),
+ true,
+ ) {
+ log_error!(
+ self.logger,
+ "error cleaning up channel monitor updates for monitor {}, reason: {}",
+ monitor_name.as_str(),
+ e
+ );
+ };
+ }
+ };
+ chain::ChannelMonitorUpdateStatus::Completed
+ }
+ Err(e) => {
+ log_error!(
+ self.logger,
+ "error writing channel monitor {}/{}/{} reason: {}",
+ CHANNEL_MONITOR_PERSISTENCE_NAMESPACE,
+ CHANNEL_MONITOR_PERSISTENCE_SUB_NAMESPACE,
+ monitor_name.as_str(),
+ e
+ );
+ chain::ChannelMonitorUpdateStatus::UnrecoverableError
+ }
+ }
+ }
+
+ /// Persists a channel update, writing only the update to the parameterized [`KVStore`] if possible.
+ ///
+ /// In some cases, this will forward to [`MonitorUpdatingPersister::persist_new_channel`]:
+ ///
+ /// - No full monitor is found in [`KVStore`]
+ /// - The number of pending updates exceeds `maximum_pending_updates` as given to [`Self::new`]
+ /// - LDK commands re-persisting the entire monitor through this function, specifically when
+ /// `update` is `None`.
+ /// - The update is at [`CLOSED_CHANNEL_UPDATE_ID`]
+ fn update_persisted_channel(
+ &self, funding_txo: OutPoint, update: Option<&ChannelMonitorUpdate>,
+ monitor: &ChannelMonitor<ChannelSigner>, monitor_update_call_id: MonitorUpdateId,
+ ) -> chain::ChannelMonitorUpdateStatus {
+ // IMPORTANT: monitor_update_call_id: MonitorUpdateId is not to be confused with
+ // ChannelMonitorUpdate's update_id.
+ if let Some(update) = update {
+ if update.update_id != CLOSED_CHANNEL_UPDATE_ID
+ && update.update_id % self.maximum_pending_updates != 0
+ {
+ let monitor_name = MonitorName::from(funding_txo);
+ let update_name = UpdateName::from(update.update_id);
+ match self.kv_store.write(
+ CHANNEL_MONITOR_UPDATE_PERSISTENCE_NAMESPACE,
+ monitor_name.as_str(),
+ update_name.as_str(),
+ &update.encode(),
+ ) {
+ Ok(()) => chain::ChannelMonitorUpdateStatus::Completed,
+ Err(e) => {
+ log_error!(
+ self.logger,
+ "error writing channel monitor update {}/{}/{} reason: {}",
+ CHANNEL_MONITOR_UPDATE_PERSISTENCE_NAMESPACE,
+ monitor_name.as_str(),
+ update_name.as_str(),
+ e
+ );
+ chain::ChannelMonitorUpdateStatus::UnrecoverableError
+ }
+ }
+ } else {
+ // We could write this update, but it meets criteria of our design that call for a full monitor write.
+ self.persist_new_channel(funding_txo, monitor, monitor_update_call_id)
+ }
+ } else {
+ // There is no update given, so we must persist a new monitor.
+ self.persist_new_channel(funding_txo, monitor, monitor_update_call_id)
+ }
+ }
+}
+
+/// A struct representing a name for a monitor.
+#[derive(Debug)]
+struct MonitorName(String);
+
+impl MonitorName {
+ /// Constructs a [`MonitorName`], after verifying that an [`OutPoint`] can
+ /// be formed from the given `name`.
+ pub fn new(name: String) -> Result<Self, io::Error> {
+ MonitorName::do_try_into_outpoint(&name)?;
+ Ok(Self(name))
+ }
+ /// Convert this monitor name to a str.
+ pub fn as_str(&self) -> &str {
+ &self.0
+ }
+ /// Attempt to form a valid [`OutPoint`] from a given name string.
+ fn do_try_into_outpoint(name: &str) -> Result<OutPoint, io::Error> {
+ let mut parts = name.splitn(2, '_');
+ let txid = if let Some(part) = parts.next() {
+ Txid::from_hex(part).map_err(|_| {
+ io::Error::new(io::ErrorKind::InvalidData, "Invalid tx ID in stored key")
+ })?
+ } else {
+ return Err(io::Error::new(
+ io::ErrorKind::InvalidData,
+ "Stored monitor key is not a splittable string",
+ ));
+ };
+ let index = if let Some(part) = parts.next() {
+ part.parse().map_err(|_| {
+ io::Error::new(io::ErrorKind::InvalidData, "Invalid tx index in stored key")
+ })?
+ } else {
+ return Err(io::Error::new(
+ io::ErrorKind::InvalidData,
+ "No tx index value found after underscore in stored key",
+ ));
+ };
+ Ok(OutPoint { txid, index })
+ }
+}
+
+impl TryFrom<&MonitorName> for OutPoint {
+ type Error = io::Error;
+
+ fn try_from(value: &MonitorName) -> Result<Self, io::Error> {
+ MonitorName::do_try_into_outpoint(&value.0)
+ }
+}
+
+impl From<OutPoint> for MonitorName {
+ fn from(value: OutPoint) -> Self {
+ MonitorName(format!("{}_{}", value.txid.to_hex(), value.index))
+ }
+}
+
+/// A struct representing a name for an update.
+#[derive(Debug)]
+struct UpdateName(u64, String);
+
+impl UpdateName {
+ /// Constructs an [`UpdateName`], after verifying that an update sequence ID
+ /// can be derived from the given `name`.
+ pub fn new(name: String) -> Result<Self, io::Error> {
+ match name.parse::<u64>() {
+ Ok(u) => Ok(u.into()),
+ Err(_) => {
+ Err(io::Error::new(io::ErrorKind::InvalidData, "cannot parse u64 from update name"))
+ }
}
}
+
+ /// Convert this monitor update name to a &str
+ pub fn as_str(&self) -> &str {
+ &self.1
+ }
+}
+
+impl From<u64> for UpdateName {
+ fn from(value: u64) -> Self {
+ Self(value, value.to_string())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::chain::chainmonitor::Persist;
+ use crate::chain::ChannelMonitorUpdateStatus;
+ use crate::events::{ClosureReason, MessageSendEventsProvider};
+ use crate::ln::functional_test_utils::*;
+ use crate::util::test_utils::{self, TestLogger, TestStore};
+ use crate::{check_added_monitors, check_closed_broadcast};
+
+ const EXPECTED_UPDATES_PER_PAYMENT: u64 = 5;
+
+ #[test]
+ fn converts_u64_to_update_name() {
+ assert_eq!(UpdateName::from(0).as_str(), "0");
+ assert_eq!(UpdateName::from(21).as_str(), "21");
+ assert_eq!(UpdateName::from(u64::MAX).as_str(), "18446744073709551615");
+ }
+
+ #[test]
+ fn bad_update_name_fails() {
+ assert!(UpdateName::new("deadbeef".to_string()).is_err());
+ assert!(UpdateName::new("-1".to_string()).is_err());
+ }
+
+ #[test]
+ fn monitor_from_outpoint_works() {
+ let monitor_name1 = MonitorName::from(OutPoint {
+ txid: Txid::from_hex("deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef").unwrap(),
+ index: 1,
+ });
+ assert_eq!(monitor_name1.as_str(), "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef_1");
+
+ let monitor_name2 = MonitorName::from(OutPoint {
+ txid: Txid::from_hex("f33dbeeff33dbeeff33dbeeff33dbeeff33dbeeff33dbeeff33dbeeff33dbeef").unwrap(),
+ index: u16::MAX,
+ });
+ assert_eq!(monitor_name2.as_str(), "f33dbeeff33dbeeff33dbeeff33dbeeff33dbeeff33dbeeff33dbeeff33dbeef_65535");
+ }
+
+ #[test]
+ fn bad_monitor_string_fails() {
+ assert!(MonitorName::new("deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef".to_string()).is_err());
+ assert!(MonitorName::new("deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef_65536".to_string()).is_err());
+ assert!(MonitorName::new("deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef_21".to_string()).is_err());
+ }
+
+ // Exercise the `MonitorUpdatingPersister` with real channels and payments.
+ #[test]
+ fn persister_with_real_monitors() {
+ // This value is used later to limit how many iterations we perform.
+ let test_max_pending_updates = 7;
+ let chanmon_cfgs = create_chanmon_cfgs(4);
+ let persister_0 = MonitorUpdatingPersister {
+ kv_store: &TestStore::new(false),
+ logger: &TestLogger::new(),
+ maximum_pending_updates: test_max_pending_updates,
+ entropy_source: &chanmon_cfgs[0].keys_manager,
+ signer_provider: &chanmon_cfgs[0].keys_manager,
+ };
+ let persister_1 = MonitorUpdatingPersister {
+ kv_store: &TestStore::new(false),
+ logger: &TestLogger::new(),
+ // Intentionally set this to a smaller value to test a different alignment.
+ maximum_pending_updates: 3,
+ entropy_source: &chanmon_cfgs[1].keys_manager,
+ signer_provider: &chanmon_cfgs[1].keys_manager,
+ };
+ let mut node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
+ let chain_mon_0 = test_utils::TestChainMonitor::new(
+ Some(&chanmon_cfgs[0].chain_source),
+ &chanmon_cfgs[0].tx_broadcaster,
+ &chanmon_cfgs[0].logger,
+ &chanmon_cfgs[0].fee_estimator,
+ &persister_0,
+ &chanmon_cfgs[0].keys_manager,
+ );
+ let chain_mon_1 = test_utils::TestChainMonitor::new(
+ Some(&chanmon_cfgs[1].chain_source),
+ &chanmon_cfgs[1].tx_broadcaster,
+ &chanmon_cfgs[1].logger,
+ &chanmon_cfgs[1].fee_estimator,
+ &persister_1,
+ &chanmon_cfgs[1].keys_manager,
+ );
+ node_cfgs[0].chain_monitor = chain_mon_0;
+ node_cfgs[1].chain_monitor = chain_mon_1;
+ let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
+ let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+
+ let broadcaster_0 = &chanmon_cfgs[2].tx_broadcaster;
+ let broadcaster_1 = &chanmon_cfgs[3].tx_broadcaster;
+
+ // Check that the persisted channel data is empty before any channels are
+ // open.
+ let mut persisted_chan_data_0 = persister_0.read_all_channel_monitors_with_updates(
+ broadcaster_0, &chanmon_cfgs[0].fee_estimator).unwrap();
+ assert_eq!(persisted_chan_data_0.len(), 0);
+ let mut persisted_chan_data_1 = persister_1.read_all_channel_monitors_with_updates(
+ broadcaster_1, &chanmon_cfgs[1].fee_estimator).unwrap();
+ assert_eq!(persisted_chan_data_1.len(), 0);
+
+ // Helper to make sure the channel is on the expected update ID.
+ macro_rules! check_persisted_data {
+ ($expected_update_id: expr) => {
+ persisted_chan_data_0 = persister_0.read_all_channel_monitors_with_updates(
+ broadcaster_0, &chanmon_cfgs[0].fee_estimator).unwrap();
+ // check that we stored only one monitor
+ assert_eq!(persisted_chan_data_0.len(), 1);
+ for (_, mon) in persisted_chan_data_0.iter() {
+ // check that when we read it, we got the right update id
+ assert_eq!(mon.get_latest_update_id(), $expected_update_id);
+ // if the CM is at the correct update id without updates, ensure no updates are stored
+ let monitor_name = MonitorName::from(mon.get_funding_txo().0);
+ let (_, cm_0) = persister_0.read_monitor(&monitor_name).unwrap();
+ if cm_0.get_latest_update_id() == $expected_update_id {
+ assert_eq!(
+ persister_0.kv_store.list(CHANNEL_MONITOR_UPDATE_PERSISTENCE_NAMESPACE,
+ monitor_name.as_str()).unwrap().len(),
+ 0,
+ "updates stored when they shouldn't be in persister 0"
+ );
+ }
+ }
+ persisted_chan_data_1 = persister_1.read_all_channel_monitors_with_updates(
+ broadcaster_1, &chanmon_cfgs[1].fee_estimator).unwrap();
+ assert_eq!(persisted_chan_data_1.len(), 1);
+ for (_, mon) in persisted_chan_data_1.iter() {
+ assert_eq!(mon.get_latest_update_id(), $expected_update_id);
+ let monitor_name = MonitorName::from(mon.get_funding_txo().0);
+ let (_, cm_1) = persister_1.read_monitor(&monitor_name).unwrap();
+ if cm_1.get_latest_update_id() == $expected_update_id {
+ assert_eq!(
+ persister_1.kv_store.list(CHANNEL_MONITOR_UPDATE_PERSISTENCE_NAMESPACE,
+ monitor_name.as_str()).unwrap().len(),
+ 0,
+ "updates stored when they shouldn't be in persister 1"
+ );
+ }
+ }
+ };
+ }
+
+ // Create some initial channel and check that a channel was persisted.
+ let _ = create_announced_chan_between_nodes(&nodes, 0, 1);
+ check_persisted_data!(0);
+
+ // Send a few payments and make sure the monitors are updated to the latest.
+ send_payment(&nodes[0], &vec![&nodes[1]][..], 8_000_000);
+ check_persisted_data!(EXPECTED_UPDATES_PER_PAYMENT);
+ send_payment(&nodes[1], &vec![&nodes[0]][..], 4_000_000);
+ check_persisted_data!(2 * EXPECTED_UPDATES_PER_PAYMENT);
+
+ // Send a few more payments to try all the alignments of max pending updates with
+ // updates for a payment sent and received.
+ let mut sender = 0;
+ for i in 3..=test_max_pending_updates * 2 {
+ let receiver;
+ if sender == 0 {
+ sender = 1;
+ receiver = 0;
+ } else {
+ sender = 0;
+ receiver = 1;
+ }
+ send_payment(&nodes[sender], &vec![&nodes[receiver]][..], 21_000);
+ check_persisted_data!(i * EXPECTED_UPDATES_PER_PAYMENT);
+ }
+
+ // Force close because cooperative close doesn't result in any persisted
+ // updates.
+ nodes[0].node.force_close_broadcasting_latest_txn(&nodes[0].node.list_channels()[0].channel_id, &nodes[1].node.get_our_node_id()).unwrap();
+
+ check_closed_event(&nodes[0], 1, ClosureReason::HolderForceClosed, false, &[nodes[1].node.get_our_node_id()], 100000);
+ check_closed_broadcast!(nodes[0], true);
+ check_added_monitors!(nodes[0], 1);
+
+ let node_txn = nodes[0].tx_broadcaster.txn_broadcast();
+ assert_eq!(node_txn.len(), 1);
+
+ connect_block(&nodes[1], &create_dummy_block(nodes[0].best_block_hash(), 42, vec![node_txn[0].clone(), node_txn[0].clone()]));
+
+ check_closed_broadcast!(nodes[1], true);
+ check_closed_event(&nodes[1], 1, ClosureReason::CommitmentTxConfirmed, false, &[nodes[0].node.get_our_node_id()], 100000);
+ check_added_monitors!(nodes[1], 1);
+
+ // Make sure everything is persisted as expected after close.
+ check_persisted_data!(CLOSED_CHANNEL_UPDATE_ID);
+
+ // Make sure the expected number of stale updates is present.
+ let persisted_chan_data = persister_0.read_all_channel_monitors_with_updates(broadcaster_0, &chanmon_cfgs[0].fee_estimator).unwrap();
+ let (_, monitor) = &persisted_chan_data[0];
+ let monitor_name = MonitorName::from(monitor.get_funding_txo().0);
+ // The channel should have 0 updates, as it wrote a full monitor and consolidated.
+ assert_eq!(persister_0.kv_store.list(CHANNEL_MONITOR_UPDATE_PERSISTENCE_NAMESPACE, monitor_name.as_str()).unwrap().len(), 0);
+ assert_eq!(persister_1.kv_store.list(CHANNEL_MONITOR_UPDATE_PERSISTENCE_NAMESPACE, monitor_name.as_str()).unwrap().len(), 0);
+ }
+
+ // Test that if the `MonitorUpdatingPersister`'s can't actually write, trying to persist a
+ // monitor or update with it results in the persister returning an UnrecoverableError status.
+ #[test]
+ fn unrecoverable_error_on_write_failure() {
+ // Set up a dummy channel and force close. This will produce a monitor
+ // that we can then use to test persistence.
+ let chanmon_cfgs = create_chanmon_cfgs(2);
+ let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
+ let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
+ let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+ let chan = create_announced_chan_between_nodes(&nodes, 0, 1);
+ nodes[1].node.force_close_broadcasting_latest_txn(&chan.2, &nodes[0].node.get_our_node_id()).unwrap();
+ check_closed_event(&nodes[1], 1, ClosureReason::HolderForceClosed, false, &[nodes[0].node.get_our_node_id()], 100000);
+ {
+ let mut added_monitors = nodes[1].chain_monitor.added_monitors.lock().unwrap();
+ let update_map = nodes[1].chain_monitor.latest_monitor_update_id.lock().unwrap();
+ let update_id = update_map.get(&added_monitors[0].0.to_channel_id()).unwrap();
+ let cmu_map = nodes[1].chain_monitor.monitor_updates.lock().unwrap();
+ let cmu = &cmu_map.get(&added_monitors[0].0.to_channel_id()).unwrap()[0];
+ let test_txo = OutPoint { txid: Txid::from_hex("8984484a580b825b9972d7adb15050b3ab624ccd731946b3eeddb92f4e7ef6be").unwrap(), index: 0 };
+
+ let ro_persister = MonitorUpdatingPersister {
+ kv_store: &TestStore::new(true),
+ logger: &TestLogger::new(),
+ maximum_pending_updates: 11,
+ entropy_source: node_cfgs[0].keys_manager,
+ signer_provider: node_cfgs[0].keys_manager,
+ };
+ match ro_persister.persist_new_channel(test_txo, &added_monitors[0].1, update_id.2) {
+ ChannelMonitorUpdateStatus::UnrecoverableError => {
+ // correct result
+ }
+ ChannelMonitorUpdateStatus::Completed => {
+ panic!("Completed persisting new channel when shouldn't have")
+ }
+ ChannelMonitorUpdateStatus::InProgress => {
+ panic!("Returned InProgress when shouldn't have")
+ }
+ }
+ match ro_persister.update_persisted_channel(test_txo, Some(cmu), &added_monitors[0].1, update_id.2) {
+ ChannelMonitorUpdateStatus::UnrecoverableError => {
+ // correct result
+ }
+ ChannelMonitorUpdateStatus::Completed => {
+ panic!("Completed persisting new channel when shouldn't have")
+ }
+ ChannelMonitorUpdateStatus::InProgress => {
+ panic!("Returned InProgress when shouldn't have")
+ }
+ }
+ added_monitors.clear();
+ }
+ nodes[1].node.get_and_clear_pending_msg_events();
+ }
+
+ // Confirm that the `clean_stale_updates` function finds and deletes stale updates.
+ #[test]
+ fn clean_stale_updates_works() {
+ let test_max_pending_updates = 7;
+ let chanmon_cfgs = create_chanmon_cfgs(3);
+ let persister_0 = MonitorUpdatingPersister {
+ kv_store: &TestStore::new(false),
+ logger: &TestLogger::new(),
+ maximum_pending_updates: test_max_pending_updates,
+ entropy_source: &chanmon_cfgs[0].keys_manager,
+ signer_provider: &chanmon_cfgs[0].keys_manager,
+ };
+ let persister_1 = MonitorUpdatingPersister {
+ kv_store: &TestStore::new(false),
+ logger: &TestLogger::new(),
+ maximum_pending_updates: test_max_pending_updates,
+ entropy_source: &chanmon_cfgs[1].keys_manager,
+ signer_provider: &chanmon_cfgs[1].keys_manager,
+ };
+ let mut node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
+ let chain_mon_0 = test_utils::TestChainMonitor::new(
+ Some(&chanmon_cfgs[0].chain_source),
+ &chanmon_cfgs[0].tx_broadcaster,
+ &chanmon_cfgs[0].logger,
+ &chanmon_cfgs[0].fee_estimator,
+ &persister_0,
+ &chanmon_cfgs[0].keys_manager,
+ );
+ let chain_mon_1 = test_utils::TestChainMonitor::new(
+ Some(&chanmon_cfgs[1].chain_source),
+ &chanmon_cfgs[1].tx_broadcaster,
+ &chanmon_cfgs[1].logger,
+ &chanmon_cfgs[1].fee_estimator,
+ &persister_1,
+ &chanmon_cfgs[1].keys_manager,
+ );
+ node_cfgs[0].chain_monitor = chain_mon_0;
+ node_cfgs[1].chain_monitor = chain_mon_1;
+ let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
+ let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+
+ let broadcaster_0 = &chanmon_cfgs[2].tx_broadcaster;
+
+ // Check that the persisted channel data is empty before any channels are
+ // open.
+ let persisted_chan_data = persister_0.read_all_channel_monitors_with_updates(broadcaster_0, &chanmon_cfgs[0].fee_estimator).unwrap();
+ assert_eq!(persisted_chan_data.len(), 0);
+
+ // Create some initial channel
+ let _ = create_announced_chan_between_nodes(&nodes, 0, 1);
+
+ // Send a few payments to advance the updates a bit
+ send_payment(&nodes[0], &vec![&nodes[1]][..], 8_000_000);
+ send_payment(&nodes[1], &vec![&nodes[0]][..], 4_000_000);
+
+ // Get the monitor and make a fake stale update at update_id=1 (lowest height of an update possible)
+ let persisted_chan_data = persister_0.read_all_channel_monitors_with_updates(broadcaster_0, &chanmon_cfgs[0].fee_estimator).unwrap();
+ let (_, monitor) = &persisted_chan_data[0];
+ let monitor_name = MonitorName::from(monitor.get_funding_txo().0);
+ persister_0
+ .kv_store
+ .write(CHANNEL_MONITOR_UPDATE_PERSISTENCE_NAMESPACE, monitor_name.as_str(), UpdateName::from(1).as_str(), &[0u8; 1])
+ .unwrap();
+
+ // Do the stale update cleanup
+ persister_0.cleanup_stale_updates(false).unwrap();
+
+ // Confirm the stale update is unreadable/gone
+ assert!(persister_0
+ .kv_store
+ .read(CHANNEL_MONITOR_UPDATE_PERSISTENCE_NAMESPACE, monitor_name.as_str(), UpdateName::from(1).as_str())
+ .is_err());
+
+ // Force close.
+ nodes[0].node.force_close_broadcasting_latest_txn(&nodes[0].node.list_channels()[0].channel_id, &nodes[1].node.get_our_node_id()).unwrap();
+ check_closed_event(&nodes[0], 1, ClosureReason::HolderForceClosed, false, &[nodes[1].node.get_our_node_id()], 100000);
+ check_closed_broadcast!(nodes[0], true);
+ check_added_monitors!(nodes[0], 1);
+
+ // Write an update near u64::MAX
+ persister_0
+ .kv_store
+ .write(CHANNEL_MONITOR_UPDATE_PERSISTENCE_NAMESPACE, monitor_name.as_str(), UpdateName::from(u64::MAX - 1).as_str(), &[0u8; 1])
+ .unwrap();
+
+ // Do the stale update cleanup
+ persister_0.cleanup_stale_updates(false).unwrap();
+
+ // Confirm the stale update is unreadable/gone
+ assert!(persister_0
+ .kv_store
+ .read(CHANNEL_MONITOR_UPDATE_PERSISTENCE_NAMESPACE, monitor_name.as_str(), UpdateName::from(u64::MAX - 1).as_str())
+ .is_err());
+ }
}
/// encoded in several different ways, which we must check for at deserialization-time. Thus, if
/// you're looking for an example of a variable-length integer to use for your own project, move
/// along, this is a rather poor design.
+#[derive(Clone, Copy, Debug, Hash, PartialOrd, Ord, PartialEq, Eq)]
pub struct BigSize(pub u64);
impl Writeable for BigSize {
#[inline]
}
}
-// u8 arrays
macro_rules! impl_array {
- ( $size:expr ) => (
- impl Writeable for [u8; $size]
- {
+ ($size:expr, $ty: ty) => (
+ impl Writeable for [$ty; $size] {
#[inline]
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
- w.write_all(self)
+ let mut out = [0; $size * core::mem::size_of::<$ty>()];
+ for (idx, v) in self.iter().enumerate() {
+ let startpos = idx * core::mem::size_of::<$ty>();
+ out[startpos..startpos + core::mem::size_of::<$ty>()].copy_from_slice(&v.to_be_bytes());
+ }
+ w.write_all(&out)
}
}
- impl Readable for [u8; $size]
- {
+ impl Readable for [$ty; $size] {
#[inline]
fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
- let mut buf = [0u8; $size];
+ let mut buf = [0u8; $size * core::mem::size_of::<$ty>()];
r.read_exact(&mut buf)?;
- Ok(buf)
+ let mut res = [0; $size];
+ for (idx, v) in res.iter_mut().enumerate() {
+ let startpos = idx * core::mem::size_of::<$ty>();
+ let mut arr = [0; core::mem::size_of::<$ty>()];
+ arr.copy_from_slice(&buf[startpos..startpos + core::mem::size_of::<$ty>()]);
+ *v = <$ty>::from_be_bytes(arr);
+ }
+ Ok(res)
}
}
);
}
-impl_array!(3); // for rgb, ISO 4712 code
-impl_array!(4); // for IPv4
-impl_array!(12); // for OnionV2
-impl_array!(16); // for IPv6
-impl_array!(32); // for channel id & hmac
-impl_array!(PUBLIC_KEY_SIZE); // for PublicKey
-impl_array!(64); // for ecdsa::Signature and schnorr::Signature
-impl_array!(66); // for MuSig2 nonces
-impl_array!(1300); // for OnionPacket.hop_data
+impl_array!(3, u8); // for rgb, ISO 4712 code
+impl_array!(4, u8); // for IPv4
+impl_array!(12, u8); // for OnionV2
+impl_array!(16, u8); // for IPv6
+impl_array!(32, u8); // for channel id & hmac
+impl_array!(PUBLIC_KEY_SIZE, u8); // for PublicKey
+impl_array!(64, u8); // for ecdsa::Signature and schnorr::Signature
+impl_array!(66, u8); // for MuSig2 nonces
+impl_array!(1300, u8); // for OnionPacket.hop_data
-impl Writeable for [u16; 8] {
- #[inline]
- fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
- for v in self.iter() {
- w.write_all(&v.to_be_bytes())?
- }
- Ok(())
- }
-}
-
-impl Readable for [u16; 8] {
- #[inline]
- fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
- let mut buf = [0u8; 16];
- r.read_exact(&mut buf)?;
- let mut res = [0u16; 8];
- for (idx, v) in res.iter_mut().enumerate() {
- *v = (buf[idx*2] as u16) << 8 | (buf[idx*2 + 1] as u16)
- }
- Ok(res)
- }
-}
+impl_array!(8, u16);
+impl_array!(32, u16);
/// A type for variable-length values within TLV record where the length is encoded as part of the record.
/// Used to prevent encoding the length twice.
}
}
+/// This is not exported to bindings users as `Duration`s are simply mapped as ints.
impl Writeable for Duration {
#[inline]
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
self.subsec_nanos().write(w)
}
}
+/// This is not exported to bindings users as `Duration`s are simply mapped as ints.
impl Readable for Duration {
#[inline]
fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
//! [`Readable`]: crate::util::ser::Readable
//! [`Writeable`]: crate::util::ser::Writeable
+// There are quite a few TLV serialization "types" which behave differently. We currently only
+// publicly document the `optional` and `required` types, not supporting anything else publicly and
+// changing them at will.
+//
+// Some of the other types include:
+// * (default_value, $default) - reads optionally, reading $default if no TLV is present
+// * (static_value, $value) - ignores any TLVs, always using $value
+// * required_vec - reads into a Vec without a length prefix, failing if no TLV is present.
+// * optional_vec - reads into an Option<Vec> without a length prefix, continuing if no TLV is
+// present. Writes from a Vec directly, only if any elements are present. Note
+// that the struct deserialization macros return a Vec, not an Option.
+// * upgradable_option - reads via MaybeReadable.
+// * upgradable_required - reads via MaybeReadable, requiring a TLV be present but may return None
+// if MaybeReadable::read() returns None.
+
/// Implements serialization for a single TLV record.
/// This is exported for use by other exported macros, do not use directly.
#[doc(hidden)]
BigSize($field.serialized_length() as u64).write($stream)?;
$field.write($stream)?;
};
- ($stream: expr, $type: expr, $field: expr, vec_type) => {
+ ($stream: expr, $type: expr, $field: expr, required_vec) => {
$crate::_encode_tlv!($stream, $type, $crate::util::ser::WithoutLength(&$field), required);
};
($stream: expr, $optional_type: expr, $optional_field: expr, option) => {
};
($stream: expr, $type: expr, $field: expr, optional_vec) => {
if !$field.is_empty() {
- $crate::_encode_tlv!($stream, $type, $field, vec_type);
+ $crate::_encode_tlv!($stream, $type, $field, required_vec);
}
};
($stream: expr, $type: expr, $field: expr, upgradable_required) => {
/// [`Writer`]: crate::util::ser::Writer
#[macro_export]
macro_rules! encode_tlv_stream {
+ ($stream: expr, {$(($type: expr, $field: expr, $fieldty: tt)),* $(,)*}) => {
+ $crate::_encode_tlv_stream!($stream, {$(($type, $field, $fieldty)),*})
+ }
+}
+
+/// Implementation of [`encode_tlv_stream`].
+/// This is exported for use by other exported macros, do not use directly.
+#[doc(hidden)]
+#[macro_export]
+macro_rules! _encode_tlv_stream {
($stream: expr, {$(($type: expr, $field: expr, $fieldty: tt)),* $(,)*}) => { {
+ $crate::_encode_tlv_stream!($stream, { $(($type, $field, $fieldty)),* }, &[])
+ } };
+ ($stream: expr, {$(($type: expr, $field: expr, $fieldty: tt)),* $(,)*}, $extra_tlvs: expr) => { {
#[allow(unused_imports)]
use $crate::{
ln::msgs::DecodeError,
$(
$crate::_encode_tlv!($stream, $type, $field, $fieldty);
)*
+ for tlv in $extra_tlvs {
+ let (typ, value): &(u64, Vec<u8>) = tlv;
+ $crate::_encode_tlv!($stream, *typ, *value, required_vec);
+ }
#[allow(unused_mut, unused_variables, unused_assignments)]
#[cfg(debug_assertions)]
$(
$crate::_check_encoded_tlv_order!(last_seen, $type, $fieldty);
)*
+ for tlv in $extra_tlvs {
+ let (typ, _): &(u64, Vec<u8>) = tlv;
+ $crate::_check_encoded_tlv_order!(last_seen, *typ, required_vec);
+ }
}
- } }
+ } };
}
/// Adds the length of the serialized field to a [`LengthCalculatingWriter`].
BigSize(field_len as u64).write(&mut $len).expect("No in-memory data may fail to serialize");
$len.0 += field_len;
};
- ($len: expr, $type: expr, $field: expr, vec_type) => {
+ ($len: expr, $type: expr, $field: expr, required_vec) => {
$crate::_get_varint_length_prefixed_tlv_length!($len, $type, $crate::util::ser::WithoutLength(&$field), required);
};
($len: expr, $optional_type: expr, $optional_field: expr, option) => {
};
($len: expr, $type: expr, $field: expr, optional_vec) => {
if !$field.is_empty() {
- $crate::_get_varint_length_prefixed_tlv_length!($len, $type, $field, vec_type);
+ $crate::_get_varint_length_prefixed_tlv_length!($len, $type, $field, required_vec);
}
};
($len: expr, $type: expr, $field: expr, (option: $trait: ident $(, $read_arg: expr)?)) => {
#[macro_export]
macro_rules! _encode_varint_length_prefixed_tlv {
($stream: expr, {$(($type: expr, $field: expr, $fieldty: tt)),*}) => { {
+ $crate::_encode_varint_length_prefixed_tlv!($stream, {$(($type, $field, $fieldty)),*}, &[])
+ } };
+ ($stream: expr, {$(($type: expr, $field: expr, $fieldty: tt)),*}, $extra_tlvs: expr) => { {
+ extern crate alloc;
use $crate::util::ser::BigSize;
+ use alloc::vec::Vec;
let len = {
#[allow(unused_mut)]
let mut len = $crate::util::ser::LengthCalculatingWriter(0);
$(
$crate::_get_varint_length_prefixed_tlv_length!(len, $type, $field, $fieldty);
)*
+ for tlv in $extra_tlvs {
+ let (typ, value): &(u64, Vec<u8>) = tlv;
+ $crate::_get_varint_length_prefixed_tlv_length!(len, *typ, *value, required_vec);
+ }
len.0
};
BigSize(len as u64).write($stream)?;
- $crate::encode_tlv_stream!($stream, { $(($type, $field, $fieldty)),* });
- } }
+ $crate::_encode_tlv_stream!($stream, { $(($type, $field, $fieldty)),* }, $extra_tlvs);
+ } };
}
/// Errors if there are missing required TLV types between the last seen type and the type currently being processed.
($last_seen_type: expr, $typ: expr, $type: expr, $field: ident, option) => {{
// no-op
}};
- ($last_seen_type: expr, $typ: expr, $type: expr, $field: ident, vec_type) => {{
- // no-op
+ ($last_seen_type: expr, $typ: expr, $type: expr, $field: ident, required_vec) => {{
+ $crate::_check_decoded_tlv_order!($last_seen_type, $typ, $type, $field, required);
}};
($last_seen_type: expr, $typ: expr, $type: expr, $field: ident, optional_vec) => {{
// no-op
($last_seen_type: expr, $type: expr, $field: ident, (required: $trait: ident $(, $read_arg: expr)?)) => {{
$crate::_check_missing_tlv!($last_seen_type, $type, $field, required);
}};
- ($last_seen_type: expr, $type: expr, $field: ident, vec_type) => {{
- // no-op
+ ($last_seen_type: expr, $type: expr, $field: ident, required_vec) => {{
+ $crate::_check_missing_tlv!($last_seen_type, $type, $field, required);
}};
($last_seen_type: expr, $type: expr, $field: ident, option) => {{
// no-op
($reader: expr, $field: ident, (required: $trait: ident $(, $read_arg: expr)?)) => {{
$field = $trait::read(&mut $reader $(, $read_arg)*)?;
}};
- ($reader: expr, $field: ident, vec_type) => {{
+ ($reader: expr, $field: ident, required_vec) => {{
let f: $crate::util::ser::WithoutLength<Vec<_>> = $crate::util::ser::Readable::read(&mut $reader)?;
- $field = Some(f.0);
+ $field = f.0;
}};
($reader: expr, $field: ident, option) => {{
$field = Some($crate::util::ser::Readable::read(&mut $reader)?);
}};
($reader: expr, $field: ident, optional_vec) => {{
- $crate::_decode_tlv!($reader, $field, vec_type);
+ let f: $crate::util::ser::WithoutLength<Vec<_>> = $crate::util::ser::Readable::read(&mut $reader)?;
+ $field = Some(f.0);
}};
// `upgradable_required` indicates we're reading a required TLV that may have been upgraded
// without backwards compat. We'll error if the field is missing, and return `Ok(None)` if the
($field: ident, required) => {
$field.0.unwrap()
};
- ($field: ident, vec_type) => {
- $field.unwrap()
+ ($field: ident, required_vec) => {
+ $field
};
($field: ident, optional_vec) => {
$field.unwrap()
($field: ident, (required: $trait: ident $(, $read_arg: expr)?)) => {
$crate::_init_tlv_field_var!($field, required);
};
- ($field: ident, vec_type) => {
- let mut $field = Some(Vec::new());
+ ($field: ident, required_vec) => {
+ let mut $field = Vec::new();
};
($field: ident, option) => {
let mut $field = None;
/// Equivalent to running [`_init_tlv_field_var`] then [`read_tlv_fields`].
///
+/// If any unused values are read, their type MUST be specified or else `rustc` will read them as an
+/// `i64`.
+///
/// This is exported for use by other exported macros, do not use directly.
#[doc(hidden)]
#[macro_export]
-macro_rules! _init_and_read_tlv_fields {
+macro_rules! _init_and_read_len_prefixed_tlv_fields {
($reader: ident, {$(($type: expr, $field: ident, $fieldty: tt)),* $(,)*}) => {
$(
$crate::_init_tlv_field_var!($field, $fieldty);
}
}
+/// Equivalent to running [`_init_tlv_field_var`] then [`decode_tlv_stream`].
+///
+/// If any unused values are read, their type MUST be specified or else `rustc` will read them as an
+/// `i64`.
+macro_rules! _init_and_read_tlv_stream {
+ ($reader: ident, {$(($type: expr, $field: ident, $fieldty: tt)),* $(,)*}) => {
+ $(
+ $crate::_init_tlv_field_var!($field, $fieldty);
+ )*
+
+ $crate::decode_tlv_stream!($reader, {
+ $(($type, $field, $fieldty)),*
+ });
+ }
+}
+
/// Implements [`Readable`]/[`Writeable`] for a struct storing it as a set of TLVs
/// If `$fieldty` is `required`, then `$field` is a required field that is not an [`Option`] nor a [`Vec`].
/// If `$fieldty` is `(default_value, $default)`, then `$field` will be set to `$default` if not present.
impl $crate::util::ser::Readable for $st {
fn read<R: $crate::io::Read>(reader: &mut R) -> Result<Self, $crate::ln::msgs::DecodeError> {
- $crate::_init_and_read_tlv_fields!(reader, {
+ $crate::_init_and_read_len_prefixed_tlv_fields!(reader, {
$(($type, $field, $fieldty)),*
});
Ok(Self {
// Because read_tlv_fields creates a labeled loop, we cannot call it twice
// in the same function body. Instead, we define a closure and call it.
let f = || {
- $crate::_init_and_read_tlv_fields!(reader, {
+ $crate::_init_and_read_len_prefixed_tlv_fields!(reader, {
$(($type, $field, $fieldty)),*
});
Ok($st::$variant_name {
f()
}),*
$($tuple_variant_id => {
- Ok($st::$tuple_variant_name(Readable::read(reader)?))
+ Ok($st::$tuple_variant_name($crate::util::ser::Readable::read(reader)?))
}),*
_ => {
Err($crate::ln::msgs::DecodeError::UnknownRequiredFeature)
// Because read_tlv_fields creates a labeled loop, we cannot call it twice
// in the same function body. Instead, we define a closure and call it.
let f = || {
- $crate::_init_and_read_tlv_fields!(reader, {
+ $crate::_init_and_read_len_prefixed_tlv_fields!(reader, {
$(($type, $field, $fieldty)),*
});
Ok(Some($st::$variant_name {
--- /dev/null
+// This file is Copyright its original authors, visible in version control
+// history.
+//
+// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
+// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// You may not use this file except in accordance with one or both of these
+// licenses.
+
+use crate::ln::channel::{ANCHOR_OUTPUT_VALUE_SATOSHI, MIN_CHAN_DUST_LIMIT_SATOSHIS};
+use crate::ln::chan_utils::{HTLCOutputInCommitment, ChannelPublicKeys, HolderCommitmentTransaction, CommitmentTransaction, ChannelTransactionParameters, TrustedCommitmentTransaction, ClosingTransaction};
+use crate::ln::{chan_utils, msgs, PaymentPreimage};
+use crate::sign::{WriteableEcdsaChannelSigner, InMemorySigner, ChannelSigner, EcdsaChannelSigner};
+
+use crate::prelude::*;
+use core::cmp;
+use crate::sync::{Mutex, Arc};
+#[cfg(test)] use crate::sync::MutexGuard;
+
+use bitcoin::blockdata::transaction::{Transaction, EcdsaSighashType};
+use bitcoin::util::sighash;
+
+use bitcoin::secp256k1;
+use bitcoin::secp256k1::{SecretKey, PublicKey};
+use bitcoin::secp256k1::{Secp256k1, ecdsa::Signature};
+use crate::events::bump_transaction::HTLCDescriptor;
+use crate::util::ser::{Writeable, Writer};
+use crate::io::Error;
+use crate::ln::features::ChannelTypeFeatures;
+
+/// Initial value for revoked commitment downward counter
+pub const INITIAL_REVOKED_COMMITMENT_NUMBER: u64 = 1 << 48;
+
+/// An implementation of Sign that enforces some policy checks. The current checks
+/// are an incomplete set. They include:
+///
+/// - When signing, the holder transaction has not been revoked
+/// - When revoking, the holder transaction has not been signed
+/// - The holder commitment number is monotonic and without gaps
+/// - The revoked holder commitment number is monotonic and without gaps
+/// - There is at least one unrevoked holder transaction at all times
+/// - The counterparty commitment number is monotonic and without gaps
+/// - The pre-derived keys and pre-built transaction in CommitmentTransaction were correctly built
+///
+/// Eventually we will probably want to expose a variant of this which would essentially
+/// be what you'd want to run on a hardware wallet.
+///
+/// Note that counterparty signatures on the holder transaction are not checked, but it should
+/// be in a complete implementation.
+///
+/// Note that before we do so we should ensure its serialization format has backwards- and
+/// forwards-compatibility prefix/suffixes!
+#[derive(Clone)]
+pub struct TestChannelSigner {
+ pub inner: InMemorySigner,
+ /// Channel state used for policy enforcement
+ pub state: Arc<Mutex<EnforcementState>>,
+ pub disable_revocation_policy_check: bool,
+}
+
+impl PartialEq for TestChannelSigner {
+ fn eq(&self, o: &Self) -> bool {
+ Arc::ptr_eq(&self.state, &o.state)
+ }
+}
+
+impl TestChannelSigner {
+ /// Construct an TestChannelSigner
+ pub fn new(inner: InMemorySigner) -> Self {
+ let state = Arc::new(Mutex::new(EnforcementState::new()));
+ Self {
+ inner,
+ state,
+ disable_revocation_policy_check: false
+ }
+ }
+
+ /// Construct an TestChannelSigner with externally managed storage
+ ///
+ /// Since there are multiple copies of this struct for each channel, some coordination is needed
+ /// so that all copies are aware of enforcement state. A pointer to this state is provided
+ /// here, usually by an implementation of KeysInterface.
+ pub fn new_with_revoked(inner: InMemorySigner, state: Arc<Mutex<EnforcementState>>, disable_revocation_policy_check: bool) -> Self {
+ Self {
+ inner,
+ state,
+ disable_revocation_policy_check
+ }
+ }
+
+ pub fn channel_type_features(&self) -> &ChannelTypeFeatures { self.inner.channel_type_features() }
+
+ #[cfg(test)]
+ pub fn get_enforcement_state(&self) -> MutexGuard<EnforcementState> {
+ self.state.lock().unwrap()
+ }
+}
+
+impl ChannelSigner for TestChannelSigner {
+ fn get_per_commitment_point(&self, idx: u64, secp_ctx: &Secp256k1<secp256k1::All>) -> PublicKey {
+ self.inner.get_per_commitment_point(idx, secp_ctx)
+ }
+
+ fn release_commitment_secret(&self, idx: u64) -> [u8; 32] {
+ {
+ let mut state = self.state.lock().unwrap();
+ assert!(idx == state.last_holder_revoked_commitment || idx == state.last_holder_revoked_commitment - 1, "can only revoke the current or next unrevoked commitment - trying {}, last revoked {}", idx, state.last_holder_revoked_commitment);
+ assert!(idx > state.last_holder_commitment, "cannot revoke the last holder commitment - attempted to revoke {} last commitment {}", idx, state.last_holder_commitment);
+ state.last_holder_revoked_commitment = idx;
+ }
+ self.inner.release_commitment_secret(idx)
+ }
+
+ fn validate_holder_commitment(&self, holder_tx: &HolderCommitmentTransaction, _preimages: Vec<PaymentPreimage>) -> Result<(), ()> {
+ let mut state = self.state.lock().unwrap();
+ let idx = holder_tx.commitment_number();
+ assert!(idx == state.last_holder_commitment || idx == state.last_holder_commitment - 1, "expecting to validate the current or next holder commitment - trying {}, current {}", idx, state.last_holder_commitment);
+ state.last_holder_commitment = idx;
+ Ok(())
+ }
+
+ fn pubkeys(&self) -> &ChannelPublicKeys { self.inner.pubkeys() }
+
+ fn channel_keys_id(&self) -> [u8; 32] { self.inner.channel_keys_id() }
+
+ fn provide_channel_parameters(&mut self, channel_parameters: &ChannelTransactionParameters) {
+ self.inner.provide_channel_parameters(channel_parameters)
+ }
+}
+
+impl EcdsaChannelSigner for TestChannelSigner {
+ fn sign_counterparty_commitment(&self, commitment_tx: &CommitmentTransaction, preimages: Vec<PaymentPreimage>, secp_ctx: &Secp256k1<secp256k1::All>) -> Result<(Signature, Vec<Signature>), ()> {
+ self.verify_counterparty_commitment_tx(commitment_tx, secp_ctx);
+
+ {
+ let mut state = self.state.lock().unwrap();
+ let actual_commitment_number = commitment_tx.commitment_number();
+ let last_commitment_number = state.last_counterparty_commitment;
+ // These commitment numbers are backwards counting. We expect either the same as the previously encountered,
+ // or the next one.
+ assert!(last_commitment_number == actual_commitment_number || last_commitment_number - 1 == actual_commitment_number, "{} doesn't come after {}", actual_commitment_number, last_commitment_number);
+ // Ensure that the counterparty doesn't get more than two broadcastable commitments -
+ // the last and the one we are trying to sign
+ assert!(actual_commitment_number >= state.last_counterparty_revoked_commitment - 2, "cannot sign a commitment if second to last wasn't revoked - signing {} revoked {}", actual_commitment_number, state.last_counterparty_revoked_commitment);
+ state.last_counterparty_commitment = cmp::min(last_commitment_number, actual_commitment_number)
+ }
+
+ Ok(self.inner.sign_counterparty_commitment(commitment_tx, preimages, secp_ctx).unwrap())
+ }
+
+ fn validate_counterparty_revocation(&self, idx: u64, _secret: &SecretKey) -> Result<(), ()> {
+ let mut state = self.state.lock().unwrap();
+ assert!(idx == state.last_counterparty_revoked_commitment || idx == state.last_counterparty_revoked_commitment - 1, "expecting to validate the current or next counterparty revocation - trying {}, current {}", idx, state.last_counterparty_revoked_commitment);
+ state.last_counterparty_revoked_commitment = idx;
+ Ok(())
+ }
+
+ fn sign_holder_commitment_and_htlcs(&self, commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1<secp256k1::All>) -> Result<(Signature, Vec<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();
+
+ 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 {
+ 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, 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())
+ }
+
+ #[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 sign_justice_revoked_output(&self, justice_tx: &Transaction, input: usize, amount: u64, per_commitment_key: &SecretKey, secp_ctx: &Secp256k1<secp256k1::All>) -> Result<Signature, ()> {
+ Ok(self.inner.sign_justice_revoked_output(justice_tx, input, amount, per_commitment_key, secp_ctx).unwrap())
+ }
+
+ fn sign_justice_revoked_htlc(&self, justice_tx: &Transaction, input: usize, amount: u64, per_commitment_key: &SecretKey, htlc: &HTLCOutputInCommitment, secp_ctx: &Secp256k1<secp256k1::All>) -> Result<Signature, ()> {
+ Ok(self.inner.sign_justice_revoked_htlc(justice_tx, input, amount, per_commitment_key, htlc, secp_ctx).unwrap())
+ }
+
+ fn sign_holder_htlc_transaction(
+ &self, htlc_tx: &Transaction, input: usize, htlc_descriptor: &HTLCDescriptor,
+ secp_ctx: &Secp256k1<secp256k1::All>
+ ) -> Result<Signature, ()> {
+ assert_eq!(htlc_tx.input[input], htlc_descriptor.unsigned_tx_input());
+ assert_eq!(htlc_tx.output[input], htlc_descriptor.tx_output(secp_ctx));
+ Ok(self.inner.sign_holder_htlc_transaction(htlc_tx, input, htlc_descriptor, secp_ctx).unwrap())
+ }
+
+ fn sign_counterparty_htlc_transaction(&self, htlc_tx: &Transaction, input: usize, amount: u64, per_commitment_point: &PublicKey, htlc: &HTLCOutputInCommitment, secp_ctx: &Secp256k1<secp256k1::All>) -> Result<Signature, ()> {
+ Ok(self.inner.sign_counterparty_htlc_transaction(htlc_tx, input, amount, per_commitment_point, htlc, secp_ctx).unwrap())
+ }
+
+ fn sign_closing_transaction(&self, closing_tx: &ClosingTransaction, secp_ctx: &Secp256k1<secp256k1::All>) -> Result<Signature, ()> {
+ closing_tx.verify(self.inner.funding_outpoint().into_bitcoin_outpoint())
+ .expect("derived different closing transaction");
+ Ok(self.inner.sign_closing_transaction(closing_tx, secp_ctx).unwrap())
+ }
+
+ fn sign_holder_anchor_input(
+ &self, anchor_tx: &Transaction, input: usize, secp_ctx: &Secp256k1<secp256k1::All>,
+ ) -> Result<Signature, ()> {
+ debug_assert!(MIN_CHAN_DUST_LIMIT_SATOSHIS > ANCHOR_OUTPUT_VALUE_SATOSHI);
+ // As long as our minimum dust limit is enforced and is greater than our anchor output
+ // value, an anchor output can only have an index within [0, 1].
+ assert!(anchor_tx.input[input].previous_output.vout == 0 || anchor_tx.input[input].previous_output.vout == 1);
+ self.inner.sign_holder_anchor_input(anchor_tx, input, secp_ctx)
+ }
+
+ fn sign_channel_announcement_with_funding_key(
+ &self, msg: &msgs::UnsignedChannelAnnouncement, secp_ctx: &Secp256k1<secp256k1::All>
+ ) -> Result<Signature, ()> {
+ self.inner.sign_channel_announcement_with_funding_key(msg, secp_ctx)
+ }
+}
+
+impl WriteableEcdsaChannelSigner for TestChannelSigner {}
+
+impl Writeable for TestChannelSigner {
+ fn write<W: Writer>(&self, writer: &mut W) -> Result<(), Error> {
+ // TestChannelSigner has two fields - `inner` ([`InMemorySigner`]) and `state`
+ // ([`EnforcementState`]). `inner` is serialized here and deserialized by
+ // [`SignerProvider::read_chan_signer`]. `state` is managed by [`SignerProvider`]
+ // and will be serialized as needed by the implementation of that trait.
+ self.inner.write(writer)?;
+ Ok(())
+ }
+}
+
+impl TestChannelSigner {
+ fn verify_counterparty_commitment_tx<'a, T: secp256k1::Signing + secp256k1::Verification>(&self, commitment_tx: &'a CommitmentTransaction, secp_ctx: &Secp256k1<T>) -> TrustedCommitmentTransaction<'a> {
+ commitment_tx.verify(&self.inner.get_channel_parameters().as_counterparty_broadcastable(),
+ self.inner.counterparty_pubkeys(), self.inner.pubkeys(), secp_ctx)
+ .expect("derived different per-tx keys or built transaction")
+ }
+
+ fn verify_holder_commitment_tx<'a, T: secp256k1::Signing + secp256k1::Verification>(&self, commitment_tx: &'a CommitmentTransaction, secp_ctx: &Secp256k1<T>) -> TrustedCommitmentTransaction<'a> {
+ commitment_tx.verify(&self.inner.get_channel_parameters().as_holder_broadcastable(),
+ self.inner.pubkeys(), self.inner.counterparty_pubkeys(), secp_ctx)
+ .expect("derived different per-tx keys or built transaction")
+ }
+}
+
+/// The state used by [`TestChannelSigner`] in order to enforce policy checks
+///
+/// This structure is maintained by KeysInterface since we may have multiple copies of
+/// the signer and they must coordinate their state.
+#[derive(Clone)]
+pub struct EnforcementState {
+ /// The last counterparty commitment number we signed, backwards counting
+ pub last_counterparty_commitment: u64,
+ /// The last counterparty commitment they revoked, backwards counting
+ pub last_counterparty_revoked_commitment: u64,
+ /// The last holder commitment number we revoked, backwards counting
+ pub last_holder_revoked_commitment: u64,
+ /// The last validated holder commitment number, backwards counting
+ pub last_holder_commitment: u64,
+}
+
+impl EnforcementState {
+ /// Enforcement state for a new channel
+ pub fn new() -> Self {
+ EnforcementState {
+ last_counterparty_commitment: INITIAL_REVOKED_COMMITMENT_NUMBER,
+ last_counterparty_revoked_commitment: INITIAL_REVOKED_COMMITMENT_NUMBER,
+ last_holder_revoked_commitment: INITIAL_REVOKED_COMMITMENT_NUMBER,
+ last_holder_commitment: INITIAL_REVOKED_COMMITMENT_NUMBER,
+ }
+ }
+}
use crate::chain::WatchedOutput;
use crate::chain::chaininterface;
use crate::chain::chaininterface::ConfirmationTarget;
+use crate::chain::chaininterface::FEERATE_FLOOR_SATS_PER_KW;
use crate::chain::chainmonitor;
use crate::chain::chainmonitor::MonitorUpdateId;
use crate::chain::channelmonitor;
use crate::chain::transaction::OutPoint;
use crate::sign;
use crate::events;
+use crate::events::bump_transaction::{WalletSource, Utxo};
+use crate::ln::ChannelId;
use crate::ln::channelmanager;
+use crate::ln::chan_utils::CommitmentTransaction;
use crate::ln::features::{ChannelFeatures, InitFeatures, NodeFeatures};
use crate::ln::{msgs, wire};
use crate::ln::msgs::LightningError;
use crate::ln::script::ShutdownScript;
+use crate::offers::invoice::UnsignedBolt12Invoice;
+use crate::offers::invoice_request::UnsignedInvoiceRequest;
use crate::routing::gossip::{EffectiveCapacity, NetworkGraph, NodeId};
use crate::routing::utxo::{UtxoLookup, UtxoLookupError, UtxoResult};
use crate::routing::router::{find_route, InFlightHtlcs, Path, Route, RouteParameters, Router, ScorerAccountingForInFlightHtlcs};
-use crate::routing::scoring::{ChannelUsage, Score};
+use crate::routing::scoring::{ChannelUsage, ScoreUpdate, ScoreLookUp};
+use crate::sync::RwLock;
use crate::util::config::UserConfig;
-use crate::util::enforcing_trait_impls::{EnforcingSigner, EnforcementState};
+use crate::util::test_channel_signer::{TestChannelSigner, EnforcementState};
use crate::util::logger::{Logger, Level, Record};
use crate::util::ser::{Readable, ReadableArgs, Writer, Writeable};
+use crate::util::persist::KVStore;
+use bitcoin::EcdsaSighashType;
use bitcoin::blockdata::constants::ChainHash;
use bitcoin::blockdata::constants::genesis_block;
use bitcoin::blockdata::transaction::{Transaction, TxOut};
use bitcoin::blockdata::block::Block;
use bitcoin::network::constants::Network;
use bitcoin::hash_types::{BlockHash, Txid};
+use bitcoin::util::sighash::SighashCache;
-use bitcoin::secp256k1::{SecretKey, PublicKey, Secp256k1, ecdsa::Signature, Scalar};
+use bitcoin::secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey};
use bitcoin::secp256k1::ecdh::SharedSecret;
-use bitcoin::secp256k1::ecdsa::RecoverableSignature;
+use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature};
+use bitcoin::secp256k1::schnorr;
#[cfg(any(test, feature = "_test_utils"))]
use regex;
use crate::io;
use crate::prelude::*;
use core::cell::RefCell;
-use core::ops::DerefMut;
use core::time::Duration;
use crate::sync::{Mutex, Arc};
use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
pub struct TestRouter<'a> {
pub network_graph: Arc<NetworkGraph<&'a TestLogger>>,
pub next_routes: Mutex<VecDeque<(RouteParameters, Result<Route, LightningError>)>>,
- pub scorer: &'a Mutex<TestScorer>,
+ pub scorer: &'a RwLock<TestScorer>,
}
impl<'a> TestRouter<'a> {
- pub fn new(network_graph: Arc<NetworkGraph<&'a TestLogger>>, scorer: &'a Mutex<TestScorer>) -> Self {
+ pub fn new(network_graph: Arc<NetworkGraph<&'a TestLogger>>, scorer: &'a RwLock<TestScorer>) -> Self {
Self { network_graph, next_routes: Mutex::new(VecDeque::new()), scorer }
}
impl<'a> Router for TestRouter<'a> {
fn find_route(
&self, payer: &PublicKey, params: &RouteParameters, first_hops: Option<&[&channelmanager::ChannelDetails]>,
- inflight_htlcs: &InFlightHtlcs
+ inflight_htlcs: InFlightHtlcs
) -> Result<Route, msgs::LightningError> {
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 {
- let mut binding = self.scorer.lock().unwrap();
- let scorer = ScorerAccountingForInFlightHtlcs::new(binding.deref_mut(), inflight_htlcs);
+ let scorer = self.scorer.read().unwrap();
+ let scorer = ScorerAccountingForInFlightHtlcs::new(scorer, &inflight_htlcs);
for path in &route.paths {
let mut aggregate_msat = 0u64;
for (idx, hop) in path.hops.iter().rev().enumerate() {
let logger = TestLogger::new();
find_route(
payer, params, &self.network_graph, first_hops, &logger,
- &ScorerAccountingForInFlightHtlcs::new(self.scorer.lock().unwrap().deref_mut(), &inflight_htlcs), &(),
+ &ScorerAccountingForInFlightHtlcs::new(self.scorer.read().unwrap(), &inflight_htlcs), &(),
&[42; 32]
)
}
fn get_secure_random_bytes(&self) -> [u8; 32] { [0; 32] }}
impl SignerProvider for OnlyReadsKeysInterface {
- type Signer = EnforcingSigner;
+ type Signer = TestChannelSigner;
fn generate_channel_keys_id(&self, _inbound: bool, _channel_value_satoshis: u64, _user_channel_id: u128) -> [u8; 32] { unreachable!(); }
let inner: InMemorySigner = ReadableArgs::read(&mut reader, self)?;
let state = Arc::new(Mutex::new(EnforcementState::new()));
- Ok(EnforcingSigner::new_with_revoked(
+ Ok(TestChannelSigner::new_with_revoked(
inner,
state,
false
}
pub struct TestChainMonitor<'a> {
- pub added_monitors: Mutex<Vec<(OutPoint, channelmonitor::ChannelMonitor<EnforcingSigner>)>>,
- pub monitor_updates: Mutex<HashMap<[u8; 32], Vec<channelmonitor::ChannelMonitorUpdate>>>,
- pub latest_monitor_update_id: Mutex<HashMap<[u8; 32], (OutPoint, u64, MonitorUpdateId)>>,
- pub chain_monitor: chainmonitor::ChainMonitor<EnforcingSigner, &'a TestChainSource, &'a chaininterface::BroadcasterInterface, &'a TestFeeEstimator, &'a TestLogger, &'a chainmonitor::Persist<EnforcingSigner>>,
+ pub added_monitors: Mutex<Vec<(OutPoint, channelmonitor::ChannelMonitor<TestChannelSigner>)>>,
+ pub monitor_updates: Mutex<HashMap<ChannelId, Vec<channelmonitor::ChannelMonitorUpdate>>>,
+ pub latest_monitor_update_id: Mutex<HashMap<ChannelId, (OutPoint, u64, MonitorUpdateId)>>,
+ pub chain_monitor: chainmonitor::ChainMonitor<TestChannelSigner, &'a TestChainSource, &'a chaininterface::BroadcasterInterface, &'a TestFeeEstimator, &'a TestLogger, &'a chainmonitor::Persist<TestChannelSigner>>,
pub keys_manager: &'a TestKeysInterface,
/// If this is set to Some(), the next update_channel call (not watch_channel) must be a
/// ChannelForceClosed event for the given channel_id with should_broadcast set to the given
/// boolean.
- pub expect_channel_force_closed: Mutex<Option<([u8; 32], bool)>>,
+ pub expect_channel_force_closed: Mutex<Option<(ChannelId, bool)>>,
}
impl<'a> TestChainMonitor<'a> {
- pub fn new(chain_source: Option<&'a TestChainSource>, broadcaster: &'a chaininterface::BroadcasterInterface, logger: &'a TestLogger, fee_estimator: &'a TestFeeEstimator, persister: &'a chainmonitor::Persist<EnforcingSigner>, keys_manager: &'a TestKeysInterface) -> Self {
+ pub fn new(chain_source: Option<&'a TestChainSource>, broadcaster: &'a chaininterface::BroadcasterInterface, logger: &'a TestLogger, fee_estimator: &'a TestFeeEstimator, persister: &'a chainmonitor::Persist<TestChannelSigner>, keys_manager: &'a TestKeysInterface) -> Self {
Self {
added_monitors: Mutex::new(Vec::new()),
monitor_updates: Mutex::new(HashMap::new()),
}
}
- pub fn complete_sole_pending_chan_update(&self, channel_id: &[u8; 32]) {
+ pub fn complete_sole_pending_chan_update(&self, channel_id: &ChannelId) {
let (outpoint, _, latest_update) = self.latest_monitor_update_id.lock().unwrap().get(channel_id).unwrap().clone();
self.chain_monitor.channel_monitor_updated(outpoint, latest_update).unwrap();
}
}
-impl<'a> chain::Watch<EnforcingSigner> for TestChainMonitor<'a> {
- fn watch_channel(&self, funding_txo: OutPoint, monitor: channelmonitor::ChannelMonitor<EnforcingSigner>) -> chain::ChannelMonitorUpdateStatus {
+impl<'a> chain::Watch<TestChannelSigner> for TestChainMonitor<'a> {
+ fn watch_channel(&self, funding_txo: OutPoint, monitor: channelmonitor::ChannelMonitor<TestChannelSigner>) -> Result<chain::ChannelMonitorUpdateStatus, ()> {
// At every point where we get a monitor update, we should be able to send a useful monitor
// to a watchtower and disk...
let mut w = TestVecWriter(Vec::new());
monitor.write(&mut w).unwrap();
- let new_monitor = <(BlockHash, channelmonitor::ChannelMonitor<EnforcingSigner>)>::read(
+ let new_monitor = <(BlockHash, channelmonitor::ChannelMonitor<TestChannelSigner>)>::read(
&mut io::Cursor::new(&w.0), (self.keys_manager, self.keys_manager)).unwrap().1;
assert!(new_monitor == monitor);
self.latest_monitor_update_id.lock().unwrap().insert(funding_txo.to_channel_id(),
let monitor = self.chain_monitor.get_monitor(funding_txo).unwrap();
w.0.clear();
monitor.write(&mut w).unwrap();
- let new_monitor = <(BlockHash, channelmonitor::ChannelMonitor<EnforcingSigner>)>::read(
+ let new_monitor = <(BlockHash, channelmonitor::ChannelMonitor<TestChannelSigner>)>::read(
&mut io::Cursor::new(&w.0), (self.keys_manager, self.keys_manager)).unwrap().1;
assert!(new_monitor == *monitor);
self.added_monitors.lock().unwrap().push((funding_txo, new_monitor));
}
}
+struct JusticeTxData {
+ justice_tx: Transaction,
+ value: u64,
+ commitment_number: u64,
+}
+
+pub(crate) struct WatchtowerPersister {
+ persister: TestPersister,
+ /// Upon a new commitment_signed, we'll get a
+ /// ChannelMonitorUpdateStep::LatestCounterpartyCommitmentTxInfo. We'll store the justice tx
+ /// amount, and commitment number so we can build the justice tx after our counterparty
+ /// revokes it.
+ unsigned_justice_tx_data: Mutex<HashMap<OutPoint, VecDeque<JusticeTxData>>>,
+ /// After receiving a revoke_and_ack for a commitment number, we'll form and store the justice
+ /// tx which would be used to provide a watchtower with the data it needs.
+ watchtower_state: Mutex<HashMap<OutPoint, HashMap<Txid, Transaction>>>,
+ destination_script: Script,
+}
+
+impl WatchtowerPersister {
+ #[cfg(test)]
+ pub(crate) fn new(destination_script: Script) -> Self {
+ WatchtowerPersister {
+ persister: TestPersister::new(),
+ unsigned_justice_tx_data: Mutex::new(HashMap::new()),
+ watchtower_state: Mutex::new(HashMap::new()),
+ destination_script,
+ }
+ }
+
+ #[cfg(test)]
+ pub(crate) fn justice_tx(&self, funding_txo: OutPoint, commitment_txid: &Txid)
+ -> Option<Transaction> {
+ self.watchtower_state.lock().unwrap().get(&funding_txo).unwrap().get(commitment_txid).cloned()
+ }
+
+ fn form_justice_data_from_commitment(&self, counterparty_commitment_tx: &CommitmentTransaction)
+ -> Option<JusticeTxData> {
+ let trusted_tx = counterparty_commitment_tx.trust();
+ let output_idx = trusted_tx.revokeable_output_index()?;
+ let built_tx = trusted_tx.built_transaction();
+ let value = built_tx.transaction.output[output_idx as usize].value;
+ let justice_tx = trusted_tx.build_to_local_justice_tx(
+ FEERATE_FLOOR_SATS_PER_KW as u64, self.destination_script.clone()).ok()?;
+ let commitment_number = counterparty_commitment_tx.commitment_number();
+ Some(JusticeTxData { justice_tx, value, commitment_number })
+ }
+}
+
+impl<Signer: sign::WriteableEcdsaChannelSigner> chainmonitor::Persist<Signer> for WatchtowerPersister {
+ fn persist_new_channel(&self, funding_txo: OutPoint,
+ data: &channelmonitor::ChannelMonitor<Signer>, id: MonitorUpdateId
+ ) -> chain::ChannelMonitorUpdateStatus {
+ let res = self.persister.persist_new_channel(funding_txo, data, id);
+
+ assert!(self.unsigned_justice_tx_data.lock().unwrap()
+ .insert(funding_txo, VecDeque::new()).is_none());
+ assert!(self.watchtower_state.lock().unwrap()
+ .insert(funding_txo, HashMap::new()).is_none());
+
+ let initial_counterparty_commitment_tx = data.initial_counterparty_commitment_tx()
+ .expect("First and only call expects Some");
+ if let Some(justice_data)
+ = self.form_justice_data_from_commitment(&initial_counterparty_commitment_tx) {
+ self.unsigned_justice_tx_data.lock().unwrap()
+ .get_mut(&funding_txo).unwrap()
+ .push_back(justice_data);
+ }
+ res
+ }
+
+ fn update_persisted_channel(
+ &self, funding_txo: OutPoint, update: Option<&channelmonitor::ChannelMonitorUpdate>,
+ data: &channelmonitor::ChannelMonitor<Signer>, update_id: MonitorUpdateId
+ ) -> chain::ChannelMonitorUpdateStatus {
+ let res = self.persister.update_persisted_channel(funding_txo, update, data, update_id);
+
+ if let Some(update) = update {
+ let commitment_txs = data.counterparty_commitment_txs_from_update(update);
+ let justice_datas = commitment_txs.into_iter()
+ .filter_map(|commitment_tx| self.form_justice_data_from_commitment(&commitment_tx));
+ let mut channels_justice_txs = self.unsigned_justice_tx_data.lock().unwrap();
+ let channel_state = channels_justice_txs.get_mut(&funding_txo).unwrap();
+ channel_state.extend(justice_datas);
+
+ while let Some(JusticeTxData { justice_tx, value, commitment_number }) = channel_state.front() {
+ let input_idx = 0;
+ let commitment_txid = justice_tx.input[input_idx].previous_output.txid;
+ match data.sign_to_local_justice_tx(justice_tx.clone(), input_idx, *value, *commitment_number) {
+ Ok(signed_justice_tx) => {
+ let dup = self.watchtower_state.lock().unwrap()
+ .get_mut(&funding_txo).unwrap()
+ .insert(commitment_txid, signed_justice_tx);
+ assert!(dup.is_none());
+ channel_state.pop_front();
+ },
+ Err(_) => break,
+ }
+ }
+ }
+ res
+ }
+}
+
pub struct TestPersister {
/// The queue of update statuses we'll return. If none are queued, ::Completed will always be
/// returned.
}
}
+pub struct TestStore {
+ persisted_bytes: Mutex<HashMap<String, HashMap<String, Vec<u8>>>>,
+ read_only: bool,
+}
+
+impl TestStore {
+ pub fn new(read_only: bool) -> Self {
+ let persisted_bytes = Mutex::new(HashMap::new());
+ Self { persisted_bytes, read_only }
+ }
+}
+
+impl KVStore for TestStore {
+ fn read(&self, namespace: &str, sub_namespace: &str, key: &str) -> io::Result<Vec<u8>> {
+ let persisted_lock = self.persisted_bytes.lock().unwrap();
+ let prefixed = if sub_namespace.is_empty() {
+ namespace.to_string()
+ } else {
+ format!("{}/{}", namespace, sub_namespace)
+ };
+
+ if let Some(outer_ref) = persisted_lock.get(&prefixed) {
+ if let Some(inner_ref) = outer_ref.get(key) {
+ let bytes = inner_ref.clone();
+ Ok(bytes)
+ } else {
+ Err(io::Error::new(io::ErrorKind::NotFound, "Key not found"))
+ }
+ } else {
+ Err(io::Error::new(io::ErrorKind::NotFound, "Namespace not found"))
+ }
+ }
+
+ fn write(&self, namespace: &str, sub_namespace: &str, key: &str, buf: &[u8]) -> io::Result<()> {
+ if self.read_only {
+ return Err(io::Error::new(
+ io::ErrorKind::PermissionDenied,
+ "Cannot modify read-only store",
+ ));
+ }
+ let mut persisted_lock = self.persisted_bytes.lock().unwrap();
+
+ let prefixed = if sub_namespace.is_empty() {
+ namespace.to_string()
+ } else {
+ format!("{}/{}", namespace, sub_namespace)
+ };
+ let outer_e = persisted_lock.entry(prefixed).or_insert(HashMap::new());
+ let mut bytes = Vec::new();
+ bytes.write_all(buf)?;
+ outer_e.insert(key.to_string(), bytes);
+ Ok(())
+ }
+
+ fn remove(&self, namespace: &str, sub_namespace: &str, key: &str, _lazy: bool) -> io::Result<()> {
+ if self.read_only {
+ return Err(io::Error::new(
+ io::ErrorKind::PermissionDenied,
+ "Cannot modify read-only store",
+ ));
+ }
+
+ let mut persisted_lock = self.persisted_bytes.lock().unwrap();
+
+ let prefixed = if sub_namespace.is_empty() {
+ namespace.to_string()
+ } else {
+ format!("{}/{}", namespace, sub_namespace)
+ };
+ if let Some(outer_ref) = persisted_lock.get_mut(&prefixed) {
+ outer_ref.remove(&key.to_string());
+ }
+
+ Ok(())
+ }
+
+ fn list(&self, namespace: &str, sub_namespace: &str) -> io::Result<Vec<String>> {
+ let mut persisted_lock = self.persisted_bytes.lock().unwrap();
+
+ let prefixed = if sub_namespace.is_empty() {
+ namespace.to_string()
+ } else {
+ format!("{}/{}", namespace, sub_namespace)
+ };
+ match persisted_lock.entry(prefixed) {
+ hash_map::Entry::Occupied(e) => Ok(e.get().keys().cloned().collect()),
+ hash_map::Entry::Vacant(_) => Ok(Vec::new()),
+ }
+ }
+}
+
pub struct TestBroadcaster {
pub txn_broadcasted: Mutex<Vec<Transaction>>,
pub blocks: Arc<Mutex<Vec<(Block, u32)>>>,
unreachable!()
}
+ fn sign_bolt12_invoice_request(
+ &self, _invoice_request: &UnsignedInvoiceRequest
+ ) -> Result<schnorr::Signature, ()> {
+ unreachable!()
+ }
+
+ fn sign_bolt12_invoice(
+ &self, _invoice: &UnsignedBolt12Invoice,
+ ) -> Result<schnorr::Signature, ()> {
+ unreachable!()
+ }
+
fn sign_gossip_message(&self, _msg: msgs::UnsignedGossipMessage) -> Result<Signature, ()> {
unreachable!()
}
self.backing.sign_invoice(hrp_bytes, invoice_data, recipient)
}
+ fn sign_bolt12_invoice_request(
+ &self, invoice_request: &UnsignedInvoiceRequest
+ ) -> Result<schnorr::Signature, ()> {
+ self.backing.sign_bolt12_invoice_request(invoice_request)
+ }
+
+ fn sign_bolt12_invoice(
+ &self, invoice: &UnsignedBolt12Invoice,
+ ) -> Result<schnorr::Signature, ()> {
+ self.backing.sign_bolt12_invoice(invoice)
+ }
+
fn sign_gossip_message(&self, msg: msgs::UnsignedGossipMessage) -> Result<Signature, ()> {
self.backing.sign_gossip_message(msg)
}
}
impl SignerProvider for TestKeysInterface {
- type Signer = EnforcingSigner;
+ type Signer = TestChannelSigner;
fn generate_channel_keys_id(&self, inbound: bool, channel_value_satoshis: u64, user_channel_id: u128) -> [u8; 32] {
self.backing.generate_channel_keys_id(inbound, channel_value_satoshis, user_channel_id)
}
- fn derive_channel_signer(&self, channel_value_satoshis: u64, channel_keys_id: [u8; 32]) -> EnforcingSigner {
+ fn derive_channel_signer(&self, channel_value_satoshis: u64, channel_keys_id: [u8; 32]) -> TestChannelSigner {
let keys = self.backing.derive_channel_signer(channel_value_satoshis, channel_keys_id);
let state = self.make_enforcement_state_cell(keys.commitment_seed);
- EnforcingSigner::new_with_revoked(keys, state, self.disable_revocation_policy_check)
+ TestChannelSigner::new_with_revoked(keys, state, self.disable_revocation_policy_check)
}
fn read_chan_signer(&self, buffer: &[u8]) -> Result<Self::Signer, msgs::DecodeError> {
let inner: InMemorySigner = ReadableArgs::read(&mut reader, self)?;
let state = self.make_enforcement_state_cell(inner.commitment_seed);
- Ok(EnforcingSigner::new_with_revoked(
+ Ok(TestChannelSigner::new_with_revoked(
inner,
state,
self.disable_revocation_policy_check
self
}
- pub fn derive_channel_keys(&self, channel_value_satoshis: u64, id: &[u8; 32]) -> EnforcingSigner {
+ pub fn derive_channel_keys(&self, channel_value_satoshis: u64, id: &[u8; 32]) -> TestChannelSigner {
let keys = self.backing.derive_channel_keys(channel_value_satoshis, id);
let state = self.make_enforcement_state_cell(keys.commitment_seed);
- EnforcingSigner::new_with_revoked(keys, state, self.disable_revocation_policy_check)
+ TestChannelSigner::new_with_revoked(keys, state, self.disable_revocation_policy_check)
}
fn make_enforcement_state_cell(&self, commitment_seed: [u8; 32]) -> Arc<Mutex<EnforcementState>> {
fn write<W: crate::util::ser::Writer>(&self, _: &mut W) -> Result<(), crate::io::Error> { unreachable!(); }
}
-impl Score for TestScorer {
+impl ScoreLookUp for TestScorer {
type ScoreParams = ();
fn channel_penalty_msat(
&self, short_channel_id: u64, _source: &NodeId, _target: &NodeId, usage: ChannelUsage, _score_params: &Self::ScoreParams
}
0
}
+}
+impl ScoreUpdate for TestScorer {
fn payment_path_failed(&mut self, _actual_path: &Path, _actual_short_channel_id: u64) {}
fn payment_path_successful(&mut self, _actual_path: &Path) {}
}
}
}
+
+pub struct TestWalletSource {
+ secret_key: SecretKey,
+ utxos: RefCell<Vec<Utxo>>,
+ secp: Secp256k1<bitcoin::secp256k1::All>,
+}
+
+impl TestWalletSource {
+ pub fn new(secret_key: SecretKey) -> Self {
+ Self {
+ secret_key,
+ utxos: RefCell::new(Vec::new()),
+ secp: Secp256k1::new(),
+ }
+ }
+
+ pub fn add_utxo(&self, outpoint: bitcoin::OutPoint, value: u64) -> TxOut {
+ let public_key = bitcoin::PublicKey::new(self.secret_key.public_key(&self.secp));
+ let utxo = Utxo::new_p2pkh(outpoint, value, &public_key.pubkey_hash());
+ self.utxos.borrow_mut().push(utxo.clone());
+ utxo.output
+ }
+
+ pub fn add_custom_utxo(&self, utxo: Utxo) -> TxOut {
+ let output = utxo.output.clone();
+ self.utxos.borrow_mut().push(utxo);
+ output
+ }
+
+ pub fn remove_utxo(&self, outpoint: bitcoin::OutPoint) {
+ self.utxos.borrow_mut().retain(|utxo| utxo.outpoint != outpoint);
+ }
+}
+
+impl WalletSource for TestWalletSource {
+ fn list_confirmed_utxos(&self) -> Result<Vec<Utxo>, ()> {
+ Ok(self.utxos.borrow().clone())
+ }
+
+ fn get_change_script(&self) -> Result<Script, ()> {
+ let public_key = bitcoin::PublicKey::new(self.secret_key.public_key(&self.secp));
+ Ok(Script::new_p2pkh(&public_key.pubkey_hash()))
+ }
+
+ fn sign_tx(&self, mut tx: Transaction) -> Result<Transaction, ()> {
+ let utxos = self.utxos.borrow();
+ for i in 0..tx.input.len() {
+ if let Some(utxo) = utxos.iter().find(|utxo| utxo.outpoint == tx.input[i].previous_output) {
+ let sighash = SighashCache::new(&tx)
+ .legacy_signature_hash(i, &utxo.output.script_pubkey, EcdsaSighashType::All as u32)
+ .map_err(|_| ())?;
+ let sig = self.secp.sign_ecdsa(&sighash.as_hash().into(), &self.secret_key);
+ let bitcoin_sig = bitcoin::EcdsaSig { sig, hash_ty: EcdsaSighashType::All }.to_vec();
+ tx.input[i].script_sig = Builder::new()
+ .push_slice(&bitcoin_sig)
+ .push_slice(&self.secret_key.public_key(&self.secp).serialize())
+ .into_script();
+ }
+ }
+ Ok(tx)
+ }
+}
+++ /dev/null
-// This is a modification of base32 encoding to support the zbase32 alphabet.
-// The original piece of software can be found at https://github.com/andreasots/base32
-// The original portions of this software are Copyright (c) 2015 The base32 Developers
-
-/* This file is licensed under either of
- * Apache License, Version 2.0, (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0) or
- * MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
- * at your option.
-*/
-
-use crate::prelude::*;
-
-const ALPHABET: &'static [u8] = b"ybndrfg8ejkmcpqxot1uwisza345h769";
-
-/// Encodes some bytes as a zbase32 string
-pub fn encode(data: &[u8]) -> String {
- let mut ret = Vec::with_capacity((data.len() + 4) / 5 * 8);
-
- for chunk in data.chunks(5) {
- let buf = {
- let mut buf = [0u8; 5];
- for (i, &b) in chunk.iter().enumerate() {
- buf[i] = b;
- }
- buf
- };
-
- ret.push(ALPHABET[((buf[0] & 0xF8) >> 3) as usize]);
- ret.push(ALPHABET[(((buf[0] & 0x07) << 2) | ((buf[1] & 0xC0) >> 6)) as usize]);
- ret.push(ALPHABET[((buf[1] & 0x3E) >> 1) as usize]);
- ret.push(ALPHABET[(((buf[1] & 0x01) << 4) | ((buf[2] & 0xF0) >> 4)) as usize]);
- ret.push(ALPHABET[(((buf[2] & 0x0F) << 1) | (buf[3] >> 7)) as usize]);
- ret.push(ALPHABET[((buf[3] & 0x7C) >> 2) as usize]);
- ret.push(ALPHABET[(((buf[3] & 0x03) << 3) | ((buf[4] & 0xE0) >> 5)) as usize]);
- ret.push(ALPHABET[(buf[4] & 0x1F) as usize]);
- }
-
- ret.truncate((data.len() * 8 + 4) / 5);
-
- // Check that our capacity calculation doesn't under-shoot in fuzzing
- #[cfg(fuzzing)]
- assert_eq!(ret.capacity(), (data.len() + 4) / 5 * 8);
-
- String::from_utf8(ret).unwrap()
-}
-
-// ASCII 0-Z
-const INV_ALPHABET: [i8; 43] = [
- -1, 18, -1, 25, 26, 27, 30, 29, 7, 31, -1, -1, -1, -1, -1, -1, -1, 24, 1, 12, 3, 8, 5, 6, 28,
- 21, 9, 10, -1, 11, 2, 16, 13, 14, 4, 22, 17, 19, -1, 20, 15, 0, 23,
-];
-
-/// Decodes a zbase32 string to the original bytes, failing if the string was not encoded by a
-/// proper zbase32 encoder.
-pub fn decode(data: &str) -> Result<Vec<u8>, ()> {
- if !data.is_ascii() {
- return Err(());
- }
-
- let data = data.as_bytes();
- let output_length = data.len() * 5 / 8;
- if data.len() > (output_length * 8 + 4) / 5 {
- // If the string has more charachters than are required to encode the number of bytes
- // decodable, treat the string as invalid.
- return Err(());
- }
-
- let mut ret = Vec::with_capacity((data.len() + 7) / 8 * 5);
-
- for chunk in data.chunks(8) {
- let buf = {
- let mut buf = [0u8; 8];
- for (i, &c) in chunk.iter().enumerate() {
- match INV_ALPHABET.get(c.to_ascii_uppercase().wrapping_sub(b'0') as usize) {
- Some(&-1) | None => return Err(()),
- Some(&value) => buf[i] = value as u8,
- };
- }
- buf
- };
- ret.push((buf[0] << 3) | (buf[1] >> 2));
- ret.push((buf[1] << 6) | (buf[2] << 1) | (buf[3] >> 4));
- ret.push((buf[3] << 4) | (buf[4] >> 1));
- ret.push((buf[4] << 7) | (buf[5] << 2) | (buf[6] >> 3));
- ret.push((buf[6] << 5) | buf[7]);
- }
- for c in ret.drain(output_length..) {
- if c != 0 {
- // If the original string had any bits set at positions outside of the encoded data,
- // treat the string as invalid.
- return Err(());
- }
- }
-
- // Check that our capacity calculation doesn't under-shoot in fuzzing
- #[cfg(fuzzing)]
- assert_eq!(ret.capacity(), (data.len() + 7) / 8 * 5);
-
- Ok(ret)
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- const TEST_DATA: &[(&str, &[u8])] = &[
- ("", &[]),
- ("yy", &[0x00]),
- ("oy", &[0x80]),
- ("tqrey", &[0x8b, 0x88, 0x80]),
- ("6n9hq", &[0xf0, 0xbf, 0xc7]),
- ("4t7ye", &[0xd4, 0x7a, 0x04]),
- ("6im5sdy", &[0xf5, 0x57, 0xbb, 0x0c]),
- ("ybndrfg8ejkmcpqxot1uwisza345h769", &[0x00, 0x44, 0x32, 0x14, 0xc7, 0x42, 0x54, 0xb6,
- 0x35, 0xcf, 0x84, 0x65, 0x3a, 0x56, 0xd7, 0xc6,
- 0x75, 0xbe, 0x77, 0xdf])
- ];
-
- #[test]
- fn test_encode() {
- for &(zbase32, data) in TEST_DATA {
- assert_eq!(encode(data), zbase32);
- }
- }
-
- #[test]
- fn test_decode() {
- for &(zbase32, data) in TEST_DATA {
- assert_eq!(decode(zbase32).unwrap(), data);
- }
- }
-
- #[test]
- fn test_decode_wrong() {
- const WRONG_DATA: &[&str] = &["00", "l1", "?", "="];
-
- for &data in WRONG_DATA {
- match decode(data) {
- Ok(_) => assert!(false, "Data shouldn't be decodable"),
- Err(_) => assert!(true),
- }
- }
- }
-}
--- /dev/null
+[package]
+name = "msrv-check"
+version = "0.1.0"
+edition = "2018"
+
+[dependencies]
+lightning = { path = "../lightning" }
+lightning-block-sync = { path = "../lightning-block-sync", features = [ "rest-client", "rpc-client" ] }
+lightning-invoice = { path = "../lightning-invoice" }
+lightning-net-tokio = { path = "../lightning-net-tokio" }
+lightning-persister = { path = "../lightning-persister" }
+lightning-background-processor = { path = "../lightning-background-processor", features = ["futures"] }
+lightning-rapid-gossip-sync = { path = "../lightning-rapid-gossip-sync" }
--- /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
-## Bug Fixes
-
-* Fixed sending large onion messages, which previously would result in an HMAC error on the second
- hop (#2277).
+++ /dev/null
-## Backwards Compatibility
-
-* `PaymentParameters` written with blinded path info using 0.0.115 will not be readable in 0.0.116
--- /dev/null
+## Backwards Compatibility
+
+* Since the addition of custom HTLC TLV support in 0.0.117, if you downgrade you may unintentionally accept payments with features you don't understand.
+++ /dev/null
-## Backwards Compat
-
-* Forwarding less than the expected amount in `ChannelManager::forward_intercepted_htlc` may break
- compatibility with versions of LDK prior to 0.0.116
-* Setting `ChannelConfig::accept_underpaying_htlcs` may break compatibility with versions of LDK
- prior to 0.0.116, and unsetting the feature between restarts may lead to payment failures.
--- /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.
--- /dev/null
+## Backwards Compatibility
+
+* Users migrating custom persistence backends from the pre-v0.0.117 `KVStorePersister` interface can use a concatenation of `[{namespace}/[{sub_namespace}/]]{key}` to recover a `key` compatible with the data model previously assumed by `KVStorePersister::persist`.
--- /dev/null
+## Backwards Compatibility
+
+* The `MonitorUpdatingPersister` can read monitors stored conventionally, such as with the `KVStorePersister` from previous LDK versions. You can use this to migrate _to_ the `MonitorUpdatingPersister`; just "point" `MonitorUpdatingPersister` to existing, fully updated `ChannelMonitors`, and it will read them and work from there. However, downgrading is more complex. Monitors stored with `MonitorUpdatingPersister` have a prepended sentinel value that prevents them from being deserialized by previous `Persist` implementations. This is to ensure that they are not accidentally read and used while pending updates are still stored and not applied, as this could result in penalty transactions. Users who wish to downgrade should perform the following steps:
+ * Make a backup copy of all channel state.
+ * Ensure all updates are applied to the monitors. This may be done by loading all the existing data with the `MonitorUpdatingPersister::read_all_channel_monitors_with_updates` function. You can then write the resulting `ChannelMonitor`s using your previous `Persist` implementation.
\ No newline at end of file
--- /dev/null
+* The `NetAddress` has been moved to `SocketAddress`. The fieds `IPv4` and `IPv6` are also rename to `TcpIpV4` and `TcpIpV6` (#2358).
--- /dev/null
+* In several APIs, `channel_id` parameters have been changed from type `[u8; 32]` to newly introduced `ChannelId` type, from `ln` namespace (`lightning::ln::ChannelId`) (PR #2485)
+++ /dev/null
- * Legacy inbound payment creation has been removed, thus there is no way to
- create a pending inbound payment which will still be claimable on LDK
- 0.0.103 or earlier. Support for claiming such payments is still retained,
- however is likely to be removed in the next release (#2351).
- * Some fields required in 0.0.103 and earlier are no longer written, thus
- deserializing objects written in 0.0.116 with 0.0.103 may now fail (#2351).
--- /dev/null
+* The `AvailableBalances::balance_msat` field has been removed in favor of `ChannelMonitor::get_claimable_balances`. `ChannelDetails` serialized with versions of LDK >= 0.0.117 will have their `balance_msat` field set to `next_outbound_htlc_limit_msat` when read by versions of LDK prior to 0.0.117 (#2476).
--- /dev/null
+# Backwards Compatibility
+
+- `Route` objects written with LDK versions prior to 0.0.117 won't be retryable after being deserialized with LDK 0.0.117 or above.