diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 00ef76f787e..951691e96bb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,19 +18,7 @@ jobs: fail-fast: false matrix: platform: [ ubuntu-latest, windows-latest, macos-latest ] - toolchain: [ stable, beta ] - include: - - toolchain: stable - platform: ubuntu-latest - # 1.48.0 is the MSRV for all crates except lightning-transaction-sync and Win/Mac - - toolchain: 1.48.0 - platform: ubuntu-latest - # Windows requires 1.49.0 because that's the MSRV for supported Tokio - - toolchain: 1.49.0 - platform: windows-latest - # MacOS-latest requires 1.54.0 because that's what's required for linking to work properly - - toolchain: 1.54.0 - platform: macos-latest + toolchain: [ stable, beta, 1.63.0 ] # 1.63.0 is the MSRV for all crates. runs-on: ${{ matrix.platform }} steps: - name: Checkout source code @@ -44,11 +32,16 @@ jobs: run: | rustup target add thumbv7m-none-eabi sudo apt-get -y install gcc-arm-none-eabi + - name: Check for unknown cfg tags + run: ci/check-cfg-flags.py - name: shellcheck the CI script if: "matrix.platform == 'ubuntu-latest'" run: | sudo apt-get -y install shellcheck shellcheck ci/ci-tests.sh + - name: Set RUSTFLAGS to deny warnings + if: "matrix.toolchain == '1.63.0'" + run: echo "RUSTFLAGS=-D warnings" >> "$GITHUB_ENV" - name: Run CI script shell: bash # Default on Winblows is powershell run: CI_MINIMIZE_DISK_USAGE=1 ./ci/ci-tests.sh @@ -168,13 +161,13 @@ jobs: run: | cargo check --release cargo check --no-default-features --features=no-std --release - cargo check --no-default-features --features=futures --release + cargo check --no-default-features --features=futures,std --release cargo doc --release - name: Run cargo check for Taproot build. run: | cargo check --release cargo check --no-default-features --features=no-std --release - cargo check --no-default-features --features=futures --release + cargo check --no-default-features --features=futures,std --release cargo doc --release env: RUSTFLAGS: '--cfg=taproot' @@ -183,7 +176,7 @@ jobs: fuzz: runs-on: ubuntu-latest env: - TOOLCHAIN: 1.58 + TOOLCHAIN: 1.63 steps: - name: Checkout source code uses: actions/checkout@v3 @@ -195,6 +188,10 @@ jobs: run: | sudo apt-get update sudo apt-get -y install build-essential binutils-dev libunwind-dev + - name: Pin the regex dependency + run: | + cd fuzz && cargo update -p regex --precise "1.9.6" --verbose && cd .. + cd lightning-invoice/fuzz && cargo update -p regex --precise "1.9.6" --verbose - name: Sanity check fuzz targets on Rust ${{ env.TOOLCHAIN }} run: cd fuzz && RUSTFLAGS="--cfg=fuzzing" cargo test --verbose --color always - name: Run fuzzers @@ -219,3 +216,20 @@ jobs: - name: Run default clippy linting run: | cargo clippy -- -Aclippy::erasing_op -Aclippy::never_loop -Aclippy::if_same_then_else -Dclippy::try_err + + rustfmt: + runs-on: ubuntu-latest + env: + TOOLCHAIN: 1.63.0 + steps: + - name: Checkout source code + uses: actions/checkout@v3 + - 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 rustfmt + run: | + rustup component add rustfmt + - name: Run rustfmt checks + run: ci/rustfmt.sh diff --git a/.gitignore b/.gitignore index 7a6dc4c7930..fbeffa8a9c9 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ lightning-rapid-gossip-sync/res/full_graph.lngossip lightning-custom-message/target lightning-transaction-sync/target no-std-check/target +msrv-no-dev-deps-check/target diff --git a/CHANGELOG.md b/CHANGELOG.md index ee3283b2930..3e473a22a1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,189 @@ +# 0.0.121 - Jan 22, 2024 - "Unwraps are Bad" + +## Bug Fixes + * Fix a deadlock when calling `batch_funding_transaction_generated` with + invalid input (#2841). + +## Security +0.0.121 fixes a denial-of-service vulnerability which is reachable from +untrusted input from peers in rare cases if we have a public channel or in +common cases if `P2PGossipSync` is used. + * A peer that failed to complete its handshake would cause a reachable + `unwrap` in LDK since 0.0.119 when LDK attempts to broadcast gossip to all + peers (#2842). + +In total, this release features 4 files changed, 52 insertions, 10 +deletions in 4 commits from 2 authors, in alphabetical order: + * Jeffrey Czyz + * Matt Corallo + +# 0.0.120 - Jan 17, 2024 - "Unblinded Fuzzers" + +## API Updates + * The `PeerManager` bound on `UtxoLookup` was removed entirely. This enables + use of `UtxoLookup` in cases broken in 0.0.119 by #2773 (#2822). + * LDK now exposes and fully implements the route blinding feature (#2812). + * The `lightning-transaction-sync` crate no longer relies on system time + without the `time` feature (#2799, #2817). + * `lightning::onion_message`'s module layout has changed (#2821). + * `Event::ChannelClosed` now includes the `channel_funding_txo` (#2800). + * `CandidateRouteHop` variants were destructured into individual structs, + hiding some fields which were not generally consumable (#2802). + +## Bug Fixes + * Fixed a rare issue where `lightning-net-tokio` may not fully flush its send + buffer, leading to connection hangs (#2832). + * Fixed a panic which may occur when connecting to a peer if we opened a second + channel with that peer while they were disconnected (#2808). + * Retries for a payment which previously failed in a blinded path will now + always use an alternative blinded path (#2818). + * `Feature`'s `Eq` and `Hash` implementation now ignore dummy bytes (#2808). + * Some missing `DiscardFunding` or `ChannelClosed` events are now generated in + rare funding-related failures (#2809). + * Fixed a privacy issue in blinded path generation where the real + `cltv_expiry_delta` would be exposed to senders (#2831). + +## Security +0.0.120 fixes a denial-of-service vulnerability which is reachable from +untrusted input from peers if the `UserConfig::manually_accept_inbound_channels` +option is enabled. + * A peer that sent an `open_channel` message with the `channel_type` field + unfilled would trigger a reachable `unwrap` since LDK 0.0.117 (#2808). + * In protocols where a funding output is shared with our counterparty before + it is given to LDK, a malicious peer could have caused a reachable panic + by reusing the same funding info in (#2809). + +In total, this release features 67 files changed, 3016 insertions, 2473 +deletions in 79 commits from 9 authors, in alphabetical order: + * Elias Rohrer + * Jeffrey Czyz + * JoseĢ A.P + * Matt Corallo + * Tibo-lg + * Valentine Wallace + * benthecarman + * optout + * shuoer86 + +# 0.0.119 - Dec 15, 2023 - "Spring Cleaning for Christmas" + +## API Updates + * The LDK crate ecosystem MSRV has been increased to 1.63 (#2681). + * The `bitcoin` dependency has been updated to version 0.30 (#2740). + * `lightning-invoice::payment::*` have been replaced with parameter generation + via `payment_parameters_from[_zero_amount]_invoice` (#2727). + * `{CoinSelection,Wallet}Source::sign_tx` are now `sign_psbt`, providing more + information, incl spent outputs, about the transaction being signed (#2775). + * Logger `Record`s now include `channel_id` and `peer_id` fields. These are + opportunistically filled in when a log record is specific to a given channel + and/or peer, and may occasionally be spuriously empty (#2314). + * When handling send or reply onion messages (e.g. for BOLT12 payments), a new + `Event::ConnectionNeeded` may be raised, indicating a direct connection + should be made to a payee or an introduction point. This event is expected to + be removed once onion message forwarding is widespread in the network (#2723) + * Scoring data decay now happens via `ScoreUpDate::time_passed`, called from + `lightning-background-processor`. `process_events_async` now takes a new + time-fetch function, and `ScoreUpDate` methods now take the current time as a + `Duration` argument. This avoids fetching time during pathfinding (#2656). + * Receiving payments to multi-hop blinded paths is now supported (#2688). + * `MessageRouter` and `Router` now feature methods to generate blinded paths to + the local node for incoming messages and payments. `Router` now extends + `MessageRouter`, and both are used in `ChannelManager` when processing or + creating BOLT12 structures to generate multi-hop blinded paths (#1781). + * `lightning-transaction-sync` now supports Electrum-based sync (#2685). + * `Confirm::get_relevant_txids` now returns the height at which a transaction + was confirmed. This can be used to assist in reorg detection (#2685). + * `ConfirmationTarget::MaxAllowedNonAnchorChannelRemoteFee` has been removed. + Non-anchor channel feerates are bounded indirectly through + `ChannelConfig::max_dust_htlc_exposure` (#2696). + * `lightning-invoice` `Description`s now rely on `UntrustedString` for + sanitization (#2730). + * `ScoreLookUp::channel_penalty_msat` now uses `CandidateRouteHop` (#2551). + * The `EcdsaChannelSigner` trait was moved to `lightning::sign::ecdsa` (#2512). + * `SignerProvider::get_destination_script` now takes `channel_keys_id` (#2744) + * `SpendableOutputDescriptor::StaticOutput` now has `channel_keys_id` (#2749). + * `EcdsaChannelSigner::sign_counterparty_commitment` now takes HTLC preimages + for both inbound and outbound HTLCs (#2753). + * `ClaimedHTLC` now includes a `counterparty_skimmed_fee_msat` field (#2715). + * `peel_payment_onion` was added to decode an encrypted onion for a payment + without receiving an HTLC. This allows for stateless verification of if a + theoretical payment would be accepted prior to receipt (#2700). + * `create_payment_onion` was added to construct an encrypted onion for a + payment path without sending an HTLC immediately (#2677). + * Various keys used in channels are now wrapped to provide type-safety for + specific usages of the keys (#2675). + * `TaggedHash` now includes the raw `tag` and `merkle_root` (#2687). + * `Offer::is_expired_no_std` was added (#2689). + * `PaymentPurpose::preimage()` was added (#2768). + * `temporary_channel_id` can now be specified in `create_channel` (#2699). + * Wire definitions for splicing messages were added (#2544). + * Various `lightning-invoice` structs now impl `Display`, now have pub fields, + or impl `From` (#2730). + * The `Hash` trait is now implemented for more structs, incl P2P msgs (#2716). + +## Performance Improvements + * Memory allocations (though not memory usage) have been substantially reduced, + meaning less overhead and hopefully less memory fragmentation (#2708, #2779). + +## Bug Fixes + * Since 0.0.117, calling `close_channel*` on a channel which has not yet been + funded would previously result in an infinite loop and hang (#2760). + * Since 0.0.116, sending payments requiring data in the onion for the recipient + which was too large for the onion may have caused corruption which resulted + in payment failure (#2752). + * Cooperative channel closure on channels with remaining output HTLCs may have + spuriously force-closed (#2529). + * In LDK versions 0.0.116 through 0.0.118, in rare cases where skimmed fees are + present on shutdown the `ChannelManager` may fail to deserialize (#2735). + * `ChannelConfig::max_dust_exposure` values which, converted to absolute fees, + exceeded 2^63 - 1 would result in an overflow and could lead to spurious + payment failures or channel closures (#2722). + * In cases where LDK is operating with provably-stale state, it panics to + avoid funds loss. This may not have happened in cases where LDK was behind + only exactly one state, leading instead to a revoked broadcast and funds + loss (#2721). + * Fixed a bug where decoding `Txid`s from Bitcoin Core JSON-RPC responses using + `lightning-block-sync` would not properly byte-swap the hash. Note that LDK + does not use this API internally (#2796). + +## Backwards Compatibility + * `ChannelManager`s written with LDK 0.0.119 are no longer readable by versions + of LDK prior to 0.0.113. Users wishing to downgrade to LDK 0.0.112 or before + can read an 0.0.119-serialized `ChannelManager` with a version of LDK from + 0.0.113 to 0.0.118, re-serialize it, and then downgrade (#2708). + * Nodes that upgrade to 0.0.119 and subsequently downgrade after receiving a + payment to a blinded path may leak recipient information if one or more of + those HTLCs later fails (#2688). + * Similarly, forwarding a blinded HTLC and subsequently downgrading to an LDK + version prior to 0.0.119 may result in leaking the path information to the + payment sender (#2540). + +In total, this release features 148 files changed, 13780 insertions, 6279 +deletions in 280 commits from 22 authors, in alphabetical order: + * Arik Sosman + * Chris Waterson + * Elias Rohrer + * Evan Feenstra + * Gursharan Singh + * Jeffrey Czyz + * John Cantrell + * Lalitmohansharma1 + * Matt Corallo + * Matthew Rheaume + * Orbital + * Rachel Malonson + * Valentine Wallace + * Willem Van Lint + * Wilmer Paulino + * alexanderwiederin + * benthecarman + * henghonglee + * jbesraa + * olegkubrakov + * optout + * shaavan + + # 0.0.118 - Oct 23, 2023 - "Just the Twelve Sinks" ## API Updates diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 350415af24c..4b8e044d1d3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -88,7 +88,7 @@ be covered by functional tests. When refactoring, structure your PR to make it easy to review and don't hesitate to split it into multiple small, focused PRs. -The Minimum Supported Rust Version (MSRV) currently is 1.48.0 (enforced by +The Minimum Supported Rust Version (MSRV) currently is 1.63.0 (enforced by our GitHub Actions). We support reading serialized LDK objects written by any version of LDK 0.0.99 and above. We support LDK versions 0.0.113 and above reading serialized LDK objects written by modern LDK. Any expected issues with @@ -120,7 +120,15 @@ Coding Conventions ------------------ Use tabs. If you want to align lines, use spaces. Any desired alignment should -display fine at any tab-length display setting. +display fine at any tab-length display setting. We use `rustfmt` to establish +uniform coding standards throughout the codebase. Please run + +```bash +./ci/rustfmt.sh +``` + +before committing and pushing any changes, as compliance will also be checked +and enforced by our CI scripts. Our CI enforces [clippy's](https://github.com/rust-lang/rust-clippy) default linting @@ -148,7 +156,7 @@ Security -------- Security is the primary focus of `rust-lightning`; disclosure of security -vulnerabilites helps prevent user loss of funds. If you believe a vulnerability +vulnerabilities helps prevent user loss of funds. If you believe a vulnerability may affect other Lightning implementations, please inform them. You can find further information on submitting (possible) vulnerabilities in the diff --git a/Cargo.toml b/Cargo.toml index 8614cb48c1f..ec9edb3ac33 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,5 @@ [workspace] +resolver = "2" members = [ "lightning", @@ -7,12 +8,13 @@ members = [ "lightning-net-tokio", "lightning-persister", "lightning-background-processor", - "lightning-rapid-gossip-sync" + "lightning-rapid-gossip-sync", + "lightning-custom-message", + "lightning-transaction-sync", + "possiblyrandom", ] exclude = [ - "lightning-custom-message", - "lightning-transaction-sync", "no-std-check", "msrv-no-dev-deps-check", "bench", @@ -37,3 +39,6 @@ lto = "off" opt-level = 3 lto = true panic = "abort" + +[patch.crates-io.possiblyrandom] +path = "possiblyrandom" diff --git a/README.md b/README.md index 49b404633b9..4d8afe43d95 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,9 @@ Rust-Lightning [![Crate](https://img.shields.io/crates/v/lightning.svg?logo=rust)](https://crates.io/crates/lightning) [![Documentation](https://img.shields.io/static/v1?logo=read-the-docs&label=docs.rs&message=lightning&color=informational)](https://docs.rs/lightning/) [![Safety Dance](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/) +[![Security Audit](https://github.com/lightningdevkit/rust-lightning/actions/workflows/audit.yml/badge.svg)](https://github.com/lightningdevkit/rust-lightning/actions/workflows/audit.yml) -[LDK](https://lightningdevkit.org)/`rust-lightning` is a highly performant and flexible +[LDK](https://lightningdevkit.org)/`rust-lightning` is a highly performant and flexible implementation of the Lightning Network protocol. The primary crate, `lightning`, is runtime-agnostic. Data persistence, chain interactions, diff --git a/bench/Cargo.toml b/bench/Cargo.toml index e582d29da81..05354890c2a 100644 --- a/bench/Cargo.toml +++ b/bench/Cargo.toml @@ -2,7 +2,7 @@ name = "lightning-bench" version = "0.0.1" authors = ["Matt Corallo"] -edition = "2018" +edition = "2021" [[bench]] name = "bench" diff --git a/bench/benches/bench.rs b/bench/benches/bench.rs index eaa3fcec50c..b854ffb93ce 100644 --- a/bench/benches/bench.rs +++ b/bench/benches/bench.rs @@ -21,5 +21,6 @@ criterion_group!(benches, 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); + lightning::routing::gossip::benches::write_network_graph, + lightning::routing::scoring::benches::decay_100k_channel_bounds); criterion_main!(benches); diff --git a/ci/ci-tests.sh b/ci/ci-tests.sh index 935cd06e54d..59acb833f1f 100755 --- a/ci/ci-tests.sh +++ b/ci/ci-tests.sh @@ -8,41 +8,69 @@ HOST_PLATFORM="$(rustc --version --verbose | grep "host:" | awk '{ print $2 }')" # 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 - - # 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 +# The tests of `lightning-transaction-sync` require `electrs` and `bitcoind` +# binaries. Here, we download the binaries, validate them, and export their +# location via `ELECTRS_EXE`/`BITCOIND_EXE` which will be used by the +# `electrsd`/`bitcoind` crates in our tests. +function DOWNLOAD_ELECTRS_AND_BITCOIND { + ELECTRS_DL_ENDPOINT="https://github.com/RCasatta/electrsd/releases/download/electrs_releases" + ELECTRS_VERSION="esplora_a33e97e1a1fc63fa9c20a116bb92579bbf43b254" + BITCOIND_DL_ENDPOINT="https://bitcoincore.org/bin/" + BITCOIND_VERSION="25.1" + if [[ "$HOST_PLATFORM" == *linux* ]]; then + ELECTRS_DL_FILE_NAME=electrs_linux_"$ELECTRS_VERSION".zip + ELECTRS_DL_HASH="865e26a96e8df77df01d96f2f569dcf9622fc87a8d99a9b8fe30861a4db9ddf1" + BITCOIND_DL_FILE_NAME=bitcoin-"$BITCOIND_VERSION"-x86_64-linux-gnu.tar.gz + BITCOIND_DL_HASH="a978c407b497a727f0444156e397b50491ce862d1f906fef9b521415b3611c8b" + elif [[ "$HOST_PLATFORM" == *darwin* ]]; then + ELECTRS_DL_FILE_NAME=electrs_macos_"$ELECTRS_VERSION".zip + ELECTRS_DL_HASH="2d5ff149e8a2482d3658e9b386830dfc40c8fbd7c175ca7cbac58240a9505bcd" + BITCOIND_DL_FILE_NAME=bitcoin-"$BITCOIND_VERSION"-x86_64-apple-darwin.tar.gz + BITCOIND_DL_HASH="1acfde0ec3128381b83e3e5f54d1c7907871d324549129592144dd12a821eff1" + else + echo -e "\n\nUnsupported platform. Exiting.." + exit 1 + fi + + DL_TMP_DIR=$(mktemp -d) + trap 'rm -rf -- "$DL_TMP_DIR"' EXIT + + pushd "$DL_TMP_DIR" + ELECTRS_DL_URL="$ELECTRS_DL_ENDPOINT"/"$ELECTRS_DL_FILE_NAME" + curl -L -o "$ELECTRS_DL_FILE_NAME" "$ELECTRS_DL_URL" + echo "$ELECTRS_DL_HASH $ELECTRS_DL_FILE_NAME"|shasum -a 256 -c + unzip "$ELECTRS_DL_FILE_NAME" + export ELECTRS_EXE="$DL_TMP_DIR"/electrs + chmod +x "$ELECTRS_EXE" + + BITCOIND_DL_URL="$BITCOIND_DL_ENDPOINT"/bitcoin-core-"$BITCOIND_VERSION"/"$BITCOIND_DL_FILE_NAME" + curl -L -o "$BITCOIND_DL_FILE_NAME" "$BITCOIND_DL_URL" + echo "$BITCOIND_DL_HASH $BITCOIND_DL_FILE_NAME"|shasum -a 256 -c + tar xzf "$BITCOIND_DL_FILE_NAME" + export BITCOIND_EXE="$DL_TMP_DIR"/bitcoin-"$BITCOIND_VERSION"/bin/bitcoind + chmod +x "$BITCOIND_EXE" + popd +} -# 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 +PIN_RELEASE_DEPS # pin the release dependencies in our main workspace -# 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 +# Starting with version 1.10.0, the `regex` crate has an MSRV of rustc 1.65.0. +[ "$RUSTC_MINOR_VERSION" -lt 65 ] && cargo update -p regex --precise "1.9.6" --verbose -# 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 addr2line v0.21 crate (a dependency of `backtrace` starting with 0.3.69) relies on rustc 1.65 +[ "$RUSTC_MINOR_VERSION" -lt 65 ] && cargo update -p backtrace --precise "0.3.68" --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 +# Starting with version 0.5.9 (there is no .6-.8), the `home` crate has an MSRV of rustc 1.70.0. +[ "$RUSTC_MINOR_VERSION" -lt 70 ] && cargo update -p home --precise "0.5.5" --verbose export RUST_BACKTRACE=1 +# Build `lightning-transaction-sync` in no_download mode. +export RUSTFLAGS="$RUSTFLAGS --cfg no_download" + echo -e "\n\nBuilding and testing all workspace crates..." cargo test --verbose --color always cargo check --verbose --color always @@ -59,9 +87,12 @@ 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 +if [[ "$HOST_PLATFORM" != *windows* ]]; then echo -e "\n\nBuilding and testing Transaction Sync Clients with features" pushd lightning-transaction-sync + + DOWNLOAD_ELECTRS_AND_BITCOIND + cargo test --verbose --color always --features esplora-blocking cargo check --verbose --color always --features esplora-blocking cargo test --verbose --color always --features esplora-async @@ -78,39 +109,36 @@ 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 - [ "$CI_MINIMIZE_DISK_USAGE" != "" ] && cargo clean - popd -fi +echo -e "\n\nTest Custom Message Macros" +pushd lightning-custom-message +cargo test --verbose --color always +[ "$CI_MINIMIZE_DISK_USAGE" != "" ] && cargo clean +popd -if [ "$RUSTC_MINOR_VERSION" -gt 51 ]; then # Current `object` MSRV, subject to change - echo -e "\n\nTest backtrace-debug builds" - pushd lightning - cargo test --verbose --color always --features backtrace - popd -fi +echo -e "\n\nTest backtrace-debug builds" +pushd lightning +cargo test --verbose --color always --features backtrace +popd 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" + RUSTFLAGS="$RUSTFLAGS -A unused_variables -A unused_macros -A unused_imports -A dead_code" 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 - [ "$RUSTC_MINOR_VERSION" -gt 50 ] && cargo test -p $DIR --verbose --color always --no-default-features --features no-std + cargo test -p $DIR --verbose --color always --no-default-features --features no-std # check if there is a conflict between no-std and the default std feature - [ "$RUSTC_MINOR_VERSION" -gt 50 ] && cargo test -p $DIR --verbose --color always --features no-std + cargo test -p $DIR --verbose --color always --features no-std done + for DIR in lightning lightning-invoice lightning-rapid-gossip-sync; do # check if there is a conflict between no-std and the c_bindings cfg - [ "$RUSTC_MINOR_VERSION" -gt 50 ] && RUSTFLAGS="--cfg=c_bindings" cargo test -p $DIR --verbose --color always --no-default-features --features=no-std + RUSTFLAGS="$RUSTFLAGS --cfg=c_bindings" cargo test -p $DIR --verbose --color always --no-default-features --features=no-std done -RUSTFLAGS="--cfg=c_bindings" cargo test --verbose --color always +RUSTFLAGS="$RUSTFLAGS --cfg=c_bindings" cargo test --verbose --color always # Note that outbound_commitment_test only runs in this mode because of hardcoded signature values pushd lightning @@ -125,16 +153,7 @@ popd echo -e "\n\nTesting no-std build on a downstream no-std crate" # check no-std compatibility across dependencies pushd no-std-check -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 +cargo check --verbose --color always --features lightning-transaction-sync [ "$CI_MINIMIZE_DISK_USAGE" != "" ] && cargo clean popd @@ -152,17 +171,7 @@ if [ -f "$(which arm-none-eabi-gcc)" ]; then popd fi -echo -e "\n\nTest Taproot builds" -pushd lightning -RUSTFLAGS="$RUSTFLAGS --cfg=taproot" cargo test --verbose --color always -p lightning -popd - -echo -e "\n\nTest dual-funding builds" -pushd lightning -RUSTFLAGS="$RUSTFLAGS --cfg=dual_funding" cargo test --verbose --color always -p lightning -popd - -echo -e "\n\nTest dual-funding builds" -pushd lightning -RUSTFLAGS="$RUSTFLAGS --cfg=dual_funding" cargo test --verbose --color always -p lightning -popd +echo -e "\n\nTest cfg-flag builds" +RUSTFLAGS="--cfg=taproot" cargo test --verbose --color always -p lightning +RUSTFLAGS="--cfg=async_signing" cargo test --verbose --color always -p lightning +RUSTFLAGS="--cfg=dual_funding" cargo test --verbose --color always -p lightning diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 573096efdfc..d87be2ef6a4 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -3,7 +3,7 @@ name = "lightning-fuzz" version = "0.0.1" authors = ["Automatically generated"] publish = false -edition = "2018" +edition = "2021" # Because the function is unused it gets dropped before we link lightning, so # we have to duplicate build.rs here. Note that this is only required for # fuzzing mode. @@ -22,7 +22,6 @@ lightning = { path = "../lightning", features = ["regex", "hashbrown", "_test_ut lightning-rapid-gossip-sync = { path = "../lightning-rapid-gossip-sync" } bitcoin = { version = "0.30.2", features = ["secp-lowmemory"] } hex = { package = "hex-conservative", version = "0.1.1", default-features = false } -hashbrown = "0.8" afl = { version = "0.12", optional = true } honggfuzz = { version = "0.5", optional = true, default-features = false } diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index f654908771f..36e7cea8a22 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -30,6 +30,8 @@ use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::hashes::sha256d::Hash as Sha256dHash; use bitcoin::hash_types::{BlockHash, WPubkeyHash}; +use lightning::blinded_path::BlindedPath; +use lightning::blinded_path::payment::ReceiveTlvs; use lightning::chain; use lightning::chain::{BestBlock, ChannelMonitorUpdateStatus, chainmonitor, channelmonitor, Confirm, Watch}; use lightning::chain::channelmonitor::{ChannelMonitor, MonitorEvent}; @@ -38,16 +40,18 @@ use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, use lightning::sign::{KeyMaterial, InMemorySigner, Recipient, EntropySource, NodeSigner, SignerProvider}; use lightning::events; use lightning::events::MessageSendEventsProvider; -use lightning::ln::{PaymentHash, PaymentPreimage, PaymentSecret}; +use lightning::ln::{ChannelId, PaymentHash, PaymentPreimage, PaymentSecret}; use lightning::ln::channelmanager::{ChainParameters, ChannelDetails, ChannelManager, PaymentSendFailure, ChannelManagerReadArgs, PaymentId, RecipientOnionFields}; use lightning::ln::channel::FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE; use lightning::ln::msgs::{self, CommitmentUpdate, ChannelMessageHandler, DecodeError, UpdateAddHTLC, Init}; use lightning::ln::script::ShutdownScript; use lightning::ln::functional_test_utils::*; -use lightning::offers::invoice::UnsignedBolt12Invoice; +use lightning::offers::invoice::{BlindedPayInfo, UnsignedBolt12Invoice}; use lightning::offers::invoice_request::UnsignedInvoiceRequest; +use lightning::onion_message::messenger::{Destination, MessageRouter, OnionMessagePath}; use lightning::util::test_channel_signer::{TestChannelSigner, EnforcementState}; use lightning::util::errors::APIError; +use lightning::util::hash_tables::*; use lightning::util::logger::Logger; use lightning::util::config::UserConfig; use lightning::util::ser::{Readable, ReadableArgs, Writeable, Writer}; @@ -56,14 +60,13 @@ use lightning::routing::router::{InFlightHtlcs, Path, Route, RouteHop, RoutePara use crate::utils::test_logger::{self, Output}; use crate::utils::test_persister::TestPersister; -use bitcoin::secp256k1::{Message, PublicKey, SecretKey, Scalar, Secp256k1}; +use bitcoin::secp256k1::{Message, PublicKey, SecretKey, Scalar, Secp256k1, self}; use bitcoin::secp256k1::ecdh::SharedSecret; use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature}; use bitcoin::secp256k1::schnorr; use std::mem; use std::cmp::{self, Ordering}; -use hashbrown::{HashSet, hash_map, HashMap}; use std::sync::{Arc,Mutex}; use std::sync::atomic; use std::io::Cursor; @@ -99,6 +102,27 @@ impl Router for FuzzRouter { action: msgs::ErrorAction::IgnoreError }) } + + fn create_blinded_payment_paths( + &self, _recipient: PublicKey, _first_hops: Vec, _tlvs: ReceiveTlvs, + _amount_msats: u64, _secp_ctx: &Secp256k1, + ) -> Result, ()> { + unreachable!() + } +} + +impl MessageRouter for FuzzRouter { + fn find_path( + &self, _sender: PublicKey, _peers: Vec, _destination: Destination + ) -> Result { + unreachable!() + } + + fn create_blinded_paths( + &self, _recipient: PublicKey, _peers: Vec, _secp_ctx: &Secp256k1, + ) -> Result, ()> { + unreachable!() + } } pub struct TestBroadcaster {} @@ -133,7 +157,7 @@ impl TestChainMonitor { logger, keys, persister, - latest_monitors: Mutex::new(HashMap::new()), + latest_monitors: Mutex::new(new_hash_map()), } } } @@ -149,20 +173,17 @@ impl chain::Watch for TestChainMonitor { fn update_channel(&self, funding_txo: OutPoint, update: &channelmonitor::ChannelMonitorUpdate) -> chain::ChannelMonitorUpdateStatus { let mut map_lock = self.latest_monitors.lock().unwrap(); - let mut map_entry = match map_lock.entry(funding_txo) { - hash_map::Entry::Occupied(entry) => entry, - hash_map::Entry::Vacant(_) => panic!("Didn't have monitor on update call"), - }; + let map_entry = map_lock.get_mut(&funding_txo).expect("Didn't have monitor on update call"); let deserialized_monitor = <(BlockHash, channelmonitor::ChannelMonitor)>:: - read(&mut Cursor::new(&map_entry.get().1), (&*self.keys, &*self.keys)).unwrap().1; + read(&mut Cursor::new(&map_entry.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)); + *map_entry = (update.update_id, ser.0); self.chain_monitor.update_channel(funding_txo, update) } - fn release_pending_monitor_events(&self) -> Vec<(OutPoint, Vec, Option)> { + fn release_pending_monitor_events(&self) -> Vec<(OutPoint, ChannelId, Vec, Option)> { return self.chain_monitor.release_pending_monitor_events(); } } @@ -443,7 +464,7 @@ pub fn do_test(data: &[u8], underlying_out: Out, anchors: bool) { ($node_id: expr, $fee_estimator: expr) => { { let logger: Arc = Arc::new(test_logger::TestLogger::new($node_id.to_string(), out.clone())); let node_secret = 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, $node_id]).unwrap(); - let keys_manager = Arc::new(KeyProvider { node_secret, rand_bytes_id: atomic::AtomicU32::new(0), enforcement_states: Mutex::new(HashMap::new()) }); + let keys_manager = Arc::new(KeyProvider { node_secret, rand_bytes_id: atomic::AtomicU32::new(0), enforcement_states: Mutex::new(new_hash_map()) }); let monitor = Arc::new(TestChainMonitor::new(broadcast.clone(), logger.clone(), $fee_estimator.clone(), Arc::new(TestPersister { update_ret: Mutex::new(ChannelMonitorUpdateStatus::Completed) @@ -484,13 +505,13 @@ pub fn do_test(data: &[u8], underlying_out: Out, anchors: bool) { config.manually_accept_inbound_channels = true; } - let mut monitors = HashMap::new(); + let mut monitors = new_hash_map(); 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)>::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 mut monitor_refs = new_hash_map(); for (outpoint, monitor) in monitors.iter_mut() { monitor_refs.insert(*outpoint, monitor); } @@ -957,7 +978,7 @@ pub fn do_test(data: &[u8], underlying_out: Out, anchors: bool) { // In case we get 256 payments we may have a hash collision, resulting in the // second claim/fail call not finding the duplicate-hash HTLC, so we have to // deduplicate the calls here. - let mut claim_set = HashSet::new(); + let mut claim_set = new_hash_map(); let mut events = nodes[$node].get_and_clear_pending_events(); // Sort events so that PendingHTLCsForwardable get processed last. This avoids a // case where we first process a PendingHTLCsForwardable, then claim/fail on a @@ -979,7 +1000,7 @@ pub fn do_test(data: &[u8], underlying_out: Out, anchors: bool) { for event in events.drain(..) { match event { events::Event::PaymentClaimable { payment_hash, .. } => { - if claim_set.insert(payment_hash.0) { + if claim_set.insert(payment_hash.0, ()).is_none() { if $fail { nodes[$node].fail_htlc_backwards(&payment_hash); } else { @@ -1260,6 +1281,94 @@ pub fn do_test(data: &[u8], underlying_out: Out, anchors: bool) { }, 0x89 => { fee_est_c.ret_val.store(253, atomic::Ordering::Release); nodes[2].maybe_update_chan_fees(); }, + 0xf0 => { + let pending_updates = monitor_a.chain_monitor.list_pending_monitor_updates().remove(&chan_1_funding).unwrap(); + if let Some(id) = pending_updates.get(0) { + monitor_a.chain_monitor.channel_monitor_updated(chan_1_funding, *id).unwrap(); + } + nodes[0].process_monitor_events(); + } + 0xf1 => { + let pending_updates = monitor_a.chain_monitor.list_pending_monitor_updates().remove(&chan_1_funding).unwrap(); + if let Some(id) = pending_updates.get(1) { + monitor_a.chain_monitor.channel_monitor_updated(chan_1_funding, *id).unwrap(); + } + nodes[0].process_monitor_events(); + } + 0xf2 => { + let pending_updates = monitor_a.chain_monitor.list_pending_monitor_updates().remove(&chan_1_funding).unwrap(); + if let Some(id) = pending_updates.last() { + monitor_a.chain_monitor.channel_monitor_updated(chan_1_funding, *id).unwrap(); + } + nodes[0].process_monitor_events(); + } + + 0xf4 => { + let pending_updates = monitor_b.chain_monitor.list_pending_monitor_updates().remove(&chan_1_funding).unwrap(); + if let Some(id) = pending_updates.get(0) { + monitor_b.chain_monitor.channel_monitor_updated(chan_1_funding, *id).unwrap(); + } + nodes[1].process_monitor_events(); + } + 0xf5 => { + let pending_updates = monitor_b.chain_monitor.list_pending_monitor_updates().remove(&chan_1_funding).unwrap(); + if let Some(id) = pending_updates.get(1) { + monitor_b.chain_monitor.channel_monitor_updated(chan_1_funding, *id).unwrap(); + } + nodes[1].process_monitor_events(); + } + 0xf6 => { + let pending_updates = monitor_b.chain_monitor.list_pending_monitor_updates().remove(&chan_1_funding).unwrap(); + if let Some(id) = pending_updates.last() { + monitor_b.chain_monitor.channel_monitor_updated(chan_1_funding, *id).unwrap(); + } + nodes[1].process_monitor_events(); + } + + 0xf8 => { + let pending_updates = monitor_b.chain_monitor.list_pending_monitor_updates().remove(&chan_2_funding).unwrap(); + if let Some(id) = pending_updates.get(0) { + monitor_b.chain_monitor.channel_monitor_updated(chan_2_funding, *id).unwrap(); + } + nodes[1].process_monitor_events(); + } + 0xf9 => { + let pending_updates = monitor_b.chain_monitor.list_pending_monitor_updates().remove(&chan_2_funding).unwrap(); + if let Some(id) = pending_updates.get(1) { + monitor_b.chain_monitor.channel_monitor_updated(chan_2_funding, *id).unwrap(); + } + nodes[1].process_monitor_events(); + } + 0xfa => { + let pending_updates = monitor_b.chain_monitor.list_pending_monitor_updates().remove(&chan_2_funding).unwrap(); + if let Some(id) = pending_updates.last() { + monitor_b.chain_monitor.channel_monitor_updated(chan_2_funding, *id).unwrap(); + } + nodes[1].process_monitor_events(); + } + + 0xfc => { + let pending_updates = monitor_c.chain_monitor.list_pending_monitor_updates().remove(&chan_2_funding).unwrap(); + if let Some(id) = pending_updates.get(0) { + monitor_c.chain_monitor.channel_monitor_updated(chan_2_funding, *id).unwrap(); + } + nodes[2].process_monitor_events(); + } + 0xfd => { + let pending_updates = monitor_c.chain_monitor.list_pending_monitor_updates().remove(&chan_2_funding).unwrap(); + if let Some(id) = pending_updates.get(1) { + monitor_c.chain_monitor.channel_monitor_updated(chan_2_funding, *id).unwrap(); + } + nodes[2].process_monitor_events(); + } + 0xfe => { + let pending_updates = monitor_c.chain_monitor.list_pending_monitor_updates().remove(&chan_2_funding).unwrap(); + if let Some(id) = pending_updates.last() { + monitor_c.chain_monitor.channel_monitor_updated(chan_2_funding, *id).unwrap(); + } + nodes[2].process_monitor_events(); + } + 0xff => { // Test that no channel is in a stuck state where neither party can send funds even // after we resolve all pending events. diff --git a/fuzz/src/full_stack.rs b/fuzz/src/full_stack.rs index 725f83af984..e128d91810a 100644 --- a/fuzz/src/full_stack.rs +++ b/fuzz/src/full_stack.rs @@ -22,12 +22,13 @@ use bitcoin::consensus::encode::deserialize; use bitcoin::network::constants::Network; use bitcoin::hashes::hex::FromHex; -use bitcoin::hashes::Hash as TraitImport; -use bitcoin::hashes::HashEngine as TraitImportEngine; +use bitcoin::hashes::Hash as _; use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::hashes::sha256d::Hash as Sha256dHash; use bitcoin::hash_types::{Txid, BlockHash, WPubkeyHash}; +use lightning::blinded_path::BlindedPath; +use lightning::blinded_path::payment::ReceiveTlvs; use lightning::chain; use lightning::chain::{BestBlock, ChannelMonitorUpdateStatus, Confirm, Listen}; use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator}; @@ -36,32 +37,33 @@ use lightning::chain::transaction::OutPoint; use lightning::sign::{InMemorySigner, Recipient, KeyMaterial, EntropySource, NodeSigner, SignerProvider}; use lightning::events::Event; use lightning::ln::{ChannelId, PaymentHash, PaymentPreimage, PaymentSecret}; -use lightning::ln::channelmanager::{ChainParameters, ChannelDetails, ChannelManager, PaymentId, RecipientOnionFields, Retry}; +use lightning::ln::channelmanager::{ChainParameters, ChannelDetails, ChannelManager, PaymentId, RecipientOnionFields, Retry, InterceptId}; 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::{BlindedPayInfo, UnsignedBolt12Invoice}; use lightning::offers::invoice_request::UnsignedInvoiceRequest; +use lightning::onion_message::messenger::{Destination, MessageRouter, OnionMessagePath}; 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, MaxDustHTLCExposure}; +use lightning::util::config::{ChannelConfig, UserConfig}; +use lightning::util::hash_tables::*; use lightning::util::errors::APIError; use lightning::util::test_channel_signer::{TestChannelSigner, EnforcementState}; use lightning::util::logger::Logger; -use lightning::util::ser::{ReadableArgs, Writeable}; +use lightning::util::ser::{Readable, ReadableArgs, Writeable}; use crate::utils::test_logger; use crate::utils::test_persister::TestPersister; -use bitcoin::secp256k1::{Message, PublicKey, SecretKey, Scalar, Secp256k1}; +use bitcoin::secp256k1::{Message, PublicKey, SecretKey, Scalar, Secp256k1, self}; use bitcoin::secp256k1::ecdh::SharedSecret; use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature}; use bitcoin::secp256k1::schnorr; use std::cell::RefCell; -use hashbrown::{HashMap, hash_map}; use std::convert::TryInto; use std::cmp; use std::sync::{Arc, Mutex}; @@ -74,6 +76,14 @@ pub fn slice_to_be16(v: &[u8]) -> u16 { ((v[1] as u16) << 8*0) } +#[inline] +pub fn be16_to_array(u: u16) -> [u8; 2] { + let mut v = [0; 2]; + v[0] = ((u >> 8*1) & 0xff) as u8; + v[1] = ((u >> 8*0) & 0xff) as u8; + v +} + #[inline] pub fn slice_to_be24(v: &[u8]) -> u32 { ((v[0] as u32) << 8*2) | @@ -81,28 +91,6 @@ pub fn slice_to_be24(v: &[u8]) -> u32 { ((v[2] as u32) << 8*0) } -#[inline] -pub fn slice_to_be32(v: &[u8]) -> u32 { - ((v[0] as u32) << 8*3) | - ((v[1] as u32) << 8*2) | - ((v[2] as u32) << 8*1) | - ((v[3] as u32) << 8*0) -} - -#[inline] -pub fn be64_to_array(u: u64) -> [u8; 8] { - let mut v = [0; 8]; - v[0] = ((u >> 8*7) & 0xff) as u8; - v[1] = ((u >> 8*6) & 0xff) as u8; - v[2] = ((u >> 8*5) & 0xff) as u8; - v[3] = ((u >> 8*4) & 0xff) as u8; - v[4] = ((u >> 8*3) & 0xff) as u8; - v[5] = ((u >> 8*2) & 0xff) as u8; - v[6] = ((u >> 8*1) & 0xff) as u8; - v[7] = ((u >> 8*0) & 0xff) as u8; - v -} - struct InputData { data: Vec, read_pos: AtomicUsize, @@ -116,6 +104,16 @@ impl InputData { Some(&self.data[old_pos..old_pos + len]) } } +impl std::io::Read for &InputData { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + if let Some(sl) = self.get_slice(buf.len()) { + buf.copy_from_slice(sl); + Ok(buf.len()) + } else { + Ok(0) + } + } +} struct FuzzEstimator { input: Arc, @@ -142,6 +140,27 @@ impl Router for FuzzRouter { action: msgs::ErrorAction::IgnoreError }) } + + fn create_blinded_payment_paths( + &self, _recipient: PublicKey, _first_hops: Vec, _tlvs: ReceiveTlvs, + _amount_msats: u64, _secp_ctx: &Secp256k1, + ) -> Result, ()> { + unreachable!() + } +} + +impl MessageRouter for FuzzRouter { + fn find_path( + &self, _sender: PublicKey, _peers: Vec, _destination: Destination + ) -> Result { + unreachable!() + } + + fn create_blinded_paths( + &self, _recipient: PublicKey, _peers: Vec, _secp_ctx: &Secp256k1, + ) -> Result, ()> { + unreachable!() + } } struct TestBroadcaster { @@ -210,7 +229,7 @@ impl<'a> MoneyLossDetector<'a> { peers, funding_txn: Vec::new(), - txids_confirmed: HashMap::new(), + txids_confirmed: new_hash_map(), header_hashes: vec![(genesis_block(Network::Bitcoin).block_hash(), 0)], height: 0, max_height: 0, @@ -222,13 +241,10 @@ impl<'a> MoneyLossDetector<'a> { let mut txdata = Vec::with_capacity(all_txn.len()); for (idx, tx) in all_txn.iter().enumerate() { let txid = tx.txid(); - match self.txids_confirmed.entry(txid) { - hash_map::Entry::Vacant(e) => { - e.insert(self.height); - txdata.push((idx + 1, tx)); - }, - _ => {}, - } + self.txids_confirmed.entry(txid).or_insert_with(|| { + txdata.push((idx + 1, tx)); + self.height + }); } self.blocks_connected += 1; @@ -410,7 +426,17 @@ impl SignerProvider for KeyProvider { } #[inline] -pub fn do_test(data: &[u8], logger: &Arc) { +pub fn do_test(mut data: &[u8], logger: &Arc) { + if data.len() < 32 { return; } + + let our_network_key = match SecretKey::from_slice(&data[..32]) { + Ok(key) => key, + Err(_) => return, + }; + data = &data[32..]; + + let config: UserConfig = if let Ok(config) = Readable::read(&mut data) { config } else { return; }; + let input = Arc::new(InputData { data: data.to_vec(), read_pos: AtomicUsize::new(0), @@ -429,6 +455,17 @@ pub fn do_test(data: &[u8], logger: &Arc) { } } + macro_rules! get_bytes { + ($len: expr) => { { + let mut res = [0; $len]; + match input.get_slice($len as usize) { + Some(slice) => res.copy_from_slice(slice), + None => return, + } + res + } } + } + macro_rules! get_pubkey { () => { match PublicKey::from_slice(get_slice!(33)) { @@ -438,10 +475,6 @@ pub fn do_test(data: &[u8], logger: &Arc) { } } - let our_network_key = match SecretKey::from_slice(get_slice!(32)) { - Ok(key) => key, - Err(_) => return, - }; let inbound_payment_key = [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, 42]; @@ -453,12 +486,8 @@ pub fn do_test(data: &[u8], logger: &Arc) { node_secret: our_network_key.clone(), inbound_payment_key: KeyMaterial(inbound_payment_key.try_into().unwrap()), counter: AtomicU64::new(0), - signer_state: RefCell::new(HashMap::new()) + signer_state: RefCell::new(new_hash_map()) }); - 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 params = ChainParameters { @@ -483,9 +512,10 @@ pub fn do_test(data: &[u8], logger: &Arc) { let mut should_forward = false; let mut payments_received: Vec = Vec::new(); - let mut payments_sent = 0; + let mut intercepted_htlcs: Vec = Vec::new(); + let mut payments_sent: u16 = 0; let mut pending_funding_generation: Vec<(ChannelId, PublicKey, u64, ScriptBuf)> = Vec::new(); - let mut pending_funding_signatures = HashMap::new(); + let mut pending_funding_signatures = new_hash_map(); loop { match get_slice!(1)[0] { @@ -533,16 +563,13 @@ pub fn do_test(data: &[u8], logger: &Arc) { 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)); + payment_hash.0[0..2].copy_from_slice(&be16_to_array(payments_sent)); payment_hash.0 = Sha256::hash(&payment_hash.0[..]).to_byte_array(); payments_sent += 1; - match channelmanager.send_payment(payment_hash, - RecipientOnionFields::spontaneous_empty(), PaymentId(payment_hash.0), params, - Retry::Attempts(0)) - { - Ok(_) => {}, - Err(_) => return, - } + let _ = channelmanager.send_payment( + payment_hash, RecipientOnionFields::spontaneous_empty(), + PaymentId(payment_hash.0), params, Retry::Attempts(2) + ); }, 15 => { let final_value_msat = slice_to_be24(get_slice!(3)) as u64; @@ -550,19 +577,29 @@ pub fn do_test(data: &[u8], logger: &Arc) { 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)); + payment_hash.0[0..2].copy_from_slice(&be16_to_array(payments_sent)); payment_hash.0 = Sha256::hash(&payment_hash.0[..]).to_byte_array(); payments_sent += 1; let mut payment_secret = PaymentSecret([0; 32]); - payment_secret.0[0..8].copy_from_slice(&be64_to_array(payments_sent)); + payment_secret.0[0..2].copy_from_slice(&be16_to_array(payments_sent)); payments_sent += 1; - match channelmanager.send_payment(payment_hash, - RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_hash.0), - params, Retry::Attempts(0)) - { - Ok(_) => {}, - Err(_) => return, - } + let _ = channelmanager.send_payment( + payment_hash, RecipientOnionFields::secret_only(payment_secret), + PaymentId(payment_hash.0), params, Retry::Attempts(2) + ); + }, + 17 => { + 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::from_payment_params_and_value( + payment_params, final_value_msat); + let _ = channelmanager.send_preflight_probes(params, None); + }, + 18 => { + let idx = u16::from_be_bytes(get_bytes!(2)) % cmp::max(payments_sent, 1); + let mut payment_id = PaymentId([0; 32]); + payment_id.0[0..2].copy_from_slice(&idx.to_be_bytes()); + channelmanager.abandon_payment(payment_id); }, 5 => { let peer_id = get_slice!(1)[0]; @@ -613,33 +650,48 @@ pub fn do_test(data: &[u8], logger: &Arc) { } }, 10 => { - 'outer_loop: for funding_generation in pending_funding_generation.drain(..) { - let mut tx = Transaction { version: 0, lock_time: LockTime::ZERO, input: Vec::new(), output: vec![TxOut { - value: funding_generation.2, script_pubkey: funding_generation.3, - }] }; - let funding_output = 'search_loop: loop { - let funding_txid = tx.txid(); - if let None = loss_detector.txids_confirmed.get(&funding_txid) { - let outpoint = OutPoint { txid: funding_txid, index: 0 }; - for chan in channelmanager.list_channels() { - if chan.channel_id == outpoint.to_channel_id() { - tx.version += 1; - continue 'search_loop; - } + let mut tx = Transaction { version: 0, lock_time: LockTime::ZERO, input: Vec::new(), output: Vec::new() }; + let mut channels = Vec::new(); + for funding_generation in pending_funding_generation.drain(..) { + let txout = TxOut { + value: funding_generation.2, script_pubkey: funding_generation.3, + }; + if !tx.output.contains(&txout) { + tx.output.push(txout); + channels.push((funding_generation.0, funding_generation.1)); + } + } + // Once we switch to V2 channel opens we should be able to drop this entirely as + // channel_ids no longer change when we set the funding tx. + 'search_loop: loop { + if tx.version > 0xff { + break; + } + let funding_txid = tx.txid(); + if loss_detector.txids_confirmed.get(&funding_txid).is_none() { + let outpoint = OutPoint { txid: funding_txid, index: 0 }; + for chan in channelmanager.list_channels() { + if chan.channel_id == ChannelId::v1_from_funding_outpoint(outpoint) { + tx.version += 1; + continue 'search_loop; } - break outpoint; - } - tx.version += 1; - if tx.version > 0xff { - continue 'outer_loop; } - }; - if let Err(e) = channelmanager.funding_transaction_generated(&funding_generation.0, &funding_generation.1, tx.clone()) { + break; + } + tx.version += 1; + } + if tx.version <= 0xff && !channels.is_empty() { + let chans = channels.iter().map(|(a, b)| (a, b)).collect::>(); + if let Err(e) = channelmanager.batch_funding_transaction_generated(&chans, tx.clone()) { // It's possible the channel has been closed in the mean time, but any other // failure may be a bug. if let APIError::ChannelUnavailable { .. } = e { } else { panic!(); } } - pending_funding_signatures.insert(funding_output, tx); + let funding_txid = tx.txid(); + for idx in 0..tx.output.len() { + let outpoint = OutPoint { txid: funding_txid, index: idx as u16 }; + pending_funding_signatures.insert(outpoint, tx.clone()); + } } }, 11 => { @@ -655,7 +707,7 @@ pub fn do_test(data: &[u8], logger: &Arc) { } }, 12 => { - let txlen = slice_to_be16(get_slice!(2)); + let txlen = u16::from_be_bytes(get_bytes!(2)); if txlen == 0 { loss_detector.connect_block(&[]); } else { @@ -683,7 +735,46 @@ pub fn do_test(data: &[u8], logger: &Arc) { channels.sort_by(|a, b| { a.channel_id.cmp(&b.channel_id) }); channelmanager.force_close_broadcasting_latest_txn(&channels[channel_id].channel_id, &channels[channel_id].counterparty.node_id).unwrap(); }, - // 15 is above + // 15, 16, 17, 18 is above + 19 => { + let mut list = loss_detector.handler.list_peers(); + list.sort_by_key(|v| v.counterparty_node_id); + if let Some(peer_details) = list.get(0) { + loss_detector.handler.disconnect_by_node_id(peer_details.counterparty_node_id); + } + }, + 20 => loss_detector.handler.disconnect_all_peers(), + 21 => loss_detector.handler.timer_tick_occurred(), + 22 => + loss_detector.handler.broadcast_node_announcement([42; 3], [43; 32], Vec::new()), + 32 => channelmanager.timer_tick_occurred(), + 33 => { + for id in intercepted_htlcs.drain(..) { + channelmanager.fail_intercepted_htlc(id).unwrap(); + } + } + 34 => { + let amt = u64::from_be_bytes(get_bytes!(8)); + let chans = channelmanager.list_channels(); + for id in intercepted_htlcs.drain(..) { + if chans.is_empty() { + channelmanager.fail_intercepted_htlc(id).unwrap(); + } else { + let chan = &chans[amt as usize % chans.len()]; + channelmanager.forward_intercepted_htlc(id, &chan.channel_id, chan.counterparty.node_id, amt).unwrap(); + } + } + } + 35 => { + let config: ChannelConfig = + if let Ok(c) = Readable::read(&mut &*input) { c } else { return; }; + let chans = channelmanager.list_channels(); + if let Some(chan) = chans.get(0) { + let _ = channelmanager.update_channel_config( + &chan.counterparty.node_id, &[chan.channel_id], &config + ); + } + } _ => return, } loss_detector.handler.process_events(); @@ -699,6 +790,11 @@ pub fn do_test(data: &[u8], logger: &Arc) { Event::PendingHTLCsForwardable {..} => { should_forward = true; }, + Event::HTLCIntercepted { intercept_id, .. } => { + if !intercepted_htlcs.contains(&intercept_id) { + intercepted_htlcs.push(intercept_id); + } + }, _ => {}, } } @@ -734,6 +830,12 @@ mod tests { } } + fn ext_from_hex(hex_with_spaces: &str, out: &mut Vec) { + for hex in hex_with_spaces.split(" ") { + out.append(&mut >::from_hex(hex).unwrap()); + } + } + #[test] fn test_no_existing_test_breakage() { // To avoid accidentally causing all existing fuzz test cases to be useless by making minor @@ -742,9 +844,6 @@ mod tests { // so this should be updated pretty liberally, but at least we'll know when changes occur. // If nothing else, this test serves as a pretty great initial full_stack_target seed. - // What each byte represents is broken down below, and then everything is concatenated into - // one large test at the end (you want %s/ -.*//g %s/\n\| \|\t\|\///g). - // Following BOLT 8, lightning message on the wire are: 2-byte encrypted message length + // 16-byte MAC of the encrypted message length + encrypted Lightning message + 16-byte MAC // of the Lightning message @@ -753,285 +852,446 @@ mod tests { // Writing new code generating transactions and see a new failure ? Don't forget to add input for the FuzzEstimator ! - // 0100000000000000000000000000000000000000000000000000000000000000 - our network key - // 00000000 - fee_proportional_millionths - // 01 - announce_channels_publicly - // - // 00 - new outbound connection with id 0 - // 030000000000000000000000000000000000000000000000000000000000000002 - peer's pubkey - // 030032 - inbound read from peer id 0 of len 50 - // 00 030000000000000000000000000000000000000000000000000000000000000002 03000000000000000000000000000000 - noise act two (0||pubkey||mac) - // - // 030012 - inbound read from peer id 0 of len 18 - // 0010 03000000000000000000000000000000 - message header indicating message length 16 - // 030020 - inbound read from peer id 0 of len 32 - // 0010 00021aaa 0008aaaaaaaaaaaa9aaa 03000000000000000000000000000000 - init message (type 16) with static_remotekey required and other bits optional and mac - // - // 030012 - inbound read from peer id 0 of len 18 - // 0147 03000000000000000000000000000000 - message header indicating message length 327 - // 0300fe - inbound read from peer id 0 of len 254 - // 0020 6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000 ff4f00f805273c1b203bb5ebf8436bfde57b3be8c2f5e95d9491dbb181909679 000000000000c350 0000000000000000 0000000000000162 ffffffffffffffff 0000000000000222 0000000000000000 000000fd 0006 01e3 030000000000000000000000000000000000000000000000000000000000000001 030000000000000000000000000000000000000000000000000000000000000002 030000000000000000000000000000000000000000000000000000000000000003 030000000000000000000000000000000000000000000000000000000000000004 - beginning of open_channel message - // 030059 - inbound read from peer id 0 of len 89 - // 030000000000000000000000000000000000000000000000000000000000000005 020900000000000000000000000000000000000000000000000000000000000000 01 0000 01021000 03000000000000000000000000000000 - rest of open_channel and mac - // - // 00fd - Two feerate requests (all returning min feerate, which our open_channel also uses) (gonna be ingested by FuzzEstimator) - // - client should now respond with accept_channel (CHECK 1: type 33 to peer 03000000) - // - // 030012 - inbound read from peer id 0 of len 18 - // 0084 03000000000000000000000000000000 - message header indicating message length 132 - // 030094 - inbound read from peer id 0 of len 148 - // 0022 ff4f00f805273c1b203bb5ebf8436bfde57b3be8c2f5e95d9491dbb181909679 3d00000000000000000000000000000000000000000000000000000000000000 0000 00000000000000000000000000000000000000000000000000000000000000210100000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000 - funding_created and mac - // - client should now respond with funding_signed (CHECK 2: type 35 to peer 03000000) - // - // 0c005e - connect a block with one transaction of len 94 - // 020000000100000000000000000000000000000000000000000000000000000000000000000000000000ffffffff0150c3000000000000220020ae0000000000000000000000000000000000000000000000000000000000000000000000 - the funding transaction - // 0c0000 - connect a block with no transactions - // 0c0000 - connect a block with no transactions - // 0c0000 - connect a block with no transactions - // 0c0000 - connect a block with no transactions - // 0c0000 - connect a block with no transactions - // 0c0000 - connect a block with no transactions - // 0c0000 - connect a block with no transactions - // 0c0000 - connect a block with no transactions - // 0c0000 - connect a block with no transactions - // 0c0000 - connect a block with no transactions - // 0c0000 - connect a block with no transactions - // 0c0000 - connect a block with no transactions - // - by now client should have sent a channel_ready (CHECK 3: SendChannelReady to 03000000 for chan 3d000000) - // - // 030012 - inbound read from peer id 0 of len 18 - // 0043 03000000000000000000000000000000 - message header indicating message length 67 - // 030053 - inbound read from peer id 0 of len 83 - // 0024 3d00000000000000000000000000000000000000000000000000000000000000 020800000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000 - channel_ready and mac - // - // 01 - new inbound connection with id 1 - // 030132 - inbound read from peer id 1 of len 50 - // 0003000000000000000000000000000000000000000000000000000000000000000703000000000000000000000000000000 - inbound noise act 1 - // 030142 - inbound read from peer id 1 of len 66 - // 000302000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003000000000000000000000000000000 - inbound noise act 3 - // - // 030112 - inbound read from peer id 1 of len 18 - // 0010 01000000000000000000000000000000 - message header indicating message length 16 - // 030120 - inbound read from peer id 1 of len 32 - // 0010 00021aaa 0008aaaaaaaaaaaa9aaa 01000000000000000000000000000000 - init message (type 16) with static_remotekey required and other bits optional and mac - // - // 05 01 030200000000000000000000000000000000000000000000000000000000000000 00c350 0003e8 - create outbound channel to peer 1 for 50k sat - // 00fd - One feerate requests (all returning min feerate) (gonna be ingested by FuzzEstimator) - // - // 030112 - inbound read from peer id 1 of len 18 - // 0112 01000000000000000000000000000000 - message header indicating message length 274 - // 0301ff - inbound read from peer id 1 of len 255 - // 0021 0000000000000000000000000000000000000000000000000000000000000e05 0000000000000162 00000000004c4b40 00000000000003e8 00000000000003e8 00000002 03f0 0005 030000000000000000000000000000000000000000000000000000000000000100 030000000000000000000000000000000000000000000000000000000000000200 030000000000000000000000000000000000000000000000000000000000000300 030000000000000000000000000000000000000000000000000000000000000400 030000000000000000000000000000000000000000000000000000000000000500 02660000000000000000000000000000 - beginning of accept_channel - // 030123 - inbound read from peer id 1 of len 35 - // 0000000000000000000000000000000000 0000 01000000000000000000000000000000 - rest of accept_channel and mac - // - // 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 - // 0023 3a00000000000000000000000000000000000000000000000000000000000000 000000000000000000000000000000000000000000000000000000000000007c0001000000000000000000000000000000000000000000000000000000000000 01000000000000000000000000000000 - funding_signed message and mac - // - // 0b - broadcast funding transaction - // - by now client should have sent a channel_ready (CHECK 4: SendChannelReady to 03020000 for chan 3f000000) - // - // 030112 - inbound read from peer id 1 of len 18 - // 0043 01000000000000000000000000000000 - message header indicating message length 67 - // 030153 - inbound read from peer id 1 of len 83 - // 0024 3a00000000000000000000000000000000000000000000000000000000000000 026700000000000000000000000000000000000000000000000000000000000000 01000000000000000000000000000000 - channel_ready and mac - // - // 030012 - inbound read from peer id 0 of len 18 - // 05ac 03000000000000000000000000000000 - message header indicating message length 1452 - // 0300ff - inbound read from peer id 0 of len 255 - // 0080 3d00000000000000000000000000000000000000000000000000000000000000 0000000000000000 0000000000003e80 ff00000000000000000000000000000000000000000000000000000000000000 000003f0 00 030000000000000000000000000000000000000000000000000000000000000555 11 020203e8 0401a0 060800000e0000010000 0a00000000000000000000000000000000000000000000000000000000000000 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff - beginning of update_add_htlc from 0 to 1 via client - // 0300ff - inbound read from peer id 0 of len 255 - // ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff - // 0300ff - inbound read from peer id 0 of len 255 - // ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff - // 0300ff - inbound read from peer id 0 of len 255 - // ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff - // 0300ff - inbound read from peer id 0 of len 255 - // ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff - // 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 - // 0084 3d00000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000300100000000000000000000000000000000000000000000000000000000000000 0000 03000000000000000000000000000000 - commitment_signed and mac - // - client should now respond with revoke_and_ack and commitment_signed (CHECK 5/6: types 133 and 132 to peer 03000000) - // - // 030012 - inbound read from peer id 0 of len 18 - // 0063 03000000000000000000000000000000 - message header indicating message length 99 - // 030073 - inbound read from peer id 0 of len 115 - // 0085 3d00000000000000000000000000000000000000000000000000000000000000 0900000000000000000000000000000000000000000000000000000000000000 020b00000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000 - revoke_and_ack and mac - // - // 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 - // 030174 - inbound read from peer id 1 of len 116 - // 0084 3a00000000000000000000000000000000000000000000000000000000000000 000000000000000000000000000000000000000000000000000000000000006a0001000000000000000000000000000000000000000000000000000000000000 0000 01000000000000000000000000000000 - commitment_signed and mac - // - // 030112 - inbound read from peer id 1 of len 18 - // 0063 01000000000000000000000000000000 - message header indicating message length 99 - // 030173 - inbound read from peer id 1 of len 115 - // 0085 3a00000000000000000000000000000000000000000000000000000000000000 6600000000000000000000000000000000000000000000000000000000000000 026400000000000000000000000000000000000000000000000000000000000000 01000000000000000000000000000000 - revoke_and_ack and mac - // - // 030112 - inbound read from peer id 1 of len 18 - // 004a 01000000000000000000000000000000 - message header indicating message length 74 - // 03015a - inbound read from peer id 1 of len 90 - // 0082 3a00000000000000000000000000000000000000000000000000000000000000 0000000000000000 ff00888888888888888888888888888888888888888888888888888888888888 01000000000000000000000000000000 - update_fulfill_htlc and mac - // - client should immediately claim the pending HTLC from peer 0 (CHECK 8: SendFulfillHTLCs for node 03000000 with preimage ff00888888 for channel 3d000000) - // - // 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 - // 0084 3a00000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000100001000000000000000000000000000000000000000000000000000000000000 0000 01000000000000000000000000000000 - commitment_signed and mac - // - // 030112 - inbound read from peer id 1 of len 18 - // 0063 01000000000000000000000000000000 - message header indicating message length 99 - // 030173 - inbound read from peer id 1 of len 115 - // 0085 3a00000000000000000000000000000000000000000000000000000000000000 6700000000000000000000000000000000000000000000000000000000000000 026500000000000000000000000000000000000000000000000000000000000000 01000000000000000000000000000000 - revoke_and_ack and mac - // - // - before responding to the commitment_signed generated above, send a new HTLC - // 030012 - inbound read from peer id 0 of len 18 - // 05ac 03000000000000000000000000000000 - message header indicating message length 1452 - // 0300ff - inbound read from peer id 0 of len 255 - // 0080 3d00000000000000000000000000000000000000000000000000000000000000 0000000000000001 0000000000003e80 ff00000000000000000000000000000000000000000000000000000000000000 000003f0 00 030000000000000000000000000000000000000000000000000000000000000555 11 020203e8 0401a0 060800000e0000010000 0a00000000000000000000000000000000000000000000000000000000000000 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff - beginning of update_add_htlc from 0 to 1 via client - // 0300ff - inbound read from peer id 0 of len 255 - // ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff - // 0300ff - inbound read from peer id 0 of len 255 - // ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff - // 0300ff - inbound read from peer id 0 of len 255 - // ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff - // 0300ff - inbound read from peer id 0 of len 255 - // ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff - // 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 - // 030073 - inbound read from peer id 0 of len 115 - // 0085 3d00000000000000000000000000000000000000000000000000000000000000 0800000000000000000000000000000000000000000000000000000000000000 020a00000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000 - revoke_and_ack and mac - // - client should now respond with revoke_and_ack and commitment_signed (CHECK 5/6 duplicates) - // - // 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 - // 0084 3d00000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000c30100000000000000000000000000000000000000000000000000000000000000 0000 03000000000000000000000000000000 - commitment_signed and mac - // - // 030012 - inbound read from peer id 0 of len 18 - // 0063 03000000000000000000000000000000 - message header indicating message length 99 - // 030073 - inbound read from peer id 0 of len 115 - // 0085 3d00000000000000000000000000000000000000000000000000000000000000 0b00000000000000000000000000000000000000000000000000000000000000 020d00000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000 - revoke_and_ack and mac - // - // 07 - process the now-pending HTLC forward - // - 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 - // 0084 3a00000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000390001000000000000000000000000000000000000000000000000000000000000 0000 01000000000000000000000000000000 - commitment_signed and mac - // - // 030112 - inbound read from peer id 1 of len 18 - // 0063 01000000000000000000000000000000 - message header indicating message length 99 - // 030173 - inbound read from peer id 1 of len 115 - // 0085 3a00000000000000000000000000000000000000000000000000000000000000 6400000000000000000000000000000000000000000000000000000000000000 027000000000000000000000000000000000000000000000000000000000000000 01000000000000000000000000000000 - revoke_and_ack and mac - // - // 030112 - inbound read from peer id 1 of len 18 - // 002c 01000000000000000000000000000000 - message header indicating message length 44 - // 03013c - inbound read from peer id 1 of len 60 - // 0083 3a00000000000000000000000000000000000000000000000000000000000000 0000000000000001 0000 01000000000000000000000000000000 - update_fail_htlc and mac - // - // 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 - // 0084 3a00000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000390001000000000000000000000000000000000000000000000000000000000000 0000 01000000000000000000000000000000 - commitment_signed and mac - // - // 030112 - inbound read from peer id 1 of len 18 - // 0063 01000000000000000000000000000000 - message header indicating message length 99 - // 030173 - inbound read from peer id 1 of len 115 - // 0085 3a00000000000000000000000000000000000000000000000000000000000000 6500000000000000000000000000000000000000000000000000000000000000 027100000000000000000000000000000000000000000000000000000000000000 01000000000000000000000000000000 - revoke_and_ack and mac - // - // 07 - process the now-pending HTLC forward - // - client now sends id 0 update_fail_htlc and commitment_signed (CHECK 9) - // - now respond to the update_fail_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 - // 030073 - inbound read from peer id 0 of len 115 - // 0085 3d00000000000000000000000000000000000000000000000000000000000000 0a00000000000000000000000000000000000000000000000000000000000000 020c00000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000 - revoke_and_ack and mac - // - // 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 - // 0084 3d00000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000320100000000000000000000000000000000000000000000000000000000000000 0000 03000000000000000000000000000000 - commitment_signed and mac - // - client should now respond with revoke_and_ack (CHECK 5 duplicate) - // - // 030012 - inbound read from peer id 0 of len 18 - // 05ac 03000000000000000000000000000000 - message header indicating message length 1452 - // 0300ff - inbound read from peer id 0 of len 255 - // 0080 3d00000000000000000000000000000000000000000000000000000000000000 0000000000000002 00000000000b0838 ff00000000000000000000000000000000000000000000000000000000000000 000003f0 00 030000000000000000000000000000000000000000000000000000000000000555 12 02030927c0 0401a0 060800000e0000010000 0a00000000000000000000000000000000000000000000000000000000000000 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff - beginning of update_add_htlc from 0 to 1 via client - // 0300ff - inbound read from peer id 0 of len 255 - // ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff - // 0300ff - inbound read from peer id 0 of len 255 - // ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff - // 0300ff - inbound read from peer id 0 of len 255 - // ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff - // 0300ff - inbound read from peer id 0 of len 255 - // ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff - // 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 - // 0084 3d00000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000750100000000000000000000000000000000000000000000000000000000000000 0001 00000000000000000000000000000000000000000000000000000000000000670500000000000000000000000000000000000000000000000000000000000006 03000000000000000000000000000000 - commitment_signed and mac - // - client should now respond with revoke_and_ack and commitment_signed (CHECK 5/6 duplicates) - // - // 030012 - inbound read from peer id 0 of len 18 - // 0063 03000000000000000000000000000000 - message header indicating message length 99 - // 030073 - inbound read from peer id 0 of len 115 - // 0085 3d00000000000000000000000000000000000000000000000000000000000000 0d00000000000000000000000000000000000000000000000000000000000000 020f00000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000 - revoke_and_ack and mac - // - // 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) + let mut test = Vec::new(); + // our network key + ext_from_hex("0100000000000000000000000000000000000000000000000000000000000000", &mut test); + // config + ext_from_hex("0000000000900000000000000000640001000000000001ffff0000000000000000ffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff000000ffffffff00ffff1a000400010000020400000000040200000a08ffffffffffffffff0001000000", &mut test); + + // new outbound connection with id 0 + ext_from_hex("00", &mut test); + // peer's pubkey + ext_from_hex("030000000000000000000000000000000000000000000000000000000000000002", &mut test); + // inbound read from peer id 0 of len 50 + ext_from_hex("030032", &mut test); + // noise act two (0||pubkey||mac) + ext_from_hex("00 030000000000000000000000000000000000000000000000000000000000000002 03000000000000000000000000000000", &mut test); + + // inbound read from peer id 0 of len 18 + ext_from_hex("030012", &mut test); + // message header indicating message length 16 + ext_from_hex("0010 03000000000000000000000000000000", &mut test); + // inbound read from peer id 0 of len 32 + ext_from_hex("030020", &mut test); + // init message (type 16) with static_remotekey required, no channel_type/anchors/taproot, and other bits optional and mac + ext_from_hex("0010 00021aaa 0008aaa20aaa2a0a9aaa 03000000000000000000000000000000", &mut test); + + // inbound read from peer id 0 of len 18 + ext_from_hex("030012", &mut test); + // message header indicating message length 327 + ext_from_hex("0147 03000000000000000000000000000000", &mut test); + // inbound read from peer id 0 of len 254 + ext_from_hex("0300fe", &mut test); + // beginning of open_channel message + ext_from_hex("0020 6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000 ff4f00f805273c1b203bb5ebf8436bfde57b3be8c2f5e95d9491dbb181909679 000000000000c350 0000000000000000 0000000000000162 ffffffffffffffff 0000000000000222 0000000000000000 000000fd 0006 01e3 030000000000000000000000000000000000000000000000000000000000000001 030000000000000000000000000000000000000000000000000000000000000002 030000000000000000000000000000000000000000000000000000000000000003 030000000000000000000000000000000000000000000000000000000000000004", &mut test); + // inbound read from peer id 0 of len 89 + ext_from_hex("030059", &mut test); + // rest of open_channel and mac + ext_from_hex("030000000000000000000000000000000000000000000000000000000000000005 020900000000000000000000000000000000000000000000000000000000000000 01 0000 01021000 03000000000000000000000000000000", &mut test); + + // One feerate request returning min feerate, which our open_channel also uses (ingested by FuzzEstimator) + ext_from_hex("00fd", &mut test); + // client should now respond with accept_channel (CHECK 1: type 33 to peer 03000000) + + // inbound read from peer id 0 of len 18 + ext_from_hex("030012", &mut test); + // message header indicating message length 132 + ext_from_hex("0084 03000000000000000000000000000000", &mut test); + // inbound read from peer id 0 of len 148 + ext_from_hex("030094", &mut test); + // funding_created and mac + ext_from_hex("0022 ff4f00f805273c1b203bb5ebf8436bfde57b3be8c2f5e95d9491dbb181909679 3d00000000000000000000000000000000000000000000000000000000000000 0000 00000000000000000000000000000000000000000000000000000000000000210100000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000", &mut test); + // client should now respond with funding_signed (CHECK 2: type 35 to peer 03000000) + + // connect a block with one transaction of len 94 + ext_from_hex("0c005e", &mut test); + // the funding transaction + ext_from_hex("020000000100000000000000000000000000000000000000000000000000000000000000000000000000ffffffff0150c3000000000000220020ae0000000000000000000000000000000000000000000000000000000000000000000000", &mut test); + // connect a block with no transactions, one per line + ext_from_hex("0c0000", &mut test); + ext_from_hex("0c0000", &mut test); + ext_from_hex("0c0000", &mut test); + ext_from_hex("0c0000", &mut test); + ext_from_hex("0c0000", &mut test); + ext_from_hex("0c0000", &mut test); + ext_from_hex("0c0000", &mut test); + ext_from_hex("0c0000", &mut test); + ext_from_hex("0c0000", &mut test); + ext_from_hex("0c0000", &mut test); + ext_from_hex("0c0000", &mut test); + ext_from_hex("0c0000", &mut test); + // by now client should have sent a channel_ready (CHECK 3: SendChannelReady to 03000000 for chan 3d000000) + + // inbound read from peer id 0 of len 18 + ext_from_hex("030012", &mut test); + // message header indicating message length 67 + ext_from_hex("0043 03000000000000000000000000000000", &mut test); + // inbound read from peer id 0 of len 83 + ext_from_hex("030053", &mut test); + // channel_ready and mac + ext_from_hex("0024 3d00000000000000000000000000000000000000000000000000000000000000 020800000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000", &mut test); + + // new inbound connection with id 1 + ext_from_hex("01", &mut test); + // inbound read from peer id 1 of len 50 + ext_from_hex("030132", &mut test); + // inbound noise act 1 + ext_from_hex("0003000000000000000000000000000000000000000000000000000000000000000703000000000000000000000000000000", &mut test); + // inbound read from peer id 1 of len 66 + ext_from_hex("030142", &mut test); + // inbound noise act 3 + ext_from_hex("000302000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003000000000000000000000000000000", &mut test); + + // inbound read from peer id 1 of len 18 + ext_from_hex("030112", &mut test); + // message header indicating message length 16 + ext_from_hex("0010 01000000000000000000000000000000", &mut test); + // inbound read from peer id 1 of len 32 + ext_from_hex("030120", &mut test); + // init message (type 16) with static_remotekey required, no channel_type/anchors/taproot, and other bits optional and mac + ext_from_hex("0010 00021aaa 0008aaa20aaa2a0a9aaa 01000000000000000000000000000000", &mut test); + + // create outbound channel to peer 1 for 50k sat + ext_from_hex("05 01 030200000000000000000000000000000000000000000000000000000000000000 00c350 0003e8", &mut test); + // One feerate requests (all returning min feerate) (gonna be ingested by FuzzEstimator) + ext_from_hex("00fd", &mut test); + + // inbound read from peer id 1 of len 18 + ext_from_hex("030112", &mut test); + // message header indicating message length 274 + ext_from_hex("0112 01000000000000000000000000000000", &mut test); + // inbound read from peer id 1 of len 255 + ext_from_hex("0301ff", &mut test); + // beginning of accept_channel + ext_from_hex("0021 0000000000000000000000000000000000000000000000000000000000000e05 0000000000000162 00000000004c4b40 00000000000003e8 00000000000003e8 00000002 03f0 0005 030000000000000000000000000000000000000000000000000000000000000100 030000000000000000000000000000000000000000000000000000000000000200 030000000000000000000000000000000000000000000000000000000000000300 030000000000000000000000000000000000000000000000000000000000000400 030000000000000000000000000000000000000000000000000000000000000500 02660000000000000000000000000000", &mut test); + // inbound read from peer id 1 of len 35 + ext_from_hex("030123", &mut test); + // rest of accept_channel and mac + ext_from_hex("0000000000000000000000000000000000 0000 01000000000000000000000000000000", &mut test); + + // create the funding transaction (client should send funding_created now) + ext_from_hex("0a", &mut test); + + // inbound read from peer id 1 of len 18 + ext_from_hex("030112", &mut test); + // message header indicating message length 98 + ext_from_hex("0062 01000000000000000000000000000000", &mut test); + // inbound read from peer id 1 of len 114 + ext_from_hex("030172", &mut test); + // funding_signed message and mac + ext_from_hex("0023 3a00000000000000000000000000000000000000000000000000000000000000 000000000000000000000000000000000000000000000000000000000000007c0001000000000000000000000000000000000000000000000000000000000000 01000000000000000000000000000000", &mut test); + + // broadcast funding transaction + ext_from_hex("0b", &mut test); + // by now client should have sent a channel_ready (CHECK 4: SendChannelReady to 03020000 for chan 3f000000) + + // inbound read from peer id 1 of len 18 + ext_from_hex("030112", &mut test); + // message header indicating message length 67 + ext_from_hex("0043 01000000000000000000000000000000", &mut test); + // inbound read from peer id 1 of len 83 + ext_from_hex("030153", &mut test); + // channel_ready and mac + ext_from_hex("0024 3a00000000000000000000000000000000000000000000000000000000000000 026700000000000000000000000000000000000000000000000000000000000000 01000000000000000000000000000000", &mut test); + + // inbound read from peer id 0 of len 18 + ext_from_hex("030012", &mut test); + // message header indicating message length 1452 + ext_from_hex("05ac 03000000000000000000000000000000", &mut test); + // inbound read from peer id 0 of len 255 + ext_from_hex("0300ff", &mut test); + // beginning of update_add_htlc from 0 to 1 via client + ext_from_hex("0080 3d00000000000000000000000000000000000000000000000000000000000000 0000000000000000 0000000000003e80 ff00000000000000000000000000000000000000000000000000000000000000 000003f0 00 030000000000000000000000000000000000000000000000000000000000000555 11 020203e8 0401a0 060800000e0000010000 0a00000000000000000000000000000000000000000000000000000000000000 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", &mut test); + // inbound read from peer id 0 of len 255 + ext_from_hex("0300ff", &mut test); + ext_from_hex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", &mut test); + // inbound read from peer id 0 of len 255 + ext_from_hex("0300ff", &mut test); + ext_from_hex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", &mut test); + // inbound read from peer id 0 of len 255 + ext_from_hex("0300ff", &mut test); + ext_from_hex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", &mut test); + // inbound read from peer id 0 of len 255 + ext_from_hex("0300ff", &mut test); + ext_from_hex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", &mut test); + // inbound read from peer id 0 of len 193 + ext_from_hex("0300c1", &mut test); + // end of update_add_htlc from 0 to 1 via client and mac + ext_from_hex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff ab00000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000", &mut test); + + // inbound read from peer id 0 of len 18 + ext_from_hex("030012", &mut test); + // message header indicating message length 100 + ext_from_hex("0064 03000000000000000000000000000000", &mut test); + // inbound read from peer id 0 of len 116 + ext_from_hex("030074", &mut test); + // commitment_signed and mac + ext_from_hex("0084 3d00000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000300100000000000000000000000000000000000000000000000000000000000000 0000 03000000000000000000000000000000", &mut test); + // client should now respond with revoke_and_ack and commitment_signed (CHECK 5/6: types 133 and 132 to peer 03000000) + + // inbound read from peer id 0 of len 18 + ext_from_hex("030012", &mut test); + // message header indicating message length 99 + ext_from_hex("0063 03000000000000000000000000000000", &mut test); + // inbound read from peer id 0 of len 115 + ext_from_hex("030073", &mut test); + // revoke_and_ack and mac + ext_from_hex("0085 3d00000000000000000000000000000000000000000000000000000000000000 0900000000000000000000000000000000000000000000000000000000000000 020b00000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000", &mut test); + + // process the now-pending HTLC forward + ext_from_hex("07", &mut test); + // client now sends id 1 update_add_htlc and commitment_signed (CHECK 7: UpdateHTLCs event for node 03020000 with 1 HTLCs for channel 3f000000) + + // we respond with commitment_signed then revoke_and_ack (a weird, but valid, order) + // inbound read from peer id 1 of len 18 + ext_from_hex("030112", &mut test); + // message header indicating message length 100 + ext_from_hex("0064 01000000000000000000000000000000", &mut test); + // inbound read from peer id 1 of len 116 + ext_from_hex("030174", &mut test); + // commitment_signed and mac + ext_from_hex("0084 3a00000000000000000000000000000000000000000000000000000000000000 000000000000000000000000000000000000000000000000000000000000006a0001000000000000000000000000000000000000000000000000000000000000 0000 01000000000000000000000000000000", &mut test); // - // 0c007d - connect a block with one transaction of len 125 - // 02000000013a000000000000000000000000000000000000000000000000000000000000000000000000000000800258020000000000002200204b0000000000000000000000000000000000000000000000000000000000000014c0000000000000160014280000000000000000000000000000000000000005000020 - the commitment transaction for channel 3f00000000000000000000000000000000000000000000000000000000000000 + // inbound read from peer id 1 of len 18 + ext_from_hex("030112", &mut test); + // message header indicating message length 99 + ext_from_hex("0063 01000000000000000000000000000000", &mut test); + // inbound read from peer id 1 of len 115 + ext_from_hex("030173", &mut test); + // revoke_and_ack and mac + ext_from_hex("0085 3a00000000000000000000000000000000000000000000000000000000000000 6600000000000000000000000000000000000000000000000000000000000000 026400000000000000000000000000000000000000000000000000000000000000 01000000000000000000000000000000", &mut test); // - // 0c005e - connect a block with one transaction of len 94 - // 0200000001730000000000000000000000000000000000000000000000000000000000000000000000000000000001a701000000000000220020b20000000000000000000000000000000000000000000000000000000000000000000000 - the HTLC timeout transaction - // 0c0000 - connect a block with no transactions - // 0c0000 - connect a block with no transactions - // 0c0000 - connect a block with no transactions - // 0c0000 - connect a block with no transactions - // 0c0000 - connect a block with no transactions + // inbound read from peer id 1 of len 18 + ext_from_hex("030112", &mut test); + // message header indicating message length 74 + ext_from_hex("004a 01000000000000000000000000000000", &mut test); + // inbound read from peer id 1 of len 90 + ext_from_hex("03015a", &mut test); + // update_fulfill_htlc and mac + ext_from_hex("0082 3a00000000000000000000000000000000000000000000000000000000000000 0000000000000000 ff00888888888888888888888888888888888888888888888888888888888888 01000000000000000000000000000000", &mut test); + // client should immediately claim the pending HTLC from peer 0 (CHECK 8: SendFulfillHTLCs for node 03000000 with preimage ff00888888 for channel 3d000000) + + // inbound read from peer id 1 of len 18 + ext_from_hex("030112", &mut test); + // message header indicating message length 100 + ext_from_hex("0064 01000000000000000000000000000000", &mut test); + // inbound read from peer id 1 of len 116 + ext_from_hex("030174", &mut test); + // commitment_signed and mac + ext_from_hex("0084 3a00000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000100001000000000000000000000000000000000000000000000000000000000000 0000 01000000000000000000000000000000", &mut test); + + // inbound read from peer id 1 of len 18 + ext_from_hex("030112", &mut test); + // message header indicating message length 99 + ext_from_hex("0063 01000000000000000000000000000000", &mut test); + // inbound read from peer id 1 of len 115 + ext_from_hex("030173", &mut test); + // revoke_and_ack and mac + ext_from_hex("0085 3a00000000000000000000000000000000000000000000000000000000000000 6700000000000000000000000000000000000000000000000000000000000000 026500000000000000000000000000000000000000000000000000000000000000 01000000000000000000000000000000", &mut test); + + // before responding to the commitment_signed generated above, send a new HTLC + // inbound read from peer id 0 of len 18 + ext_from_hex("030012", &mut test); + // message header indicating message length 1452 + ext_from_hex("05ac 03000000000000000000000000000000", &mut test); + // inbound read from peer id 0 of len 255 + ext_from_hex("0300ff", &mut test); + // beginning of update_add_htlc from 0 to 1 via client + ext_from_hex("0080 3d00000000000000000000000000000000000000000000000000000000000000 0000000000000001 0000000000003e80 ff00000000000000000000000000000000000000000000000000000000000000 000003f0 00 030000000000000000000000000000000000000000000000000000000000000555 11 020203e8 0401a0 060800000e0000010000 0a00000000000000000000000000000000000000000000000000000000000000 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", &mut test); + // inbound read from peer id 0 of len 255 + ext_from_hex("0300ff", &mut test); + ext_from_hex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", &mut test); + // inbound read from peer id 0 of len 255 + ext_from_hex("0300ff", &mut test); + ext_from_hex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", &mut test); + // inbound read from peer id 0 of len 255 + ext_from_hex("0300ff", &mut test); + ext_from_hex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", &mut test); + // inbound read from peer id 0 of len 255 + ext_from_hex("0300ff", &mut test); + ext_from_hex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", &mut test); + // inbound read from peer id 0 of len 193 + ext_from_hex("0300c1", &mut test); + // end of update_add_htlc from 0 to 1 via client and mac + ext_from_hex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff ab00000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000", &mut test); + + // now respond to the update_fulfill_htlc+commitment_signed messages the client sent to peer 0 + // inbound read from peer id 0 of len 18 + ext_from_hex("030012", &mut test); + // message header indicating message length 99 + ext_from_hex("0063 03000000000000000000000000000000", &mut test); + // inbound read from peer id 0 of len 115 + ext_from_hex("030073", &mut test); + // revoke_and_ack and mac + ext_from_hex("0085 3d00000000000000000000000000000000000000000000000000000000000000 0800000000000000000000000000000000000000000000000000000000000000 020a00000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000", &mut test); + // client should now respond with revoke_and_ack and commitment_signed (CHECK 5/6 duplicates) + + // inbound read from peer id 0 of len 18 + ext_from_hex("030012", &mut test); + // message header indicating message length 100 + ext_from_hex("0064 03000000000000000000000000000000", &mut test); + // inbound read from peer id 0 of len 116 + ext_from_hex("030074", &mut test); + // commitment_signed and mac + ext_from_hex("0084 3d00000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000c30100000000000000000000000000000000000000000000000000000000000000 0000 03000000000000000000000000000000", &mut test); + + // inbound read from peer id 0 of len 18 + ext_from_hex("030012", &mut test); + // message header indicating message length 99 + ext_from_hex("0063 03000000000000000000000000000000", &mut test); + // inbound read from peer id 0 of len 115 + ext_from_hex("030073", &mut test); + // revoke_and_ack and mac + ext_from_hex("0085 3d00000000000000000000000000000000000000000000000000000000000000 0b00000000000000000000000000000000000000000000000000000000000000 020d00000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000", &mut test); + + // process the now-pending HTLC forward + ext_from_hex("07", &mut test); + // client now sends id 1 update_add_htlc and commitment_signed (CHECK 7 duplicate) + // we respond with revoke_and_ack, then commitment_signed, then update_fail_htlc + + // inbound read from peer id 1 of len 18 + ext_from_hex("030112", &mut test); + // message header indicating message length 100 + ext_from_hex("0064 01000000000000000000000000000000", &mut test); + // inbound read from peer id 1 of len 116 + ext_from_hex("030174", &mut test); + // commitment_signed and mac + ext_from_hex("0084 3a00000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000390001000000000000000000000000000000000000000000000000000000000000 0000 01000000000000000000000000000000", &mut test); + + // inbound read from peer id 1 of len 18 + ext_from_hex("030112", &mut test); + // message header indicating message length 99 + ext_from_hex("0063 01000000000000000000000000000000", &mut test); + // inbound read from peer id 1 of len 115 + ext_from_hex("030173", &mut test); + // revoke_and_ack and mac + ext_from_hex("0085 3a00000000000000000000000000000000000000000000000000000000000000 6400000000000000000000000000000000000000000000000000000000000000 027000000000000000000000000000000000000000000000000000000000000000 01000000000000000000000000000000", &mut test); + + // inbound read from peer id 1 of len 18 + ext_from_hex("030112", &mut test); + // message header indicating message length 44 + ext_from_hex("002c 01000000000000000000000000000000", &mut test); + // inbound read from peer id 1 of len 60 + ext_from_hex("03013c", &mut test); + // update_fail_htlc and mac + ext_from_hex("0083 3a00000000000000000000000000000000000000000000000000000000000000 0000000000000001 0000 01000000000000000000000000000000", &mut test); + + // inbound read from peer id 1 of len 18 + ext_from_hex("030112", &mut test); + // message header indicating message length 100 + ext_from_hex("0064 01000000000000000000000000000000", &mut test); + // inbound read from peer id 1 of len 116 + ext_from_hex("030174", &mut test); + // commitment_signed and mac + ext_from_hex("0084 3a00000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000390001000000000000000000000000000000000000000000000000000000000000 0000 01000000000000000000000000000000", &mut test); + + // inbound read from peer id 1 of len 18 + ext_from_hex("030112", &mut test); + // message header indicating message length 99 + ext_from_hex("0063 01000000000000000000000000000000", &mut test); + // inbound read from peer id 1 of len 115 + ext_from_hex("030173", &mut test); + // revoke_and_ack and mac + ext_from_hex("0085 3a00000000000000000000000000000000000000000000000000000000000000 6500000000000000000000000000000000000000000000000000000000000000 027100000000000000000000000000000000000000000000000000000000000000 01000000000000000000000000000000", &mut test); + + // process the now-pending HTLC forward + ext_from_hex("07", &mut test); + // client now sends id 0 update_fail_htlc and commitment_signed (CHECK 9) + // now respond to the update_fail_htlc+commitment_signed messages the client sent to peer 0 + + // inbound read from peer id 0 of len 18 + ext_from_hex("030012", &mut test); + // message header indicating message length 99 + ext_from_hex("0063 03000000000000000000000000000000", &mut test); + // inbound read from peer id 0 of len 115 + ext_from_hex("030073", &mut test); + // revoke_and_ack and mac + ext_from_hex("0085 3d00000000000000000000000000000000000000000000000000000000000000 0a00000000000000000000000000000000000000000000000000000000000000 020c00000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000", &mut test); + + // inbound read from peer id 0 of len 18 + ext_from_hex("030012", &mut test); + // message header indicating message length 100 + ext_from_hex("0064 03000000000000000000000000000000", &mut test); + // inbound read from peer id 0 of len 116 + ext_from_hex("030074", &mut test); + // commitment_signed and mac + ext_from_hex("0084 3d00000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000320100000000000000000000000000000000000000000000000000000000000000 0000 03000000000000000000000000000000", &mut test); + // client should now respond with revoke_and_ack (CHECK 5 duplicate) + + // inbound read from peer id 0 of len 18 + ext_from_hex("030012", &mut test); + // message header indicating message length 1452 + ext_from_hex("05ac 03000000000000000000000000000000", &mut test); + // inbound read from peer id 0 of len 255 + ext_from_hex("0300ff", &mut test); + // beginning of update_add_htlc from 0 to 1 via client + ext_from_hex("0080 3d00000000000000000000000000000000000000000000000000000000000000 0000000000000002 00000000000b0838 ff00000000000000000000000000000000000000000000000000000000000000 000003f0 00 030000000000000000000000000000000000000000000000000000000000000555 12 02030927c0 0401a0 060800000e0000010000 0a00000000000000000000000000000000000000000000000000000000000000 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", &mut test); + // inbound read from peer id 0 of len 255 + ext_from_hex("0300ff", &mut test); + ext_from_hex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", &mut test); + // inbound read from peer id 0 of len 255 + ext_from_hex("0300ff", &mut test); + ext_from_hex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", &mut test); + // inbound read from peer id 0 of len 255 + ext_from_hex("0300ff", &mut test); + ext_from_hex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", &mut test); + // inbound read from peer id 0 of len 255 + ext_from_hex("0300ff", &mut test); + ext_from_hex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", &mut test); + // inbound read from peer id 0 of len 193 + ext_from_hex("0300c1", &mut test); + // end of update_add_htlc from 0 to 1 via client and mac + ext_from_hex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 5300000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000", &mut test); + + // inbound read from peer id 0 of len 18 + ext_from_hex("030012", &mut test); + // message header indicating message length 164 + ext_from_hex("00a4 03000000000000000000000000000000", &mut test); + // inbound read from peer id 0 of len 180 + ext_from_hex("0300b4", &mut test); + // commitment_signed and mac + ext_from_hex("0084 3d00000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000750100000000000000000000000000000000000000000000000000000000000000 0001 00000000000000000000000000000000000000000000000000000000000000670500000000000000000000000000000000000000000000000000000000000006 03000000000000000000000000000000", &mut test); + // client should now respond with revoke_and_ack and commitment_signed (CHECK 5/6 duplicates) + + // inbound read from peer id 0 of len 18 + ext_from_hex("030012", &mut test); + // message header indicating message length 99 + ext_from_hex("0063 03000000000000000000000000000000", &mut test); + // inbound read from peer id 0 of len 115 + ext_from_hex("030073", &mut test); + // revoke_and_ack and mac + ext_from_hex("0085 3d00000000000000000000000000000000000000000000000000000000000000 0d00000000000000000000000000000000000000000000000000000000000000 020f00000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000", &mut test); + + // process the now-pending HTLC forward + ext_from_hex("07", &mut test); + // client now sends id 1 update_add_htlc and commitment_signed (CHECK 7 duplicate) + + // connect a block with one transaction of len 125 + ext_from_hex("0c007d", &mut test); + // the commitment transaction for channel 3f00000000000000000000000000000000000000000000000000000000000000 + ext_from_hex("02000000013a000000000000000000000000000000000000000000000000000000000000000000000000000000800258020000000000002200204b0000000000000000000000000000000000000000000000000000000000000014c0000000000000160014280000000000000000000000000000000000000005000020", &mut test); // - // 07 - process the now-pending HTLC forward - // - client now fails the HTLC backwards as it was unable to extract the payment preimage (CHECK 9 duplicate and CHECK 10) + // connect a block with one transaction of len 94 + ext_from_hex("0c005e", &mut test); + // the HTLC timeout transaction + ext_from_hex("0200000001730000000000000000000000000000000000000000000000000000000000000000000000000000000001a701000000000000220020b20000000000000000000000000000000000000000000000000000000000000000000000", &mut test); + // connect a block with no transactions + ext_from_hex("0c0000", &mut test); + // connect a block with no transactions + ext_from_hex("0c0000", &mut test); + // connect a block with no transactions + ext_from_hex("0c0000", &mut test); + // connect a block with no transactions + ext_from_hex("0c0000", &mut test); + // connect a block with no transactions + ext_from_hex("0c0000", &mut test); + + // process the now-pending HTLC forward + ext_from_hex("07", &mut test); + // 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(&>::from_hex("01000000000000000000000000000000000000000000000000000000000000000000000001000300000000000000000000000000000000000000000000000000000000000000020300320003000000000000000000000000000000000000000000000000000000000000000203000000000000000000000000000000030012001003000000000000000000000000000000030020001000021aaa0008aaaaaaaaaaaa9aaa030000000000000000000000000000000300120147030000000000000000000000000000000300fe00206fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000ff4f00f805273c1b203bb5ebf8436bfde57b3be8c2f5e95d9491dbb181909679000000000000c35000000000000000000000000000000162ffffffffffffffff00000000000002220000000000000000000000fd000601e3030000000000000000000000000000000000000000000000000000000000000001030000000000000000000000000000000000000000000000000000000000000002030000000000000000000000000000000000000000000000000000000000000003030000000000000000000000000000000000000000000000000000000000000004030059030000000000000000000000000000000000000000000000000000000000000005020900000000000000000000000000000000000000000000000000000000000000010000010210000300000000000000000000000000000000fd0300120084030000000000000000000000000000000300940022ff4f00f805273c1b203bb5ebf8436bfde57b3be8c2f5e95d9491dbb1819096793d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000210100000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000c005e020000000100000000000000000000000000000000000000000000000000000000000000000000000000ffffffff0150c3000000000000220020ae00000000000000000000000000000000000000000000000000000000000000000000000c00000c00000c00000c00000c00000c00000c00000c00000c00000c00000c00000c000003001200430300000000000000000000000000000003005300243d0000000000000000000000000000000000000000000000000000000000000002080000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000010301320003000000000000000000000000000000000000000000000000000000000000000703000000000000000000000000000000030142000302000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003000000000000000000000000000000030112001001000000000000000000000000000000030120001000021aaa0008aaaaaaaaaaaa9aaa01000000000000000000000000000000050103020000000000000000000000000000000000000000000000000000000000000000c3500003e800fd0301120112010000000000000000000000000000000301ff00210000000000000000000000000000000000000000000000000000000000000e05000000000000016200000000004c4b4000000000000003e800000000000003e80000000203f000050300000000000000000000000000000000000000000000000000000000000001000300000000000000000000000000000000000000000000000000000000000002000300000000000000000000000000000000000000000000000000000000000003000300000000000000000000000000000000000000000000000000000000000004000300000000000000000000000000000000000000000000000000000000000005000266000000000000000000000000000003012300000000000000000000000000000000000000010000000000000000000000000000000a00fd00fd03011200620100000000000000000000000000000003017200233a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007c0001000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000b03011200430100000000000000000000000000000003015300243a000000000000000000000000000000000000000000000000000000000000000267000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003001205ac030000000000000000000000000000000300ff00803d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e80ff00000000000000000000000000000000000000000000000000000000000000000003f00003000000000000000000000000000000000000000000000000000000000000055511020203e80401a0060800000e00000100000a00000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300c1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffab000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000fd03001200640300000000000000000000000000000003007400843d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030010000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000900000000000000000000000000000000000000000000000000000000000000020b00000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000700fd00fd03011200640100000000000000000000000000000003017400843a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006a000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003011200630100000000000000000000000000000003017300853a00000000000000000000000000000000000000000000000000000000000000660000000000000000000000000000000000000000000000000000000000000002640000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000030112004a0100000000000000000000000000000003015a00823a000000000000000000000000000000000000000000000000000000000000000000000000000000ff008888888888888888888888888888888888888888888888888888888888880100000000000000000000000000000003011200640100000000000000000000000000000003017400843a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003011200630100000000000000000000000000000003017300853a0000000000000000000000000000000000000000000000000000000000000067000000000000000000000000000000000000000000000000000000000000000265000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003001205ac030000000000000000000000000000000300ff00803d0000000000000000000000000000000000000000000000000000000000000000000000000000010000000000003e80ff00000000000000000000000000000000000000000000000000000000000000000003f00003000000000000000000000000000000000000000000000000000000000000055511020203e80401a0060800000e00000100000a00000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300c1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffab000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000fd03001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000020a000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001200640300000000000000000000000000000003007400843d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c3010000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000b00000000000000000000000000000000000000000000000000000000000000020d00000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000700fd00fd03011200640100000000000000000000000000000003017400843a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000039000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003011200630100000000000000000000000000000003017300853a00000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000002700000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000030112002c0100000000000000000000000000000003013c00833a00000000000000000000000000000000000000000000000000000000000000000000000000000100000100000000000000000000000000000003011200640100000000000000000000000000000003017400843a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000039000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003011200630100000000000000000000000000000003017300853a000000000000000000000000000000000000000000000000000000000000006500000000000000000000000000000000000000000000000000000000000000027100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000703001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000020c000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001200640300000000000000000000000000000003007400843d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000032010000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001205ac030000000000000000000000000000000300ff00803d00000000000000000000000000000000000000000000000000000000000000000000000000000200000000000b0838ff00000000000000000000000000000000000000000000000000000000000000000003f0000300000000000000000000000000000000000000000000000000000000000005551202030927c00401a0060800000e00000100000a00000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300c1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff53000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000fd03001200a4030000000000000000000000000000000300b400843d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007501000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006705000000000000000000000000000000000000000000000000000000000000060300000000000000000000000000000003001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000d00000000000000000000000000000000000000000000000000000000000000020f00000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000700fd00fd0c007d02000000013a000000000000000000000000000000000000000000000000000000000000000000000000000000800258020000000000002200204b0000000000000000000000000000000000000000000000000000000000000014c00000000000001600142800000000000000000000000000000000000000050000200c005e0200000001730000000000000000000000000000000000000000000000000000000000000000000000000000000001a701000000000000220020b200000000000000000000000000000000000000000000000000000000000000000000000c00000c00000c00000c00000c000007").unwrap(), &(Arc::clone(&logger) as Arc)); + super::do_test(&test, &(Arc::clone(&logger) as Arc)); 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 @@ -1045,4 +1305,108 @@ mod tests { assert_eq!(log_entries.get(&("lightning::ln::peer_handler".to_string(), "Handling UpdateHTLCs event in peer_handler for node 030000000000000000000000000000000000000000000000000000000000000002 with 0 adds, 0 fulfills, 1 fails for channel 3d00000000000000000000000000000000000000000000000000000000000000".to_string())), Some(&2)); // 9 assert_eq!(log_entries.get(&("lightning::chain::channelmonitor".to_string(), "Input spending counterparty commitment tx (0000000000000000000000000000000000000000000000000000000000000073:0) in 0000000000000000000000000000000000000000000000000000000000000067 resolves outbound HTLC with payment hash ff00000000000000000000000000000000000000000000000000000000000000 with timeout".to_string())), Some(&1)); // 10 } + + #[test] + fn test_gossip_exchange_breakage() { + // To avoid accidentally causing all existing fuzz test cases to be useless by making minor + // changes (such as requesting feerate info in a new place), we exchange some gossip + // messages. Obviously this is pretty finicky, so this should be updated pretty liberally, + // but at least we'll know when changes occur. + // This test serves as a pretty good full_stack_target seed. + + // What each byte represents is broken down below, and then everything is concatenated into + // one large test at the end (you want %s/ -.*//g %s/\n\| \|\t\|\///g). + + // Following BOLT 8, lightning message on the wire are: 2-byte encrypted message length + + // 16-byte MAC of the encrypted message length + encrypted Lightning message + 16-byte MAC + // of the Lightning message + // I.e 2nd inbound read, len 18 : 0006 (encrypted message length) + 03000000000000000000000000000000 (MAC of the encrypted message length) + // Len 22 : 0010 00000000 (encrypted lightning message) + 03000000000000000000000000000000 (MAC of the Lightning message) + + // Writing new code generating transactions and see a new failure ? Don't forget to add input for the FuzzEstimator ! + + let mut test = Vec::new(); + + // our network key + ext_from_hex("0100000000000000000000000000000000000000000000000000000000000000", &mut test); + // config + ext_from_hex("0000000000900000000000000000640001000000000001ffff0000000000000000ffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff000000ffffffff00ffff1a000400010000020400000000040200000a08ffffffffffffffff0001000000", &mut test); + + // new outbound connection with id 0 + ext_from_hex("00", &mut test); + // peer's pubkey + ext_from_hex("030000000000000000000000000000000000000000000000000000000000000002", &mut test); + // inbound read from peer id 0 of len 50 + ext_from_hex("030032", &mut test); + // noise act two (0||pubkey||mac) + ext_from_hex("00 030000000000000000000000000000000000000000000000000000000000000002 03000000000000000000000000000000", &mut test); + + // inbound read from peer id 0 of len 18 + ext_from_hex("030012", &mut test); + // message header indicating message length 16 + ext_from_hex("0010 03000000000000000000000000000000", &mut test); + // inbound read from peer id 0 of len 32 + ext_from_hex("030020", &mut test); + // init message (type 16) with static_remotekey required, no channel_type/anchors/taproot, and other bits optional and mac + ext_from_hex("0010 00021aaa 0008aaa20aaa2a0a9aaa 03000000000000000000000000000000", &mut test); + + // new inbound connection with id 1 + ext_from_hex("01", &mut test); + // inbound read from peer id 1 of len 50 + ext_from_hex("030132", &mut test); + // inbound noise act 1 + ext_from_hex("0003000000000000000000000000000000000000000000000000000000000000000703000000000000000000000000000000", &mut test); + // inbound read from peer id 1 of len 66 + ext_from_hex("030142", &mut test); + // inbound noise act 3 + ext_from_hex("000302000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003000000000000000000000000000000", &mut test); + + // inbound read from peer id 1 of len 18 + ext_from_hex("030112", &mut test); + // message header indicating message length 16 + ext_from_hex("0010 01000000000000000000000000000000", &mut test); + // inbound read from peer id 1 of len 32 + ext_from_hex("030120", &mut test); + // init message (type 16) with static_remotekey required, no channel_type/anchors/taproot, and other bits optional and mac + ext_from_hex("0010 00021aaa 0008aaa20aaa2a0a9aaa 01000000000000000000000000000000", &mut test); + + // inbound read from peer id 0 of len 18 + ext_from_hex("030012", &mut test); + // message header indicating message length 432 + ext_from_hex("01b0 03000000000000000000000000000000", &mut test); + // inbound read from peer id 0 of len 255 + ext_from_hex("0300ff", &mut test); + // First part of channel_announcement (type 256) + ext_from_hex("0100 00000000000000000000000000000000000000000000000000000000000000b20303030303030303030303030303030303030303030303030303030303030303 00000000000000000000000000000000000000000000000000000000000000b20202020202020202020202020202020202020202020202020202020202020202 00000000000000000000000000000000000000000000000000000000000000b20303030303030303030303030303030303030303030303030303030303030303 00000000000000000000000000000000000000000000000000000000000000b20202020202020202020202020202020202020202020202020202020202", &mut test); + // inbound read from peer id 0 of len 193 + ext_from_hex("0300c1", &mut test); + // Last part of channel_announcement and mac + ext_from_hex("020202 00006fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000000000000000002a030303030303030303030303030303030303030303030303030303030303030303020202020202020202020202020202020202020202020202020202020202020202030303030303030303030303030303030303030303030303030303030303030303020202020202020202020202020202020202020202020202020202020202020202 03000000000000000000000000000000", &mut test); + + // inbound read from peer id 0 of len 18 + ext_from_hex("030012", &mut test); + // message header indicating message length 138 + ext_from_hex("008a 03000000000000000000000000000000", &mut test); + // inbound read from peer id 0 of len 154 + ext_from_hex("03009a", &mut test); + // channel_update (type 258) and mac + ext_from_hex("0102 00000000000000000000000000000000000000000000000000000000000000a60303030303030303030303030303030303030303030303030303030303030303 6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000 000000000000002a0000002c01000028000000000000000000000000000000000000000005f5e100 03000000000000000000000000000000", &mut test); + + // inbound read from peer id 0 of len 18 + ext_from_hex("030012", &mut test); + // message header indicating message length 142 + ext_from_hex("008e 03000000000000000000000000000000", &mut test); + // inbound read from peer id 0 of len 158 + ext_from_hex("03009e", &mut test); + // node_announcement (type 257) and mac + ext_from_hex("0101 00000000000000000000000000000000000000000000000000000000000000280303030303030303030303030303030303030303030303030303030303030303 00000000002b03030303030303030303030303030303030303030303030303030303030303030300000000000000000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000", &mut test); + + let logger = Arc::new(TrackingLogger { lines: Mutex::new(HashMap::new()) }); + super::do_test(&test, &(Arc::clone(&logger) as Arc)); + + let log_entries = logger.lines.lock().unwrap(); + assert_eq!(log_entries.get(&("lightning::ln::peer_handler".to_string(), "Sending message to all peers except Some(PublicKey(0000000000000000000000000000000000000000000000000000000000000002ff00000000000000000000000000000000000000000000000000000000000002)) or the announced channel's counterparties: ChannelAnnouncement { node_signature_1: 3026020200b202200303030303030303030303030303030303030303030303030303030303030303, node_signature_2: 3026020200b202200202020202020202020202020202020202020202020202020202020202020202, bitcoin_signature_1: 3026020200b202200303030303030303030303030303030303030303030303030303030303030303, bitcoin_signature_2: 3026020200b202200202020202020202020202020202020202020202020202020202020202020202, contents: UnsignedChannelAnnouncement { features: [], chain_hash: 6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000, short_channel_id: 42, node_id_1: NodeId(030303030303030303030303030303030303030303030303030303030303030303), node_id_2: NodeId(020202020202020202020202020202020202020202020202020202020202020202), bitcoin_key_1: NodeId(030303030303030303030303030303030303030303030303030303030303030303), bitcoin_key_2: NodeId(020202020202020202020202020202020202020202020202020202020202020202), excess_data: [] } }".to_string())), Some(&1)); + assert_eq!(log_entries.get(&("lightning::ln::peer_handler".to_string(), "Sending message to all peers except Some(PublicKey(0000000000000000000000000000000000000000000000000000000000000002ff00000000000000000000000000000000000000000000000000000000000002)): ChannelUpdate { signature: 3026020200a602200303030303030303030303030303030303030303030303030303030303030303, contents: UnsignedChannelUpdate { chain_hash: 6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000, short_channel_id: 42, timestamp: 44, flags: 0, cltv_expiry_delta: 40, htlc_minimum_msat: 0, htlc_maximum_msat: 100000000, fee_base_msat: 0, fee_proportional_millionths: 0, excess_data: [] } }".to_string())), Some(&1)); + assert_eq!(log_entries.get(&("lightning::ln::peer_handler".to_string(), "Sending message to all peers except Some(PublicKey(0000000000000000000000000000000000000000000000000000000000000002ff00000000000000000000000000000000000000000000000000000000000002)) or the announced node: NodeAnnouncement { signature: 302502012802200303030303030303030303030303030303030303030303030303030303030303, contents: UnsignedNodeAnnouncement { features: [], timestamp: 43, node_id: NodeId(030303030303030303030303030303030303030303030303030303030303030303), rgb: [0, 0, 0], alias: NodeAlias([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]), addresses: [], excess_address_data: [], excess_data: [] } }".to_string())), Some(&1)); + } } diff --git a/fuzz/src/indexedmap.rs b/fuzz/src/indexedmap.rs index 7cbb8957ad2..58775d02467 100644 --- a/fuzz/src/indexedmap.rs +++ b/fuzz/src/indexedmap.rs @@ -9,7 +9,7 @@ use lightning::util::indexed_map::{IndexedMap, self}; use std::collections::{BTreeMap, btree_map}; -use hashbrown::HashSet; +use lightning::util::hash_tables::*; use crate::utils::test_logger; @@ -80,23 +80,23 @@ fn check_eq(btree: &BTreeMap, mut indexed: IndexedMap) { } } - let mut key_set = HashSet::with_capacity(256); + let mut key_set = hash_map_with_capacity(1024); for k in indexed.unordered_keys() { - assert!(key_set.insert(*k)); + assert!(key_set.insert(*k, ()).is_none()); assert!(btree.contains_key(k)); } assert_eq!(key_set.len(), btree.len()); key_set.clear(); for (k, v) in indexed.unordered_iter() { - assert!(key_set.insert(*k)); + assert!(key_set.insert(*k, ()).is_none()); assert_eq!(btree.get(k).unwrap(), v); } assert_eq!(key_set.len(), btree.len()); key_set.clear(); for (k, v) in indexed_clone.unordered_iter_mut() { - assert!(key_set.insert(*k)); + assert!(key_set.insert(*k, ()).is_none()); assert_eq!(btree.get(k).unwrap(), v); } assert_eq!(key_set.len(), btree.len()); diff --git a/fuzz/src/invoice_request_deser.rs b/fuzz/src/invoice_request_deser.rs index 22a2258f4e2..414ce1cdd1c 100644 --- a/fuzz/src/invoice_request_deser.rs +++ b/fuzz/src/invoice_request_deser.rs @@ -9,7 +9,7 @@ use bitcoin::secp256k1::{KeyPair, Parity, PublicKey, Secp256k1, SecretKey, self}; use crate::utils::test_logger; -use core::convert::{Infallible, TryFrom}; +use core::convert::TryFrom; use lightning::blinded_path::BlindedPath; use lightning::sign::EntropySource; use lightning::ln::PaymentHash; @@ -37,16 +37,16 @@ pub fn do_test(data: &[u8], _out: Out) { let even_pubkey = x_only_pubkey.public_key(Parity::Even); if signing_pubkey == odd_pubkey || signing_pubkey == even_pubkey { unsigned_invoice - .sign::<_, Infallible>( - |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) + .sign(|message: &UnsignedBolt12Invoice| + Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) ) .unwrap() .write(&mut buffer) .unwrap(); } else { unsigned_invoice - .sign::<_, Infallible>( - |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) + .sign(|message: &UnsignedBolt12Invoice| + Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) ) .unwrap_err(); } diff --git a/fuzz/src/offer_deser.rs b/fuzz/src/offer_deser.rs index e16c3b4103b..5aad4642e46 100644 --- a/fuzz/src/offer_deser.rs +++ b/fuzz/src/offer_deser.rs @@ -9,7 +9,7 @@ use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey}; use crate::utils::test_logger; -use core::convert::{Infallible, TryFrom}; +use core::convert::TryFrom; use lightning::offers::invoice_request::UnsignedInvoiceRequest; use lightning::offers::offer::{Amount, Offer, Quantity}; use lightning::offers::parse::Bolt12SemanticError; @@ -29,8 +29,8 @@ pub fn do_test(data: &[u8], _out: Out) { if let Ok(invoice_request) = build_response(&offer, pubkey) { invoice_request - .sign::<_, Infallible>( - |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) + .sign(|message: &UnsignedInvoiceRequest| + Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) ) .unwrap() .write(&mut buffer) diff --git a/fuzz/src/onion_hop_data.rs b/fuzz/src/onion_hop_data.rs index cc80ccf9320..e7f51b99167 100644 --- a/fuzz/src/onion_hop_data.rs +++ b/fuzz/src/onion_hop_data.rs @@ -16,16 +16,18 @@ use lightning::util::test_utils; #[inline] pub fn onion_hop_data_test(data: &[u8], _out: Out) { use lightning::util::ser::ReadableArgs; + use bitcoin::secp256k1::PublicKey; let mut r = ::std::io::Cursor::new(data); let node_signer = test_utils::TestNodeSigner::new(test_utils::privkey(42)); - let _ = >::read(&mut r, &&node_signer); + let _ = , &&test_utils::TestNodeSigner)>>::read(&mut r, (None, &&node_signer)); } #[no_mangle] pub extern "C" fn onion_hop_data_run(data: *const u8, datalen: usize) { use lightning::util::ser::ReadableArgs; + use bitcoin::secp256k1::PublicKey; 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 _ = >::read(&mut r, &&node_signer); + let _ = , &&test_utils::TestNodeSigner)>>::read(&mut r, (None, &&node_signer)); } diff --git a/fuzz/src/onion_message.rs b/fuzz/src/onion_message.rs index 1051083d36b..f2bae246fab 100644 --- a/fuzz/src/onion_message.rs +++ b/fuzz/src/onion_message.rs @@ -1,21 +1,24 @@ // Imports that need to be added manually use bitcoin::bech32::u5; use bitcoin::blockdata::script::ScriptBuf; -use bitcoin::secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey}; +use bitcoin::secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey, self}; use bitcoin::secp256k1::ecdh::SharedSecret; use bitcoin::secp256k1::ecdsa::RecoverableSignature; use bitcoin::secp256k1::schnorr; -use lightning::sign::{Recipient, KeyMaterial, EntropySource, NodeSigner, SignerProvider}; +use lightning::blinded_path::BlindedPath; use lightning::ln::features::InitFeatures; use lightning::ln::msgs::{self, DecodeError, OnionMessageHandler}; use lightning::ln::script::ShutdownScript; use lightning::offers::invoice::UnsignedBolt12Invoice; use lightning::offers::invoice_request::UnsignedInvoiceRequest; +use lightning::sign::{Recipient, KeyMaterial, EntropySource, NodeSigner, SignerProvider}; use lightning::util::test_channel_signer::TestChannelSigner; use lightning::util::logger::Logger; use lightning::util::ser::{Readable, Writeable, Writer}; -use lightning::onion_message::{CustomOnionMessageHandler, Destination, MessageRouter, OffersMessage, OffersMessageHandler, OnionMessageContents, OnionMessagePath, OnionMessenger, PendingOnionMessage}; +use lightning::onion_message::messenger::{CustomOnionMessageHandler, Destination, MessageRouter, OnionMessagePath, OnionMessenger, PendingOnionMessage}; +use lightning::onion_message::offers::{OffersMessage, OffersMessageHandler}; +use lightning::onion_message::packet::OnionMessageContents; use crate::utils::test_logger; @@ -82,6 +85,12 @@ impl MessageRouter for TestMessageRouter { first_node_addresses: None, }) } + + fn create_blinded_paths( + &self, _recipient: PublicKey, _peers: Vec, _secp_ctx: &Secp256k1, + ) -> Result, ()> { + unreachable!() + } } struct TestOffersMessageHandler {} diff --git a/fuzz/src/peer_crypt.rs b/fuzz/src/peer_crypt.rs index 3acf4d664f6..41d8c093606 100644 --- a/fuzz/src/peer_crypt.rs +++ b/fuzz/src/peer_crypt.rs @@ -83,7 +83,7 @@ pub fn do_test(data: &[u8]) { Ok(len) => len, Err(_) => return, }; - buf.copy_from_slice(&get_slice!(len as usize + 16)); + buf[..len as usize + 16].copy_from_slice(&get_slice!(len as usize + 16)); match crypter.decrypt_message(&mut buf[..len as usize + 16]) { Ok(_) => {}, Err(_) => return, diff --git a/fuzz/src/refund_deser.rs b/fuzz/src/refund_deser.rs index fd273d7e028..14b136ec35b 100644 --- a/fuzz/src/refund_deser.rs +++ b/fuzz/src/refund_deser.rs @@ -9,7 +9,7 @@ use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey, self}; use crate::utils::test_logger; -use core::convert::{Infallible, TryFrom}; +use core::convert::TryFrom; use lightning::blinded_path::BlindedPath; use lightning::sign::EntropySource; use lightning::ln::PaymentHash; @@ -33,8 +33,8 @@ pub fn do_test(data: &[u8], _out: Out) { if let Ok(invoice) = build_response(&refund, pubkey, &secp_ctx) { invoice - .sign::<_, Infallible>( - |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) + .sign(|message: &UnsignedBolt12Invoice| + Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) ) .unwrap() .write(&mut buffer) diff --git a/fuzz/src/router.rs b/fuzz/src/router.rs index c9ceea3d0b3..bf94c910531 100644 --- a/fuzz/src/router.rs +++ b/fuzz/src/router.rs @@ -23,6 +23,7 @@ use lightning::routing::utxo::{UtxoFuture, UtxoLookup, UtxoLookupError, UtxoResu use lightning::routing::router::{find_route, PaymentParameters, RouteHint, RouteHintHop, RouteParameters}; use lightning::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringFeeParameters, ProbabilisticScoringDecayParameters}; use lightning::util::config::UserConfig; +use lightning::util::hash_tables::*; use lightning::util::ser::Readable; use bitcoin::hashes::Hash; @@ -32,7 +33,6 @@ use bitcoin::network::constants::Network; use crate::utils::test_logger; use std::convert::TryInto; -use hashbrown::HashSet; use std::sync::Arc; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -198,7 +198,7 @@ pub fn do_test(data: &[u8], out: Out) { net_graph: &net_graph, }; - let mut node_pks = HashSet::new(); + let mut node_pks = new_hash_map(); let mut scid = 42; macro_rules! first_hops { @@ -208,7 +208,8 @@ pub fn do_test(data: &[u8], out: Out) { 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 (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(), @@ -242,6 +243,8 @@ pub fn do_test(data: &[u8], out: Out) { config: None, feerate_sat_per_1000_weight: None, channel_shutdown_state: Some(channelmanager::ChannelShutdownState::NotShuttingDown), + pending_inbound_htlcs: Vec::new(), + pending_outbound_htlcs: Vec::new(), }); } Some(&$first_hops_vec[..]) @@ -255,7 +258,8 @@ pub fn do_test(data: &[u8], out: Out) { 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(); + 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, @@ -275,7 +279,7 @@ pub fn do_test(data: &[u8], out: Out) { ($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 { + 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); @@ -295,20 +299,20 @@ pub fn do_test(data: &[u8], out: Out) { return; } let msg = decode_msg_with_len16!(msgs::UnsignedNodeAnnouncement, 288); - node_pks.insert(get_pubkey_from_node_id!(msg.node_id)); + node_pks.insert(get_pubkey_from_node_id!(msg.node_id), ()); let _ = net_graph.update_node_from_unsigned_announcement(&msg); }, 1 => { let msg = decode_msg_with_len16!(msgs::UnsignedChannelAnnouncement, 32+8+33*4); - node_pks.insert(get_pubkey_from_node_id!(msg.node_id_1)); - node_pks.insert(get_pubkey_from_node_id!(msg.node_id_2)); + node_pks.insert(get_pubkey_from_node_id!(msg.node_id_1), ()); + node_pks.insert(get_pubkey_from_node_id!(msg.node_id_2), ()); let _ = net_graph.update_channel_from_unsigned_announcement:: <&FuzzChainSource<'_, '_, Out>>(&msg, &None); }, 2 => { let msg = decode_msg_with_len16!(msgs::UnsignedChannelAnnouncement, 32+8+33*4); - node_pks.insert(get_pubkey_from_node_id!(msg.node_id_1)); - node_pks.insert(get_pubkey_from_node_id!(msg.node_id_2)); + node_pks.insert(get_pubkey_from_node_id!(msg.node_id_1), ()); + node_pks.insert(get_pubkey_from_node_id!(msg.node_id_2), ()); let _ = net_graph.update_channel_from_unsigned_announcement(&msg, &Some(&chain_source)); }, 3 => { @@ -365,7 +369,7 @@ pub fn do_test(data: &[u8], out: Out) { }).collect(); let mut features = Bolt12InvoiceFeatures::empty(); features.set_basic_mpp_optional(); - find_routes!(first_hops, vec![dummy_pk].iter(), |final_amt, _, _| { + find_routes!(first_hops, [(dummy_pk, ())].iter(), |final_amt, _, _| { RouteParameters::from_payment_params_and_value(PaymentParameters::blinded(last_hops.clone()) .with_bolt12_features(features.clone()).unwrap(), final_amt) diff --git a/lightning-background-processor/Cargo.toml b/lightning-background-processor/Cargo.toml index 933cbc466a6..1872a1d0714 100644 --- a/lightning-background-processor/Cargo.toml +++ b/lightning-background-processor/Cargo.toml @@ -1,13 +1,13 @@ [package] name = "lightning-background-processor" -version = "0.0.118" +version = "0.0.121" authors = ["Valentine Wallace "] license = "MIT OR Apache-2.0" repository = "https://github.com/lightningdevkit/rust-lightning" description = """ Utilities to perform required background tasks for Rust Lightning. """ -edition = "2018" +edition = "2021" [package.metadata.docs.rs] all-features = true @@ -22,11 +22,11 @@ default = ["std"] [dependencies] bitcoin = { version = "0.30.2", default-features = false } -lightning = { version = "0.0.118", path = "../lightning", default-features = false } -lightning-rapid-gossip-sync = { version = "0.0.118", path = "../lightning-rapid-gossip-sync", default-features = false } +lightning = { version = "0.0.121", path = "../lightning", default-features = false } +lightning-rapid-gossip-sync = { version = "0.0.121", 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.118", path = "../lightning", features = ["_test_utils"] } -lightning-invoice = { version = "0.26.0", path = "../lightning-invoice" } -lightning-persister = { version = "0.0.118", path = "../lightning-persister" } +tokio = { version = "1.35", features = [ "macros", "rt", "rt-multi-thread", "sync", "time" ] } +lightning = { version = "0.0.121", path = "../lightning", features = ["_test_utils"] } +lightning-invoice = { version = "0.29.0", path = "../lightning-invoice" } +lightning-persister = { version = "0.0.121", path = "../lightning-persister" } diff --git a/lightning-background-processor/src/lib.rs b/lightning-background-processor/src/lib.rs index 449891ba07f..48581766af8 100644 --- a/lightning-background-processor/src/lib.rs +++ b/lightning-background-processor/src/lib.rs @@ -2,9 +2,8 @@ //! running properly, and (2) either can or should be run in the background. See docs for //! [`BackgroundProcessor`] for more details on the nitty-gritty. -// 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)] +#![deny(rustdoc::broken_intra_doc_links)] +#![deny(rustdoc::private_intra_doc_links)] #![deny(missing_docs)] #![cfg_attr(not(feature = "futures"), deny(unsafe_code))] @@ -28,7 +27,10 @@ use lightning::chain::chainmonitor::{ChainMonitor, Persist}; use lightning::sign::{EntropySource, NodeSigner, SignerProvider}; use lightning::events::{Event, PathFailure}; #[cfg(feature = "std")] -use lightning::events::{EventHandler, EventsProvider}; +use lightning::events::EventHandler; +#[cfg(any(feature = "std", feature = "futures"))] +use lightning::events::EventsProvider; + use lightning::ln::channelmanager::ChannelManager; use lightning::ln::msgs::OnionMessageHandler; use lightning::ln::peer_handler::APeerManager; @@ -114,7 +116,7 @@ const ONION_MESSAGE_HANDLER_TIMER: u64 = 1; const NETWORK_PRUNE_TIMER: u64 = 60 * 60; #[cfg(not(test))] -const SCORER_PERSIST_TIMER: u64 = 60 * 60; +const SCORER_PERSIST_TIMER: u64 = 60 * 5; #[cfg(test)] const SCORER_PERSIST_TIMER: u64 = 1; @@ -245,30 +247,30 @@ fn handle_network_graph_update( /// Updates scorer based on event and returns whether an update occurred so we can decide whether /// to persist. fn update_scorer<'a, S: 'static + Deref + Send + Sync, SC: 'a + WriteableScore<'a>>( - scorer: &'a S, event: &Event + scorer: &'a S, event: &Event, duration_since_epoch: Duration, ) -> bool { match event { Event::PaymentPathFailed { ref path, short_channel_id: Some(scid), .. } => { let mut score = scorer.write_lock(); - score.payment_path_failed(path, *scid); + score.payment_path_failed(path, *scid, duration_since_epoch); }, 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); + score.probe_successful(path, duration_since_epoch); }, Event::PaymentPathSuccessful { path, .. } => { let mut score = scorer.write_lock(); - score.payment_path_successful(path); + score.payment_path_successful(path, duration_since_epoch); }, Event::ProbeSuccessful { path, .. } => { let mut score = scorer.write_lock(); - score.probe_successful(path); + score.probe_successful(path, duration_since_epoch); }, Event::ProbeFailed { path, short_channel_id: Some(scid), .. } => { let mut score = scorer.write_lock(); - score.probe_failed(path, *scid); + score.probe_failed(path, *scid, duration_since_epoch); }, _ => return false, } @@ -281,7 +283,7 @@ macro_rules! define_run_body { $channel_manager: ident, $process_channel_manager_events: expr, $peer_manager: ident, $process_onion_message_handler_events: expr, $gossip_sync: ident, $logger: ident, $scorer: ident, $loop_exit_check: expr, $await: expr, $get_timer: expr, - $timer_elapsed: expr, $check_slow_await: expr + $timer_elapsed: expr, $check_slow_await: expr, $time_fetch: expr, ) => { { log_trace!($logger, "Calling ChannelManager's timer_tick_occurred on startup"); $channel_manager.timer_tick_occurred(); @@ -295,6 +297,7 @@ macro_rules! define_run_body { let mut last_scorer_persist_call = $get_timer(SCORER_PERSIST_TIMER); let mut last_rebroadcast_call = $get_timer(REBROADCAST_TIMER); let mut have_pruned = false; + let mut have_decayed_scorer = false; loop { $process_channel_manager_events; @@ -384,11 +387,10 @@ macro_rules! define_run_body { if should_prune { // The network graph must not be pruned while rapid sync completion is pending if let Some(network_graph) = $gossip_sync.prunable_network_graph() { - #[cfg(feature = "std")] { + if let Some(duration_since_epoch) = $time_fetch() { log_trace!($logger, "Pruning and persisting network graph."); - network_graph.remove_stale_channels_and_tracking(); - } - #[cfg(not(feature = "std"))] { + network_graph.remove_stale_channels_and_tracking_with_time(duration_since_epoch.as_secs()); + } else { log_warn!($logger, "Not pruning network graph, consider enabling `std` or doing so manually with remove_stale_channels_and_tracking_with_time."); log_trace!($logger, "Persisting network graph."); } @@ -403,9 +405,24 @@ macro_rules! define_run_body { last_prune_call = $get_timer(prune_timer); } + if !have_decayed_scorer { + if let Some(ref scorer) = $scorer { + if let Some(duration_since_epoch) = $time_fetch() { + log_trace!($logger, "Calling time_passed on scorer at startup"); + scorer.write_lock().time_passed(duration_since_epoch); + } + } + have_decayed_scorer = true; + } + if $timer_elapsed(&mut last_scorer_persist_call, SCORER_PERSIST_TIMER) { if let Some(ref scorer) = $scorer { - log_trace!($logger, "Persisting scorer"); + if let Some(duration_since_epoch) = $time_fetch() { + log_trace!($logger, "Calling time_passed and persisting scorer"); + scorer.write_lock().time_passed(duration_since_epoch); + } else { + log_trace!($logger, "Persisting scorer"); + } if let Err(e) = $persister.persist_scorer(&scorer) { log_error!($logger, "Error: Failed to persist scorer, check your disk and permissions {}", e) } @@ -511,12 +528,16 @@ use core::task; /// are unsure, you should set the flag, as the performance impact of it is minimal unless there /// are hundreds or thousands of simultaneous process calls running. /// +/// The `fetch_time` parameter should return the current wall clock time, if one is available. If +/// no time is available, some features may be disabled, however the node will still operate fine. +/// /// For example, in order to process background events in a [Tokio](https://tokio.rs/) task, you /// could setup `process_events_async` like this: /// ``` /// # use lightning::io; /// # use std::sync::{Arc, RwLock}; /// # use std::sync::atomic::{AtomicBool, Ordering}; +/// # use std::time::SystemTime; /// # use lightning_background_processor::{process_events_async, GossipSync}; /// # struct MyStore {} /// # impl lightning::util::persist::KVStore for MyStore { @@ -585,6 +606,7 @@ use core::task; /// Some(background_scorer), /// sleeper, /// mobile_interruptable_platform, +/// || Some(SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap()) /// ) /// .await /// .expect("Failed to process events"); @@ -621,11 +643,12 @@ pub async fn process_events_async< S: 'static + Deref + Send + Sync, SC: for<'b> WriteableScore<'b>, SleepFuture: core::future::Future + core::marker::Unpin, - Sleeper: Fn(Duration) -> SleepFuture + Sleeper: Fn(Duration) -> SleepFuture, + FetchTime: Fn() -> Option, >( persister: PS, event_handler: EventHandler, chain_monitor: M, channel_manager: CM, gossip_sync: GossipSync, peer_manager: PM, logger: L, scorer: Option, - sleeper: Sleeper, mobile_interruptable_platform: bool, + sleeper: Sleeper, mobile_interruptable_platform: bool, fetch_time: FetchTime, ) -> Result<(), lightning::io::Error> where UL::Target: 'static + UtxoLookup, @@ -649,15 +672,18 @@ where let scorer = &scorer; let logger = &logger; let persister = &persister; + let fetch_time = &fetch_time; async move { if let Some(network_graph) = network_graph { handle_network_graph_update(network_graph, &event) } if let Some(ref scorer) = scorer { - if update_scorer(scorer, &event) { - log_trace!(logger, "Persisting scorer after update"); - if let Err(e) = persister.persist_scorer(&scorer) { - log_error!(logger, "Error: Failed to persist scorer, check your disk and permissions {}", e) + if let Some(duration_since_epoch) = fetch_time() { + if update_scorer(scorer, &event, duration_since_epoch) { + log_trace!(logger, "Persisting scorer after update"); + if let Err(e) = persister.persist_scorer(&scorer) { + log_error!(logger, "Error: Failed to persist scorer, check your disk and permissions {}", e) + } } } } @@ -689,7 +715,7 @@ where task::Poll::Ready(exit) => { should_break = exit; true }, task::Poll::Pending => false, } - }, mobile_interruptable_platform + }, mobile_interruptable_platform, fetch_time, ) } @@ -704,8 +730,6 @@ async fn process_onion_message_handler_events_async< where PM::Target: APeerManager + Send + Sync, { - use lightning::events::EventsProvider; - let events = core::cell::RefCell::new(Vec::new()); peer_manager.onion_message_handler().process_pending_events(&|e| events.borrow_mut().push(e)); @@ -811,7 +835,10 @@ impl BackgroundProcessor { handle_network_graph_update(network_graph, &event) } if let Some(ref scorer) = scorer { - if update_scorer(scorer, &event) { + use std::time::SystemTime; + let duration_since_epoch = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) + .expect("Time should be sometime after 1970"); + if update_scorer(scorer, &event, duration_since_epoch) { log_trace!(logger, "Persisting scorer after update"); if let Err(e) = persister.persist_scorer(&scorer) { log_error!(logger, "Error: Failed to persist scorer, check your disk and permissions {}", e) @@ -827,10 +854,15 @@ impl BackgroundProcessor { peer_manager.onion_message_handler().process_pending_events(&event_handler), gossip_sync, logger, scorer, stop_thread.load(Ordering::Acquire), { Sleeper::from_two_futures( - channel_manager.get_event_or_persistence_needed_future(), - chain_monitor.get_update_future() + &channel_manager.get_event_or_persistence_needed_future(), + &chain_monitor.get_update_future() ).wait_timeout(Duration::from_millis(100)); }, - |_| Instant::now(), |time: &Instant, dur| time.elapsed().as_secs() > dur, false + |_| Instant::now(), |time: &Instant, dur| time.elapsed().as_secs() > dur, false, + || { + use std::time::SystemTime; + Some(SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) + .expect("Time should be sometime after 1970")) + }, ) }); Self { stop_thread: stop_thread_clone, thread_handle: Some(handle) } @@ -897,14 +929,14 @@ mod tests { use lightning::chain::transaction::OutPoint; use lightning::events::{Event, PathFailure, MessageSendEventsProvider, MessageSendEvent}; use lightning::{get_event_msg, get_event}; - use lightning::ln::PaymentHash; + use lightning::ln::{PaymentHash, ChannelId}; use lightning::ln::channelmanager; use lightning::ln::channelmanager::{BREAKDOWN_TIMEOUT, ChainParameters, MIN_CLTV_EXPIRY_DELTA, PaymentId}; use lightning::ln::features::{ChannelFeatures, NodeFeatures}; use lightning::ln::functional_test_utils::*; use lightning::ln::msgs::{ChannelMessageHandler, Init}; use lightning::ln::peer_handler::{PeerManager, MessageHandler, SocketDescriptor, IgnoringMessageHandler}; - use lightning::routing::gossip::{NetworkGraph, NodeId, P2PGossipSync}; + use lightning::routing::gossip::{NetworkGraph, P2PGossipSync}; use lightning::routing::scoring::{ChannelUsage, ScoreUpdate, ScoreLookUp, LockableScore}; use lightning::routing::router::{DefaultRouter, Path, RouteHop, CandidateRouteHop}; use lightning::util::config::UserConfig; @@ -952,6 +984,7 @@ mod tests { Arc>>, Arc, + Arc, Arc>, (), TestScorer> @@ -1118,7 +1151,7 @@ mod tests { } impl ScoreUpdate for TestScorer { - fn payment_path_failed(&mut self, actual_path: &Path, actual_short_channel_id: u64) { + fn payment_path_failed(&mut self, actual_path: &Path, actual_short_channel_id: u64, _: Duration) { if let Some(expectations) = &mut self.event_expectations { match expectations.pop_front().unwrap() { TestResult::PaymentFailure { path, short_channel_id } => { @@ -1138,7 +1171,7 @@ mod tests { } } - fn payment_path_successful(&mut self, actual_path: &Path) { + fn payment_path_successful(&mut self, actual_path: &Path, _: Duration) { if let Some(expectations) = &mut self.event_expectations { match expectations.pop_front().unwrap() { TestResult::PaymentFailure { path, .. } => { @@ -1157,7 +1190,7 @@ mod tests { } } - fn probe_failed(&mut self, actual_path: &Path, _: u64) { + fn probe_failed(&mut self, actual_path: &Path, _: u64, _: Duration) { if let Some(expectations) = &mut self.event_expectations { match expectations.pop_front().unwrap() { TestResult::PaymentFailure { path, .. } => { @@ -1175,7 +1208,7 @@ mod tests { } } } - fn probe_successful(&mut self, actual_path: &Path) { + fn probe_successful(&mut self, actual_path: &Path, _: Duration) { if let Some(expectations) = &mut self.event_expectations { match expectations.pop_front().unwrap() { TestResult::PaymentFailure { path, .. } => { @@ -1193,6 +1226,7 @@ mod tests { } } } + fn time_passed(&mut self, _: Duration) {} } #[cfg(c_bindings)] @@ -1230,12 +1264,12 @@ mod tests { let genesis_block = genesis_block(network); let network_graph = Arc::new(NetworkGraph::new(network, logger.clone())); let scorer = Arc::new(LockingWrapper::new(TestScorer::new())); + let now = Duration::from_secs(genesis_block.header.time as u64); let seed = [i as u8; 32]; - let router = Arc::new(DefaultRouter::new(network_graph.clone(), logger.clone(), seed, scorer.clone(), Default::default())); + let keys_manager = Arc::new(KeysManager::new(&seed, now.as_secs(), now.subsec_nanos())); + let router = Arc::new(DefaultRouter::new(network_graph.clone(), logger.clone(), Arc::clone(&keys_manager), scorer.clone(), Default::default())); let chain_source = Arc::new(test_utils::TestChainSource::new(Network::Bitcoin)); 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(), kv_store.clone())); let best_block = BestBlock::from_network(network); let params = ChainParameters { network, best_block }; @@ -1308,8 +1342,8 @@ mod tests { fn confirm_transaction_depth(node: &mut Node, tx: &Transaction, depth: u32) { for i in 1..=depth { - let prev_blockhash = node.best_block.block_hash(); - let height = node.best_block.height() + 1; + let prev_blockhash = node.best_block.block_hash; + let height = node.best_block.height + 1; let header = create_dummy_header(prev_blockhash, height); let txdata = vec![(0, tx)]; node.best_block = BestBlock::new(header.block_hash(), height); @@ -1381,7 +1415,7 @@ mod tests { } // Force-close the channel. - nodes[0].node.force_close_broadcasting_latest_txn(&OutPoint { txid: tx.txid(), index: 0 }.to_channel_id(), &nodes[1].node.get_our_node_id()).unwrap(); + nodes[0].node.force_close_broadcasting_latest_txn(&ChannelId::v1_from_funding_outpoint(OutPoint { txid: tx.txid(), index: 0 }), &nodes[1].node.get_our_node_id()).unwrap(); // Check that the force-close updates are persisted. check_persisted_data!(nodes[0].node, filepath.clone()); @@ -1470,7 +1504,7 @@ mod tests { tokio::time::sleep(dur).await; false // Never exit }) - }, false, + }, false, || Some(Duration::ZERO), ); match bp_future.await { Ok(_) => panic!("Expected error persisting manager"), @@ -1601,7 +1635,7 @@ mod tests { loop { let log_entries = nodes[0].logger.lines.lock().unwrap(); - let expected_log = "Persisting scorer".to_string(); + let expected_log = "Calling time_passed and persisting scorer".to_string(); if log_entries.get(&("lightning_background_processor", expected_log)).is_some() { break } @@ -1700,7 +1734,7 @@ mod tests { _ = exit_receiver.changed() => true, } }) - }, false, + }, false, || Some(Duration::from_secs(1696300000)), ); let t1 = tokio::spawn(bp_future); @@ -1875,7 +1909,7 @@ mod tests { _ = exit_receiver.changed() => true, } }) - }, false, + }, false, || Some(Duration::ZERO), ); let t1 = tokio::spawn(bp_future); let t2 = tokio::spawn(async move { diff --git a/lightning-block-sync/Cargo.toml b/lightning-block-sync/Cargo.toml index 5a8f887c1d8..5e41f4d19ae 100644 --- a/lightning-block-sync/Cargo.toml +++ b/lightning-block-sync/Cargo.toml @@ -1,13 +1,13 @@ [package] name = "lightning-block-sync" -version = "0.0.118" +version = "0.0.121" authors = ["Jeffrey Czyz", "Matt Corallo"] license = "MIT OR Apache-2.0" repository = "https://github.com/lightningdevkit/rust-lightning" description = """ Utilities to fetch the chain data from a block source and feed them into Rust Lightning. """ -edition = "2018" +edition = "2021" [package.metadata.docs.rs] all-features = true @@ -20,11 +20,11 @@ rpc-client = [ "serde_json", "chunked_transfer" ] [dependencies] bitcoin = "0.30.2" hex = { package = "hex-conservative", version = "0.1.1", default-features = false } -lightning = { version = "0.0.118", path = "../lightning" } -tokio = { version = "1.0", features = [ "io-util", "net", "time" ], optional = true } +lightning = { version = "0.0.121", path = "../lightning" } +tokio = { version = "1.35", features = [ "io-util", "net", "time", "rt" ], optional = true } serde_json = { version = "1.0", optional = true } chunked_transfer = { version = "1.4", optional = true } [dev-dependencies] -lightning = { version = "0.0.118", path = "../lightning", features = ["_test_utils"] } -tokio = { version = "1.14", features = [ "macros", "rt" ] } +lightning = { version = "0.0.121", path = "../lightning", features = ["_test_utils"] } +tokio = { version = "1.35", features = [ "macros", "rt" ] } diff --git a/lightning-block-sync/src/convert.rs b/lightning-block-sync/src/convert.rs index 0f9ab8c43ba..ed811d2cc0c 100644 --- a/lightning-block-sync/src/convert.rs +++ b/lightning-block-sync/src/convert.rs @@ -162,25 +162,8 @@ impl TryInto<(BlockHash, Option)> for JsonResponse { impl TryInto for JsonResponse { type Error = std::io::Error; fn try_into(self) -> std::io::Result { - match self.0.as_str() { - None => Err(std::io::Error::new( - std::io::ErrorKind::InvalidData, - "expected JSON string", - )), - Some(hex_data) => match Vec::::from_hex(hex_data) { - Err(_) => Err(std::io::Error::new( - std::io::ErrorKind::InvalidData, - "invalid hex data", - )), - Ok(txid_data) => match encode::deserialize(&txid_data) { - Err(_) => Err(std::io::Error::new( - std::io::ErrorKind::InvalidData, - "invalid txid", - )), - Ok(txid) => Ok(txid), - }, - }, - } + let hex_data = self.0.as_str().ok_or(Self::Error::new(std::io::ErrorKind::InvalidData, "expected JSON string" ))?; + Txid::from_str(hex_data).map_err(|err|Self::Error::new(std::io::ErrorKind::InvalidData, err.to_string() )) } } @@ -264,10 +247,12 @@ impl TryInto for JsonResponse { /// 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. +#[cfg(feature = "rest-client")] pub(crate) struct GetUtxosResponse { pub(crate) hit_bitmap_nonempty: bool } +#[cfg(feature = "rest-client")] impl TryInto for JsonResponse { type Error = std::io::Error; @@ -622,7 +607,7 @@ pub(crate) mod tests { match TryInto::::try_into(response) { Err(e) => { assert_eq!(e.kind(), std::io::ErrorKind::InvalidData); - assert_eq!(e.get_ref().unwrap().to_string(), "invalid hex data"); + assert_eq!(e.get_ref().unwrap().to_string(), "bad hex string length 6 (expected 64)"); } Ok(_) => panic!("Expected error"), } @@ -634,7 +619,7 @@ pub(crate) mod tests { match TryInto::::try_into(response) { Err(e) => { assert_eq!(e.kind(), std::io::ErrorKind::InvalidData); - assert_eq!(e.get_ref().unwrap().to_string(), "invalid txid"); + assert_eq!(e.get_ref().unwrap().to_string(), "bad hex string length 4 (expected 64)"); } Ok(_) => panic!("Expected error"), } @@ -650,6 +635,20 @@ pub(crate) mod tests { } } + #[test] + fn into_txid_from_bitcoind_rpc_json_response() { + let mut rpc_response = serde_json::json!( + {"error": "", "id": "770", "result": "7934f775149929a8b742487129a7c3a535dfb612f0b726cc67bc10bc2628f906"} + + ); + let r: std::io::Result = JsonResponse(rpc_response.get_mut("result").unwrap().take()) + .try_into(); + assert_eq!( + r.unwrap().to_string(), + "7934f775149929a8b742487129a7c3a535dfb612f0b726cc67bc10bc2628f906" + ); + } + // TryInto can be used in two ways, first with plain hex response where data is // the hex encoded transaction (e.g. as a result of getrawtransaction) or as a JSON object // where the hex encoded transaction can be found in the hex field of the object (if present) diff --git a/lightning-block-sync/src/gossip.rs b/lightning-block-sync/src/gossip.rs index 86aa12121ba..9cd4049679c 100644 --- a/lightning-block-sync/src/gossip.rs +++ b/lightning-block-sync/src/gossip.rs @@ -137,7 +137,6 @@ pub struct GossipVerifier, gossiper: Arc>, Self, L>>, spawn: S, @@ -278,7 +277,6 @@ impl { Vec::new() }, HttpMessageLength::ContentLength(length) => { if length == 0 || length > MAX_HTTP_MESSAGE_BODY_SIZE { - return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "out of range")) + return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, format!("invalid response length: {} bytes", length))); } else { let mut content = vec![0; length]; #[cfg(feature = "tokio")] @@ -727,7 +727,7 @@ pub(crate) mod client_tests { match client.get::("/foo", "foo.com").await { Err(e) => { assert_eq!(e.kind(), std::io::ErrorKind::InvalidData); - assert_eq!(e.get_ref().unwrap().to_string(), "out of range"); + assert_eq!(e.get_ref().unwrap().to_string(), "invalid response length: 8032001 bytes"); }, Ok(_) => panic!("Expected error"), } diff --git a/lightning-block-sync/src/lib.rs b/lightning-block-sync/src/lib.rs index 77ff3f0810b..4a01d4673b3 100644 --- a/lightning-block-sync/src/lib.rs +++ b/lightning-block-sync/src/lib.rs @@ -13,9 +13,8 @@ //! Both features support either blocking I/O using `std::net::TcpStream` or, with feature `tokio`, //! non-blocking I/O using `tokio::net::TcpStream` from inside a Tokio runtime. -// 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)] +#![deny(rustdoc::broken_intra_doc_links)] +#![deny(rustdoc::private_intra_doc_links)] #![deny(missing_docs)] #![deny(unsafe_code)] diff --git a/lightning-custom-message/Cargo.toml b/lightning-custom-message/Cargo.toml index 182ff498512..fbec38431c4 100644 --- a/lightning-custom-message/Cargo.toml +++ b/lightning-custom-message/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lightning-custom-message" -version = "0.0.118" +version = "0.0.121" authors = ["Jeffrey Czyz"] license = "MIT OR Apache-2.0" repository = "https://github.com/lightningdevkit/rust-lightning" @@ -15,4 +15,4 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] bitcoin = "0.30.2" -lightning = { version = "0.0.118", path = "../lightning" } +lightning = { version = "0.0.121", path = "../lightning" } diff --git a/lightning-invoice/Cargo.toml b/lightning-invoice/Cargo.toml index 0b45fa41e6c..1d2a4cdbfbe 100644 --- a/lightning-invoice/Cargo.toml +++ b/lightning-invoice/Cargo.toml @@ -1,14 +1,14 @@ [package] name = "lightning-invoice" description = "Data structures to parse and serialize BOLT11 lightning invoices" -version = "0.26.0" +version = "0.29.0" authors = ["Sebastian Geisler "] documentation = "https://docs.rs/lightning-invoice/" license = "MIT OR Apache-2.0" keywords = [ "lightning", "bitcoin", "invoice", "BOLT11" ] readme = "README.md" repository = "https://github.com/lightningdevkit/rust-lightning/" -edition = "2018" +edition = "2021" [package.metadata.docs.rs] all-features = true @@ -16,20 +16,19 @@ rustdoc-args = ["--cfg", "docsrs"] [features] default = ["std"] -no-std = ["hashbrown", "lightning/no-std"] -std = ["bitcoin_hashes/std", "num-traits/std", "lightning/std", "bech32/std"] +no-std = ["lightning/no-std"] +std = ["bitcoin/std", "num-traits/std", "lightning/std", "bech32/std"] [dependencies] bech32 = { version = "0.9.0", default-features = false } -lightning = { version = "0.0.118", path = "../lightning", default-features = false } +lightning = { version = "0.0.121", path = "../lightning", default-features = false } secp256k1 = { version = "0.27.0", default-features = false, features = ["recovery", "alloc"] } num-traits = { version = "0.2.8", default-features = false } -bitcoin_hashes = { version = "0.12.0", default-features = false } -hashbrown = { version = "0.8", optional = true } serde = { version = "1.0.118", optional = true } bitcoin = { version = "0.30.2", default-features = false } [dev-dependencies] -lightning = { version = "0.0.118", path = "../lightning", default-features = false, features = ["_test_utils"] } +lightning = { version = "0.0.121", path = "../lightning", default-features = false, features = ["_test_utils"] } hex = { package = "hex-conservative", version = "0.1.1", default-features = false } serde_json = { version = "1"} +hashbrown = { version = "0.13", default-features = false } diff --git a/lightning-invoice/fuzz/Cargo.toml b/lightning-invoice/fuzz/Cargo.toml index 0d5fbb365ba..746fe63ba03 100644 --- a/lightning-invoice/fuzz/Cargo.toml +++ b/lightning-invoice/fuzz/Cargo.toml @@ -3,7 +3,7 @@ name = "lightning-invoice-fuzz" version = "0.0.1" authors = ["Automatically generated"] publish = false -edition = "2018" +edition = "2021" [package.metadata] cargo-fuzz = true diff --git a/lightning-invoice/fuzz/fuzz_targets/serde_data_part.rs b/lightning-invoice/fuzz/fuzz_targets/serde_data_part.rs index 406f96764e9..871c6c7d755 100644 --- a/lightning-invoice/fuzz/fuzz_targets/serde_data_part.rs +++ b/lightning-invoice/fuzz/fuzz_targets/serde_data_part.rs @@ -48,9 +48,9 @@ mod tests { for (idx, c) in hex.as_bytes().iter().filter(|&&c| c != b'\n').enumerate() { b <<= 4; match *c { - b'A'...b'F' => b |= c - b'A' + 10, - b'a'...b'f' => b |= c - b'a' + 10, - b'0'...b'9' => b |= c - b'0', + b'A'..=b'F' => b |= c - b'A' + 10, + b'a'..=b'f' => b |= c - b'a' + 10, + b'0'..=b'9' => b |= c - b'0', _ => panic!("Bad hex"), } if (idx & 1) == 1 { diff --git a/lightning-invoice/src/de.rs b/lightning-invoice/src/de.rs index 0276b742f28..c75373ea9da 100644 --- a/lightning-invoice/src/de.rs +++ b/lightning-invoice/src/de.rs @@ -11,8 +11,8 @@ use bech32::{u5, FromBase32}; use bitcoin::{PubkeyHash, ScriptHash}; use bitcoin::address::WitnessVersion; -use bitcoin_hashes::Hash; -use bitcoin_hashes::sha256; +use bitcoin::hashes::Hash; +use bitcoin::hashes::sha256; use crate::prelude::*; use lightning::ln::PaymentSecret; use lightning::routing::gossip::RoutingFees; @@ -564,14 +564,14 @@ impl FromBase32 for Fallback { 17 => { let pkh = match PubkeyHash::from_slice(&bytes) { Ok(pkh) => pkh, - Err(bitcoin_hashes::Error::InvalidLength(_, _)) => return Err(Bolt11ParseError::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(Bolt11ParseError::InvalidScriptHashLength), + Err(bitcoin::hashes::Error::InvalidLength(_, _)) => return Err(Bolt11ParseError::InvalidScriptHashLength), }; Ok(Fallback::ScriptHash(sh)) } @@ -726,7 +726,7 @@ mod test { use crate::de::Bolt11ParseError; use secp256k1::PublicKey; use bech32::u5; - use bitcoin_hashes::sha256; + use bitcoin::hashes::sha256; use std::str::FromStr; const CHARSET_REV: [i8; 128] = [ @@ -856,7 +856,7 @@ mod test { use bech32::FromBase32; use bitcoin::{PubkeyHash, ScriptHash}; use bitcoin::address::WitnessVersion; - use bitcoin_hashes::Hash; + use bitcoin::hashes::Hash; let cases = vec![ ( diff --git a/lightning-invoice/src/lib.rs b/lightning-invoice/src/lib.rs index d87c6c89372..5b326911444 100644 --- a/lightning-invoice/src/lib.rs +++ b/lightning-invoice/src/lib.rs @@ -1,6 +1,5 @@ -// 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)] +#![deny(rustdoc::broken_intra_doc_links)] +#![deny(rustdoc::private_intra_doc_links)] #![deny(missing_docs)] #![deny(non_upper_case_globals)] @@ -31,7 +30,6 @@ pub mod payment; pub mod utils; extern crate bech32; -extern crate bitcoin_hashes; #[macro_use] extern crate lightning; extern crate num_traits; extern crate secp256k1; @@ -47,7 +45,7 @@ use std::time::SystemTime; use bech32::u5; use bitcoin::{Address, Network, PubkeyHash, ScriptHash}; use bitcoin::address::{Payload, WitnessProgram, WitnessVersion}; -use bitcoin_hashes::{Hash, sha256}; +use bitcoin::hashes::{Hash, sha256}; use lightning::ln::features::Bolt11InvoiceFeatures; use lightning::util::invoice::construct_invoice_preimage; @@ -79,25 +77,15 @@ mod de; mod ser; mod tb; +#[allow(unused_imports)] mod prelude { - #[cfg(feature = "hashbrown")] - extern crate hashbrown; - pub use alloc::{vec, vec::Vec, string::String}; - #[cfg(not(feature = "hashbrown"))] - pub use std::collections::{HashMap, hash_map}; - #[cfg(feature = "hashbrown")] - pub use self::hashbrown::{HashMap, HashSet, hash_map}; pub use alloc::string::ToString; } use crate::prelude::*; -/// Sync compat for std/no_std -#[cfg(not(feature = "std"))] -mod sync; - /// Errors that indicate what is wrong with the invoice. They have some granularity for debug /// reasons, but should generally result in an "invalid BOLT11 invoice" message for the user. #[allow(missing_docs)] @@ -167,10 +155,10 @@ pub const DEFAULT_MIN_FINAL_CLTV_EXPIRY_DELTA: u64 = 18; /// extern crate secp256k1; /// extern crate lightning; /// extern crate lightning_invoice; -/// extern crate bitcoin_hashes; +/// extern crate bitcoin; /// -/// use bitcoin_hashes::Hash; -/// use bitcoin_hashes::sha256; +/// use bitcoin::hashes::Hash; +/// use bitcoin::hashes::sha256; /// /// use secp256k1::Secp256k1; /// use secp256k1::SecretKey; @@ -528,7 +516,7 @@ impl Ord for Bolt11InvoiceSignature { /// The encoded route has to be <1024 5bit characters long (<=639 bytes or <=12 hops) /// #[derive(Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] -pub struct PrivateRoute(pub RouteHint); +pub struct PrivateRoute(RouteHint); /// Tag constants as specified in BOLT11 #[allow(missing_docs)] @@ -555,7 +543,7 @@ impl InvoiceBuilder PublicKey { + match self.payee_pub_key() { + Some(pk) => *pk, + None => self.recover_payee_pub_key() + } + } + /// Returns the Duration since the Unix epoch at which the invoice expires. /// Returning None if overflow occurred. pub fn expires_at(&self) -> Option { @@ -1757,7 +1754,7 @@ impl<'de> Deserialize<'de> for Bolt11Invoice { #[cfg(test)] mod test { use bitcoin::ScriptBuf; - use bitcoin_hashes::sha256; + use bitcoin::hashes::sha256; use std::str::FromStr; #[test] @@ -2051,7 +2048,7 @@ mod test { use lightning::routing::router::RouteHintHop; use secp256k1::Secp256k1; use secp256k1::{SecretKey, PublicKey}; - use std::time::{UNIX_EPOCH, Duration}; + use std::time::Duration; let secp_ctx = Secp256k1::new(); @@ -2140,7 +2137,7 @@ mod test { assert_eq!(invoice.currency(), Currency::BitcoinTestnet); #[cfg(feature = "std")] assert_eq!( - invoice.timestamp().duration_since(UNIX_EPOCH).unwrap().as_secs(), + invoice.timestamp().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs(), 1234567 ); assert_eq!(invoice.payee_pub_key(), Some(&public_key)); diff --git a/lightning-invoice/src/payment.rs b/lightning-invoice/src/payment.rs index 7306d12f5e6..8196fa9eb89 100644 --- a/lightning-invoice/src/payment.rs +++ b/lightning-invoice/src/payment.rs @@ -10,7 +10,7 @@ //! Convenient utilities for paying Lightning invoices. use crate::Bolt11Invoice; -use crate::bitcoin_hashes::Hash; +use bitcoin::hashes::Hash; use lightning::ln::PaymentHash; use lightning::ln::channelmanager::RecipientOnionFields; @@ -84,15 +84,13 @@ fn params_from_invoice(invoice: &Bolt11Invoice, amount_msat: u64) mod tests { use super::*; use crate::{InvoiceBuilder, Currency}; - use bitcoin_hashes::sha256::Hash as Sha256; - use lightning::events::Event; - use lightning::ln::channelmanager::{Retry, PaymentId}; - use lightning::ln::msgs::ChannelMessageHandler; + use bitcoin::hashes::sha256::Hash as Sha256; use lightning::ln::PaymentSecret; - use lightning::ln::functional_test_utils::*; use lightning::routing::router::Payee; use secp256k1::{SecretKey, PublicKey, Secp256k1}; - use std::time::{SystemTime, Duration}; + use core::time::Duration; + #[cfg(feature = "std")] + use std::time::SystemTime; fn duration_since_epoch() -> Duration { #[cfg(feature = "std")] @@ -171,6 +169,10 @@ mod tests { #[test] #[cfg(feature = "std")] fn payment_metadata_end_to_end() { + use lightning::events::Event; + use lightning::ln::channelmanager::{Retry, PaymentId}; + use lightning::ln::msgs::ChannelMessageHandler; + use lightning::ln::functional_test_utils::*; // Test that a payment metadata read from an invoice passed to `pay_invoice` makes it all // the way out through the `PaymentClaimable` event. let chanmon_cfgs = create_chanmon_cfgs(2); diff --git a/lightning-invoice/src/ser.rs b/lightning-invoice/src/ser.rs index fe42f72b653..dc63783bfa3 100644 --- a/lightning-invoice/src/ser.rs +++ b/lightning-invoice/src/ser.rs @@ -52,7 +52,7 @@ impl<'a, W: WriteBase32> BytesToBase32<'a, W> { } // Combine all bits from buffer with enough bits from this rounds byte so that they fill - // a u5. Save reamining bits from byte to buffer. + // a u5. Save remaining bits from byte to buffer. let from_buffer = self.buffer >> 3; let from_byte = byte >> (3 + self.buffer_bits); // buffer_bits <= 4 diff --git a/lightning-invoice/src/sync.rs b/lightning-invoice/src/sync.rs deleted file mode 100644 index fae923feb65..00000000000 --- a/lightning-invoice/src/sync.rs +++ /dev/null @@ -1,37 +0,0 @@ -use core::cell::{RefCell, RefMut}; -use core::ops::{Deref, DerefMut}; - -pub type LockResult = Result; - -pub struct Mutex { - inner: RefCell -} - -#[must_use = "if unused the Mutex will immediately unlock"] -pub struct MutexGuard<'a, T: ?Sized + 'a> { - lock: RefMut<'a, T>, -} - -impl Deref for MutexGuard<'_, T> { - type Target = T; - - fn deref(&self) -> &T { - &self.lock.deref() - } -} - -impl DerefMut for MutexGuard<'_, T> { - fn deref_mut(&mut self) -> &mut T { - self.lock.deref_mut() - } -} - -impl Mutex { - pub fn new(inner: T) -> Mutex { - Mutex { inner: RefCell::new(inner) } - } - - pub fn lock<'a>(&'a self) -> LockResult> { - Ok(MutexGuard { lock: self.inner.borrow_mut() }) - } -} diff --git a/lightning-invoice/src/utils.rs b/lightning-invoice/src/utils.rs index 1248221a32e..d45a4e8e646 100644 --- a/lightning-invoice/src/utils.rs +++ b/lightning-invoice/src/utils.rs @@ -4,7 +4,7 @@ use crate::{Bolt11Invoice, CreationError, Currency, InvoiceBuilder, SignOrCreati use crate::{prelude::*, Description, Bolt11InvoiceDescription, Sha256}; use bech32::ToBase32; -use bitcoin_hashes::Hash; +use bitcoin::hashes::Hash; use lightning::chain; use lightning::chain::chaininterface::{BroadcasterInterface, FeeEstimator}; use lightning::sign::{Recipient, NodeSigner, SignerProvider, EntropySource}; @@ -16,6 +16,7 @@ use lightning::routing::gossip::RoutingFees; use lightning::routing::router::{RouteHint, RouteHintHop, Router}; use lightning::util::logger::{Logger, Record}; use secp256k1::PublicKey; +use alloc::collections::{btree_map, BTreeMap}; use core::ops::Deref; use core::time::Duration; use core::iter::Iterator; @@ -603,7 +604,7 @@ fn sort_and_filter_channels( where L::Target: Logger, { - let mut filtered_channels: HashMap = HashMap::new(); + let mut filtered_channels: BTreeMap = BTreeMap::new(); let min_inbound_capacity = min_inbound_capacity_msat.unwrap_or(0); let mut min_capacity_channel_exists = false; let mut online_channel_exists = false; @@ -664,7 +665,7 @@ where } match filtered_channels.entry(channel.counterparty.node_id) { - hash_map::Entry::Occupied(mut entry) => { + btree_map::Entry::Occupied(mut entry) => { let current_max_capacity = entry.get().inbound_capacity_msat; // If this channel is public and the previous channel is not, ensure we replace the // previous channel to avoid announcing non-public channels. @@ -697,7 +698,7 @@ where channel.inbound_capacity_msat); } } - hash_map::Entry::Vacant(entry) => { + btree_map::Entry::Vacant(entry) => { entry.insert(channel); } } @@ -816,14 +817,15 @@ impl<'a, 'b, L: Deref> WithChannelDetails<'a, 'b, L> where L::Target: Logger { #[cfg(test)] mod test { - use core::cell::RefCell; use core::time::Duration; use crate::{Currency, Description, Bolt11InvoiceDescription, SignOrCreationError, CreationError}; - use bitcoin_hashes::{Hash, sha256}; - use bitcoin_hashes::sha256::Hash as Sha256; + use bitcoin::hashes::{Hash, sha256}; + use bitcoin::hashes::sha256::Hash as Sha256; use lightning::sign::PhantomKeysManager; - use lightning::events::{MessageSendEvent, MessageSendEventsProvider, Event, EventsProvider}; - use lightning::ln::{PaymentPreimage, PaymentHash}; + use lightning::events::{MessageSendEvent, MessageSendEventsProvider}; + use lightning::ln::PaymentHash; + #[cfg(feature = "std")] + use lightning::ln::PaymentPreimage; use lightning::ln::channelmanager::{PhantomRouteHints, MIN_FINAL_CLTV_EXPIRY_DELTA, PaymentId, RecipientOnionFields, Retry}; use lightning::ln::functional_test_utils::*; use lightning::ln::msgs::ChannelMessageHandler; @@ -1294,6 +1296,9 @@ mod test { #[cfg(feature = "std")] fn do_test_multi_node_receive(user_generated_pmt_hash: bool) { + use lightning::events::{Event, EventsProvider}; + use core::cell::RefCell; + let mut chanmon_cfgs = create_chanmon_cfgs(3); let seed_1 = [42u8; 32]; let seed_2 = [43u8; 32]; diff --git a/lightning-invoice/tests/ser_de.rs b/lightning-invoice/tests/ser_de.rs index 98886bef788..92bc87bef63 100644 --- a/lightning-invoice/tests/ser_de.rs +++ b/lightning-invoice/tests/ser_de.rs @@ -1,5 +1,4 @@ extern crate bech32; -extern crate bitcoin_hashes; extern crate lightning; extern crate lightning_invoice; extern crate secp256k1; @@ -8,7 +7,7 @@ extern crate hex; use bitcoin::address::WitnessVersion; use bitcoin::{PubkeyHash, ScriptHash}; use bitcoin::hashes::hex::FromHex; -use bitcoin_hashes::{sha256, Hash}; +use bitcoin::hashes::{sha256, Hash}; use lightning::ln::PaymentSecret; use lightning::routing::gossip::RoutingFees; use lightning::routing::router::{RouteHint, RouteHintHop}; diff --git a/lightning-net-tokio/Cargo.toml b/lightning-net-tokio/Cargo.toml index 247481fcfc8..f779a4395b7 100644 --- a/lightning-net-tokio/Cargo.toml +++ b/lightning-net-tokio/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lightning-net-tokio" -version = "0.0.118" +version = "0.0.121" authors = ["Matt Corallo"] license = "MIT OR Apache-2.0" repository = "https://github.com/lightningdevkit/rust-lightning/" @@ -8,7 +8,7 @@ description = """ Implementation of the rust-lightning network stack using Tokio. For Rust-Lightning clients which wish to make direct connections to Lightning P2P nodes, this is a simple alternative to implementing the required network stack, especially for those already using Tokio. """ -edition = "2018" +edition = "2021" [package.metadata.docs.rs] all-features = true @@ -16,9 +16,9 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] bitcoin = "0.30.2" -lightning = { version = "0.0.118", path = "../lightning" } -tokio = { version = "1.0", features = [ "rt", "sync", "net", "time" ] } +lightning = { version = "0.0.121", path = "../lightning" } +tokio = { version = "1.35", features = [ "rt", "sync", "net", "time" ] } [dev-dependencies] -tokio = { version = "1.14", features = [ "macros", "rt", "rt-multi-thread", "sync", "net", "time" ] } -lightning = { version = "0.0.118", path = "../lightning", features = ["_test_utils"] } +tokio = { version = "1.35", features = [ "macros", "rt", "rt-multi-thread", "sync", "net", "time" ] } +lightning = { version = "0.0.121", path = "../lightning", features = ["_test_utils"] } diff --git a/lightning-net-tokio/src/lib.rs b/lightning-net-tokio/src/lib.rs index 8b9c4beba7d..69c24a95c27 100644 --- a/lightning-net-tokio/src/lib.rs +++ b/lightning-net-tokio/src/lib.rs @@ -22,9 +22,8 @@ //! //! [`PeerManager`]: lightning::ln::peer_handler::PeerManager -// 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)] +#![deny(rustdoc::broken_intra_doc_links)] +#![deny(rustdoc::private_intra_doc_links)] #![deny(missing_docs)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] @@ -503,6 +502,9 @@ impl peer_handler::SocketDescriptor for SocketDescriptor { written_len += res; if written_len == data.len() { return written_len; } }, + Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => { + continue; + } Err(_) => return written_len, } }, diff --git a/lightning-persister/Cargo.toml b/lightning-persister/Cargo.toml index 387366bff0e..aad5f2dc1e9 100644 --- a/lightning-persister/Cargo.toml +++ b/lightning-persister/Cargo.toml @@ -1,13 +1,13 @@ [package] name = "lightning-persister" -version = "0.0.118" +version = "0.0.121" authors = ["Valentine Wallace", "Matt Corallo"] license = "MIT OR Apache-2.0" repository = "https://github.com/lightningdevkit/rust-lightning" description = """ Utilities for LDK data persistence and retrieval. """ -edition = "2018" +edition = "2021" [package.metadata.docs.rs] all-features = true @@ -15,7 +15,7 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] bitcoin = "0.30.2" -lightning = { version = "0.0.118", path = "../lightning" } +lightning = { version = "0.0.121", path = "../lightning" } [target.'cfg(windows)'.dependencies] windows-sys = { version = "0.48.0", default-features = false, features = ["Win32_Storage_FileSystem", "Win32_Foundation"] } @@ -24,5 +24,5 @@ windows-sys = { version = "0.48.0", default-features = false, features = ["Win32 criterion = { version = "0.4", optional = true, default-features = false } [dev-dependencies] -lightning = { version = "0.0.118", path = "../lightning", features = ["_test_utils"] } +lightning = { version = "0.0.121", path = "../lightning", features = ["_test_utils"] } bitcoin = { version = "0.30.2", default-features = false } diff --git a/lightning-persister/src/fs_store.rs b/lightning-persister/src/fs_store.rs index 118cf9af7ba..350b1cdd195 100644 --- a/lightning-persister/src/fs_store.rs +++ b/lightning-persister/src/fs_store.rs @@ -381,11 +381,6 @@ mod tests { use lightning::util::persist::read_channel_monitors; use std::fs; use std::str::FromStr; - #[cfg(target_os = "windows")] - use { - lightning::get_event_msg, - lightning::ln::msgs::ChannelMessageHandler, - }; impl Drop for FilesystemStore { fn drop(&mut self) { @@ -455,7 +450,7 @@ mod tests { 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(); + let update_id = update_map.get(&added_monitors[0].1.channel_id()).unwrap(); // Set the store's directory to read-only, which should result in // returning an unrecoverable failure when we then attempt to persist a @@ -494,7 +489,7 @@ mod tests { 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(); + let update_id = update_map.get(&added_monitors[0].1.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 diff --git a/lightning-persister/src/lib.rs b/lightning-persister/src/lib.rs index ae258e137d7..8e7d9055a6a 100644 --- a/lightning-persister/src/lib.rs +++ b/lightning-persister/src/lib.rs @@ -1,8 +1,7 @@ //! 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)] + +#![deny(rustdoc::broken_intra_doc_links)] +#![deny(rustdoc::private_intra_doc_links)] #![deny(missing_docs)] diff --git a/lightning-rapid-gossip-sync/Cargo.toml b/lightning-rapid-gossip-sync/Cargo.toml index 2018e3b2483..5da607c9fa0 100644 --- a/lightning-rapid-gossip-sync/Cargo.toml +++ b/lightning-rapid-gossip-sync/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "lightning-rapid-gossip-sync" -version = "0.0.118" +version = "0.0.121" authors = ["Arik Sosman "] license = "MIT OR Apache-2.0" repository = "https://github.com/lightningdevkit/rust-lightning" -edition = "2018" +edition = "2021" description = """ Utility to process gossip routing data from Rapid Gossip Sync Server. """ @@ -15,11 +15,11 @@ no-std = ["lightning/no-std"] std = ["lightning/std"] [dependencies] -lightning = { version = "0.0.118", path = "../lightning", default-features = false } +lightning = { version = "0.0.121", path = "../lightning", default-features = false } bitcoin = { version = "0.30.2", default-features = false } [target.'cfg(ldk_bench)'.dependencies] criterion = { version = "0.4", optional = true, default-features = false } [dev-dependencies] -lightning = { version = "0.0.118", path = "../lightning", features = ["_test_utils"] } +lightning = { version = "0.0.121", path = "../lightning", features = ["_test_utils"] } diff --git a/lightning-rapid-gossip-sync/src/error.rs b/lightning-rapid-gossip-sync/src/error.rs deleted file mode 100644 index ffd6760f8a9..00000000000 --- a/lightning-rapid-gossip-sync/src/error.rs +++ /dev/null @@ -1,40 +0,0 @@ -use core::fmt::Debug; -use core::fmt::Formatter; -use lightning::ln::msgs::{DecodeError, LightningError}; - -/// All-encompassing standard error type that processing can return -pub enum GraphSyncError { - /// Error trying to read the update data, typically due to an erroneous data length indication - /// that is greater than the actual amount of data provided - DecodeError(DecodeError), - /// Error applying the patch to the network graph, usually the result of updates that are too - /// old or missing prerequisite data to the application of updates out of order - LightningError(LightningError), -} - -impl From for GraphSyncError { - fn from(error: lightning::io::Error) -> Self { - Self::DecodeError(DecodeError::Io(error.kind())) - } -} - -impl From for GraphSyncError { - fn from(error: DecodeError) -> Self { - Self::DecodeError(error) - } -} - -impl From for GraphSyncError { - fn from(error: LightningError) -> Self { - Self::LightningError(error) - } -} - -impl Debug for GraphSyncError { - fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { - match self { - GraphSyncError::DecodeError(e) => f.write_fmt(format_args!("DecodeError: {:?}", e)), - GraphSyncError::LightningError(e) => f.write_fmt(format_args!("LightningError: {:?}", e)) - } - } -} diff --git a/lightning-rapid-gossip-sync/src/lib.rs b/lightning-rapid-gossip-sync/src/lib.rs index c15eedabbe1..68c451a6372 100644 --- a/lightning-rapid-gossip-sync/src/lib.rs +++ b/lightning-rapid-gossip-sync/src/lib.rs @@ -1,6 +1,5 @@ -// 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)] +#![deny(rustdoc::broken_intra_doc_links)] +#![deny(rustdoc::private_intra_doc_links)] #![deny(missing_docs)] #![deny(unsafe_code)] @@ -75,17 +74,42 @@ use core::ops::Deref; use core::sync::atomic::{AtomicBool, Ordering}; use lightning::io; +use lightning::ln::msgs::{DecodeError, LightningError}; use lightning::routing::gossip::NetworkGraph; use lightning::util::logger::Logger; -pub use crate::error::GraphSyncError; - -/// Error types that these functions can return -mod error; - /// Core functionality of this crate mod processing; +/// All-encompassing standard error type that processing can return +#[derive(Debug)] +pub enum GraphSyncError { + /// Error trying to read the update data, typically due to an erroneous data length indication + /// that is greater than the actual amount of data provided + DecodeError(DecodeError), + /// Error applying the patch to the network graph, usually the result of updates that are too + /// old or missing prerequisite data to the application of updates out of order + LightningError(LightningError), +} + +impl From for GraphSyncError { + fn from(error: lightning::io::Error) -> Self { + Self::DecodeError(DecodeError::Io(error.kind())) + } +} + +impl From for GraphSyncError { + fn from(error: DecodeError) -> Self { + Self::DecodeError(error) + } +} + +impl From for GraphSyncError { + fn from(error: LightningError) -> Self { + Self::LightningError(error) + } +} + /// The main Rapid Gossip Sync object. /// /// See [crate-level documentation] for usage. @@ -168,7 +192,7 @@ mod tests { use lightning::ln::msgs::DecodeError; use lightning::routing::gossip::NetworkGraph; use lightning::util::test_utils::TestLogger; - use crate::RapidGossipSync; + use crate::{GraphSyncError, RapidGossipSync}; #[test] fn test_sync_from_file() { @@ -266,7 +290,7 @@ mod tests { let start = std::time::Instant::now(); let sync_result = rapid_sync .sync_network_graph_with_file_path("./res/full_graph.lngossip"); - if let Err(crate::error::GraphSyncError::DecodeError(DecodeError::Io(io_error))) = &sync_result { + if let Err(GraphSyncError::DecodeError(DecodeError::Io(io_error))) = &sync_result { let error_string = format!("Input file lightning-rapid-gossip-sync/res/full_graph.lngossip is missing! Download it from https://bitcoin.ninja/ldk-compressed_graph-285cb27df79-2022-07-21.bin\n\n{:?}", io_error); #[cfg(not(require_route_graph_test))] { diff --git a/lightning-rapid-gossip-sync/src/processing.rs b/lightning-rapid-gossip-sync/src/processing.rs index d54f1329798..9023b9ba38c 100644 --- a/lightning-rapid-gossip-sync/src/processing.rs +++ b/lightning-rapid-gossip-sync/src/processing.rs @@ -14,8 +14,7 @@ use lightning::{log_debug, log_warn, log_trace, log_given_level, log_gossip}; use lightning::util::ser::{BigSize, Readable}; use lightning::io; -use crate::error::GraphSyncError; -use crate::RapidGossipSync; +use crate::{GraphSyncError, RapidGossipSync}; #[cfg(all(feature = "std", not(test)))] use std::time::{SystemTime, UNIX_EPOCH}; @@ -269,9 +268,8 @@ mod tests { use lightning::routing::gossip::NetworkGraph; use lightning::util::test_utils::TestLogger; - use crate::error::GraphSyncError; use crate::processing::STALE_RGS_UPDATE_AGE_LIMIT_SECS; - use crate::RapidGossipSync; + use crate::{GraphSyncError, RapidGossipSync}; const VALID_RGS_BINARY: [u8; 300] = [ 76, 68, 75, 1, 111, 226, 140, 10, 182, 241, 179, 114, 193, 166, 162, 70, 174, 99, 247, diff --git a/lightning-transaction-sync/Cargo.toml b/lightning-transaction-sync/Cargo.toml index 20e03ce6c27..4118d3012ef 100644 --- a/lightning-transaction-sync/Cargo.toml +++ b/lightning-transaction-sync/Cargo.toml @@ -1,20 +1,21 @@ [package] name = "lightning-transaction-sync" -version = "0.0.118" +version = "0.0.121" authors = ["Elias Rohrer"] license = "MIT OR Apache-2.0" repository = "https://github.com/lightningdevkit/rust-lightning" description = """ Utilities for syncing LDK via the transaction-based `Confirm` interface. """ -edition = "2018" +edition = "2021" [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] [features] -default = [] +default = ["time"] +time = [] esplora-async = ["async-interface", "esplora-client/async", "futures"] esplora-async-https = ["esplora-async", "esplora-client/async-https-rustls"] esplora-blocking = ["esplora-client/blocking"] @@ -22,7 +23,7 @@ electrum = ["electrum-client"] async-interface = [] [dependencies] -lightning = { version = "0.0.118", path = "../lightning", default-features = false } +lightning = { version = "0.0.121", path = "../lightning", default-features = false, features = ["std"] } bitcoin = { version = "0.30.2", default-features = false } bdk-macros = "0.6" futures = { version = "0.3", optional = true } @@ -30,6 +31,11 @@ esplora-client = { version = "0.6", default-features = false, optional = true } electrum-client = { version = "0.18.0", optional = true } [dev-dependencies] -lightning = { version = "0.0.118", path = "../lightning", features = ["std", "_test_utils"] } -electrsd = { version = "0.26.0", features = ["legacy", "esplora_a33e97e1", "bitcoind_25_0"] } -tokio = { version = "1.14.0", features = ["full"] } +lightning = { version = "0.0.121", path = "../lightning", default-features = false, features = ["std", "_test_utils"] } +tokio = { version = "1.35.0", features = ["full"] } + +[target.'cfg(all(not(target_os = "windows"), not(no_download)))'.dev-dependencies] +electrsd = { version = "0.26.0", default-features = false, features = ["legacy", "esplora_a33e97e1", "bitcoind_25_0"] } + +[target.'cfg(all(not(target_os = "windows"), no_download))'.dev-dependencies] +electrsd = { version = "0.26.0", default-features = false, features = ["legacy"] } diff --git a/lightning-transaction-sync/src/electrum.rs b/lightning-transaction-sync/src/electrum.rs index 07e11338905..d0c8afef77e 100644 --- a/lightning-transaction-sync/src/electrum.rs +++ b/lightning-transaction-sync/src/electrum.rs @@ -86,6 +86,7 @@ where let mut sync_state = self.sync_state.lock().unwrap(); log_trace!(self.logger, "Starting transaction sync."); + #[cfg(feature = "time")] let start_time = Instant::now(); let mut num_confirmed = 0; let mut num_unconfirmed = 0; @@ -210,10 +211,15 @@ where sync_state.pending_sync = false; } } + #[cfg(feature = "time")] log_debug!(self.logger, "Finished transaction sync at tip {} in {}ms: {} confirmed, {} unconfirmed.", tip_header.block_hash(), start_time.elapsed().as_millis(), num_confirmed, num_unconfirmed); + #[cfg(not(feature = "time"))] + log_debug!(self.logger, + "Finished transaction sync at tip {}: {} confirmed, {} unconfirmed.", + tip_header.block_hash(), num_confirmed, num_unconfirmed); Ok(()) } diff --git a/lightning-transaction-sync/src/esplora.rs b/lightning-transaction-sync/src/esplora.rs index 953f8b0718c..eb52faf3364 100644 --- a/lightning-transaction-sync/src/esplora.rs +++ b/lightning-transaction-sync/src/esplora.rs @@ -14,7 +14,6 @@ use esplora_client::r#async::AsyncClient; #[cfg(not(feature = "async-interface"))] use esplora_client::blocking::BlockingClient; -use std::time::Instant; use std::collections::HashSet; use core::ops::Deref; @@ -91,7 +90,8 @@ where let mut sync_state = self.sync_state.lock().await; log_trace!(self.logger, "Starting transaction sync."); - let start_time = Instant::now(); + #[cfg(feature = "time")] + let start_time = std::time::Instant::now(); let mut num_confirmed = 0; let mut num_unconfirmed = 0; @@ -227,8 +227,12 @@ where sync_state.pending_sync = false; } } + #[cfg(feature = "time")] log_debug!(self.logger, "Finished transaction sync at tip {} in {}ms: {} confirmed, {} unconfirmed.", tip_hash, start_time.elapsed().as_millis(), num_confirmed, num_unconfirmed); + #[cfg(not(feature = "time"))] + log_debug!(self.logger, "Finished transaction sync at tip {}: {} confirmed, {} unconfirmed.", + tip_hash, num_confirmed, num_unconfirmed); Ok(()) } diff --git a/lightning-transaction-sync/src/lib.rs b/lightning-transaction-sync/src/lib.rs index 21b6a4e97c1..7bd4b4aee3f 100644 --- a/lightning-transaction-sync/src/lib.rs +++ b/lightning-transaction-sync/src/lib.rs @@ -58,9 +58,8 @@ //! [`ChainMonitor`]: lightning::chain::chainmonitor::ChainMonitor //! [`ChannelManager`]: lightning::ln::channelmanager::ChannelManager -// 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)] +#![deny(rustdoc::broken_intra_doc_links)] +#![deny(rustdoc::private_intra_doc_links)] #![deny(missing_docs)] #![deny(unsafe_code)] diff --git a/lightning-transaction-sync/tests/integration_tests.rs b/lightning-transaction-sync/tests/integration_tests.rs index 8aadf9a2ed1..48044b236bf 100644 --- a/lightning-transaction-sync/tests/integration_tests.rs +++ b/lightning-transaction-sync/tests/integration_tests.rs @@ -1,4 +1,4 @@ -#![cfg(any(feature = "esplora-blocking", feature = "esplora-async", feature = "electrum"))] +#![cfg(all(not(target_os = "windows"), any(feature = "esplora-blocking", feature = "esplora-async", feature = "electrum")))] #[cfg(any(feature = "esplora-blocking", feature = "esplora-async"))] use lightning_transaction_sync::EsploraSyncClient; diff --git a/lightning/Cargo.toml b/lightning/Cargo.toml index 76d751af391..96070e4d0dd 100644 --- a/lightning/Cargo.toml +++ b/lightning/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lightning" -version = "0.0.118" +version = "0.0.121" authors = ["Matt Corallo"] license = "MIT OR Apache-2.0" repository = "https://github.com/lightningdevkit/rust-lightning/" @@ -9,7 +9,7 @@ A Bitcoin Lightning library in Rust. Does most of the hard work, without implying a specific runtime, requiring clients implement basic network logic, chain interactions and disk storage. Still missing tons of error-handling. See GitHub issues for suggested projects if you want to contribute. Don't have to bother telling you not to use this for anything serious, because you'd have to build a client around it to even try. """ -edition = "2018" +edition = "2021" [package.metadata.docs.rs] features = ["std"] @@ -31,7 +31,7 @@ unsafe_revoked_tx_signing = [] # Override signing to not include randomness when generating signatures for test vectors. _test_vectors = [] -no-std = ["hashbrown", "bitcoin/no-std", "core2/alloc"] +no-std = ["hashbrown", "possiblyrandom", "bitcoin/no-std", "core2/alloc", "libm"] std = ["bitcoin/std"] # Generates low-r bitcoin signatures, which saves 1 byte in 50% of the cases @@ -42,12 +42,14 @@ default = ["std", "grind_signatures"] [dependencies] bitcoin = { version = "0.30.2", default-features = false, features = ["secp-recovery"] } -hashbrown = { version = "0.8", optional = true } +hashbrown = { version = "0.13", optional = true, default-features = false } +possiblyrandom = { version = "0.1", optional = true, default-features = false } hex = { package = "hex-conservative", version = "0.1.1", default-features = false } regex = { version = "1.5.6", optional = true } backtrace = { version = "0.3", optional = true } core2 = { version = "0.3.0", optional = true, default-features = false } +libm = { version = "0.2", optional = true, default-features = false } [dev-dependencies] regex = "1.5.6" diff --git a/lightning/src/blinded_path/message.rs b/lightning/src/blinded_path/message.rs index d2e81444ef6..3a5541fa146 100644 --- a/lightning/src/blinded_path/message.rs +++ b/lightning/src/blinded_path/message.rs @@ -5,10 +5,10 @@ use crate::blinded_path::utils; use crate::io; use crate::io::Cursor; use crate::ln::onion_utils; -use crate::onion_message::ControlTlvs; +use crate::onion_message::packet::ControlTlvs; use crate::prelude::*; use crate::sign::{NodeSigner, Recipient}; -use crate::util::chacha20poly1305rfc::ChaChaPolyReadAdapter; +use crate::crypto::streams::ChaChaPolyReadAdapter; use crate::util::ser::{FixedLengthReader, LengthReadableArgs, Writeable, Writer}; use core::mem; diff --git a/lightning/src/blinded_path/mod.rs b/lightning/src/blinded_path/mod.rs index d75b4f25b36..e70f310f5e1 100644 --- a/lightning/src/blinded_path/mod.rs +++ b/lightning/src/blinded_path/mod.rs @@ -85,14 +85,15 @@ impl BlindedPath { /// Create a one-hop blinded path for a payment. pub fn one_hop_for_payment( - payee_node_id: PublicKey, payee_tlvs: payment::ReceiveTlvs, entropy_source: &ES, - secp_ctx: &Secp256k1 + payee_node_id: PublicKey, payee_tlvs: payment::ReceiveTlvs, min_final_cltv_expiry_delta: u16, + entropy_source: &ES, secp_ctx: &Secp256k1 ) -> 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 + &[], payee_node_id, payee_tlvs, htlc_maximum_msat, min_final_cltv_expiry_delta, + entropy_source, secp_ctx ) } @@ -105,15 +106,17 @@ impl BlindedPath { /// /// [`ForwardTlvs`]: crate::blinded_path::payment::ForwardTlvs // TODO: make all payloads the same size with padding + add dummy hops - pub(crate) fn new_for_payment( + pub fn new_for_payment( intermediate_nodes: &[payment::ForwardNode], payee_node_id: PublicKey, - payee_tlvs: payment::ReceiveTlvs, htlc_maximum_msat: u64, entropy_source: &ES, - secp_ctx: &Secp256k1 + payee_tlvs: payment::ReceiveTlvs, htlc_maximum_msat: u64, min_final_cltv_expiry_delta: u16, + entropy_source: &ES, secp_ctx: &Secp256k1 ) -> 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"); - let blinded_payinfo = payment::compute_payinfo(intermediate_nodes, &payee_tlvs, htlc_maximum_msat)?; + let blinded_payinfo = payment::compute_payinfo( + intermediate_nodes, &payee_tlvs, htlc_maximum_msat, min_final_cltv_expiry_delta + )?; 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), diff --git a/lightning/src/blinded_path/payment.rs b/lightning/src/blinded_path/payment.rs index 7b604fbdcb1..106cd802caa 100644 --- a/lightning/src/blinded_path/payment.rs +++ b/lightning/src/blinded_path/payment.rs @@ -8,6 +8,7 @@ use crate::blinded_path::BlindedHop; use crate::blinded_path::utils; use crate::io; use crate::ln::PaymentSecret; +use crate::ln::channelmanager::CounterpartyForwardingInfo; use crate::ln::features::BlindedHopFeatures; use crate::ln::msgs::DecodeError; use crate::offers::invoice::BlindedPayInfo; @@ -96,6 +97,27 @@ pub struct PaymentConstraints { pub htlc_minimum_msat: u64, } +impl TryFrom for PaymentRelay { + type Error = (); + + fn try_from(info: CounterpartyForwardingInfo) -> Result { + let CounterpartyForwardingInfo { + fee_base_msat, fee_proportional_millionths, cltv_expiry_delta + } = info; + + // Avoid exposing esoteric CLTV expiry deltas + let cltv_expiry_delta = match cltv_expiry_delta { + 0..=40 => 40, + 41..=80 => 80, + 81..=144 => 144, + 145..=216 => 216, + _ => return Err(()), + }; + + Ok(Self { cltv_expiry_delta, fee_proportional_millionths, fee_base_msat }) + } +} + impl Writeable for ForwardTlvs { fn write(&self, w: &mut W) -> Result<(), io::Error> { encode_tlv_stream!(w, { @@ -193,11 +215,12 @@ pub(crate) fn amt_to_forward_msat(inbound_amt_msat: u64, payment_relay: &Payment } pub(super) fn compute_payinfo( - intermediate_nodes: &[ForwardNode], payee_tlvs: &ReceiveTlvs, payee_htlc_maximum_msat: u64 + intermediate_nodes: &[ForwardNode], payee_tlvs: &ReceiveTlvs, payee_htlc_maximum_msat: u64, + min_final_cltv_expiry_delta: u16 ) -> Result { let mut curr_base_fee: u64 = 0; let mut curr_prop_mil: u64 = 0; - let mut cltv_expiry_delta: u16 = 0; + let mut cltv_expiry_delta: u16 = min_final_cltv_expiry_delta; 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. @@ -270,6 +293,7 @@ mod tests { use crate::blinded_path::payment::{ForwardNode, ForwardTlvs, ReceiveTlvs, PaymentConstraints, PaymentRelay}; use crate::ln::PaymentSecret; use crate::ln::features::BlindedHopFeatures; + use crate::ln::functional_test_utils::TEST_FINAL_CLTV; #[test] fn compute_payinfo() { @@ -317,10 +341,10 @@ mod tests { }, }; let htlc_maximum_msat = 100_000; - let blinded_payinfo = super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, htlc_maximum_msat).unwrap(); + let blinded_payinfo = super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, htlc_maximum_msat, 12).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.cltv_expiry_delta, 300); assert_eq!(blinded_payinfo.htlc_minimum_msat, 900); assert_eq!(blinded_payinfo.htlc_maximum_msat, htlc_maximum_msat); } @@ -334,10 +358,10 @@ mod tests { htlc_minimum_msat: 1, }, }; - let blinded_payinfo = super::compute_payinfo(&[], &recv_tlvs, 4242).unwrap(); + let blinded_payinfo = super::compute_payinfo(&[], &recv_tlvs, 4242, TEST_FINAL_CLTV as u16).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.cltv_expiry_delta, TEST_FINAL_CLTV as u16); assert_eq!(blinded_payinfo.htlc_minimum_msat, 1); assert_eq!(blinded_payinfo.htlc_maximum_msat, 4242); } @@ -388,7 +412,7 @@ mod tests { }, }; let htlc_maximum_msat = 100_000; - let blinded_payinfo = super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, htlc_maximum_msat).unwrap(); + let blinded_payinfo = super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, htlc_maximum_msat, TEST_FINAL_CLTV as u16).unwrap(); assert_eq!(blinded_payinfo.htlc_minimum_msat, 2_000); } @@ -438,10 +462,10 @@ mod tests { }, }; let htlc_minimum_msat = 3798; - assert!(super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, htlc_minimum_msat - 1).is_err()); + assert!(super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, htlc_minimum_msat - 1, TEST_FINAL_CLTV as u16).is_err()); let htlc_maximum_msat = htlc_minimum_msat + 1; - let blinded_payinfo = super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, htlc_maximum_msat).unwrap(); + let blinded_payinfo = super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, htlc_maximum_msat, TEST_FINAL_CLTV as u16).unwrap(); assert_eq!(blinded_payinfo.htlc_minimum_msat, htlc_minimum_msat); assert_eq!(blinded_payinfo.htlc_maximum_msat, htlc_maximum_msat); } @@ -492,7 +516,7 @@ mod tests { }, }; - let blinded_payinfo = super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, 10_000).unwrap(); + let blinded_payinfo = super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, 10_000, TEST_FINAL_CLTV as u16).unwrap(); assert_eq!(blinded_payinfo.htlc_maximum_msat, 3997); } } diff --git a/lightning/src/blinded_path/utils.rs b/lightning/src/blinded_path/utils.rs index 2e691898dc2..d4894a86aa1 100644 --- a/lightning/src/blinded_path/utils.rs +++ b/lightning/src/blinded_path/utils.rs @@ -18,8 +18,8 @@ use bitcoin::secp256k1::ecdh::SharedSecret; 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::onion_message::messenger::Destination; +use crate::crypto::streams::ChaChaPolyWriteAdapter; use crate::util::ser::{Readable, Writeable}; use crate::io; diff --git a/lightning/src/chain/chaininterface.rs b/lightning/src/chain/chaininterface.rs index 2d7f0c18af3..1f42dc2fe42 100644 --- a/lightning/src/chain/chaininterface.rs +++ b/lightning/src/chain/chaininterface.rs @@ -40,7 +40,7 @@ pub trait BroadcasterInterface { /// be sure to manage both cases correctly. /// /// Bitcoin transaction packages are defined in BIP 331 and here: - /// https://github.com/bitcoin/bitcoin/blob/master/doc/policy/packages.md + /// fn broadcast_transactions(&self, txs: &[&Transaction]); } diff --git a/lightning/src/chain/chainmonitor.rs b/lightning/src/chain/chainmonitor.rs index 1fe3fcd9ff1..015b3dacfc3 100644 --- a/lightning/src/chain/chainmonitor.rs +++ b/lightning/src/chain/chainmonitor.rs @@ -31,18 +31,18 @@ use crate::chain::{ChannelMonitorUpdateStatus, Filter, WatchedOutput}; use crate::chain::chaininterface::{BroadcasterInterface, FeeEstimator}; use crate::chain::channelmonitor::{ChannelMonitor, ChannelMonitorUpdate, Balance, MonitorEvent, TransactionOutputs, WithChannelMonitor, LATENCY_GRACE_PERIOD_BLOCKS}; use crate::chain::transaction::{OutPoint, TransactionData}; +use crate::ln::ChannelId; use crate::sign::ecdsa::WriteableEcdsaChannelSigner; use crate::events; use crate::events::{Event, EventHandler}; use crate::util::atomic_counter::AtomicCounter; -use crate::util::logger::Logger; +use crate::util::logger::{Logger, WithContext}; use crate::util::errors::APIError; use crate::util::wakers::{Future, Notifier}; use crate::ln::channelmanager::ChannelDetails; use crate::prelude::*; use crate::sync::{RwLock, RwLockReadGuard, Mutex, MutexGuard}; -use core::iter::FromIterator; use core::ops::Deref; use core::sync::atomic::{AtomicUsize, Ordering}; use bitcoin::secp256k1::PublicKey; @@ -158,7 +158,7 @@ pub trait Persist { /// /// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager /// [`Writeable::write`]: crate::util::ser::Writeable::write - fn persist_new_channel(&self, channel_id: OutPoint, data: &ChannelMonitor, update_id: MonitorUpdateId) -> ChannelMonitorUpdateStatus; + fn persist_new_channel(&self, channel_funding_outpoint: OutPoint, data: &ChannelMonitor, update_id: MonitorUpdateId) -> ChannelMonitorUpdateStatus; /// Update one channel's data. The provided [`ChannelMonitor`] has already applied the given /// update. @@ -193,7 +193,7 @@ pub trait Persist { /// [`ChannelMonitorUpdateStatus`] for requirements when returning errors. /// /// [`Writeable::write`]: crate::util::ser::Writeable::write - fn update_persisted_channel(&self, channel_id: OutPoint, update: Option<&ChannelMonitorUpdate>, data: &ChannelMonitor, update_id: MonitorUpdateId) -> ChannelMonitorUpdateStatus; + fn update_persisted_channel(&self, channel_funding_outpoint: OutPoint, update: Option<&ChannelMonitorUpdate>, data: &ChannelMonitor, update_id: MonitorUpdateId) -> ChannelMonitorUpdateStatus; } struct MonitorHolder { @@ -287,7 +287,7 @@ pub struct ChainMonitor, Option)>>, + pending_monitor_events: Mutex, Option)>>, /// The best block height seen, used as a proxy for the passage of time. highest_chain_height: AtomicUsize, @@ -317,7 +317,7 @@ where C::Target: chain::Filter, FN: Fn(&ChannelMonitor, &TransactionData) -> Vec { let err_str = "ChannelMonitor[Update] persistence failed unrecoverably. This indicates we cannot continue normal operation and must shut down."; - let funding_outpoints: HashSet = HashSet::from_iter(self.monitors.read().unwrap().keys().cloned()); + let funding_outpoints = hash_set_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) { @@ -402,7 +402,8 @@ where C::Target: chain::Filter, outpoint: OutPoint { txid, index: idx as u16 }, script_pubkey: output.script_pubkey, }; - chain_source.register_output(output) + log_trace!(logger, "Adding monitoring for spends of outpoint {} to the filter", output.outpoint); + chain_source.register_output(output); } } } @@ -418,7 +419,7 @@ where C::Target: chain::Filter, /// transactions relevant to the watched channels. pub fn new(chain_source: Option, broadcaster: T, logger: L, feeest: F, persister: P) -> Self { Self { - monitors: RwLock::new(HashMap::new()), + monitors: RwLock::new(new_hash_map()), sync_persistence_id: AtomicCounter::new(), chain_source, broadcaster, @@ -470,20 +471,23 @@ where C::Target: chain::Filter, } } - /// Lists the funding outpoint of each [`ChannelMonitor`] being monitored. + /// Lists the funding outpoint and channel ID of each [`ChannelMonitor`] being monitored. /// /// Note that [`ChannelMonitor`]s are not removed when a channel is closed as they are always /// monitoring for on-chain state resolutions. - pub fn list_monitors(&self) -> Vec { - self.monitors.read().unwrap().keys().map(|outpoint| *outpoint).collect() + pub fn list_monitors(&self) -> Vec<(OutPoint, ChannelId)> { + self.monitors.read().unwrap().iter().map(|(outpoint, monitor_holder)| { + let channel_id = monitor_holder.monitor.channel_id(); + (*outpoint, channel_id) + }).collect() } #[cfg(not(c_bindings))] /// Lists the pending updates for each [`ChannelMonitor`] (by `OutPoint` being monitored). pub fn list_pending_monitor_updates(&self) -> HashMap> { - self.monitors.read().unwrap().iter().map(|(outpoint, holder)| { + hash_map_from_iter(self.monitors.read().unwrap().iter().map(|(outpoint, holder)| { (*outpoint, holder.pending_monitor_updates.lock().unwrap().clone()) - }).collect() + })) } #[cfg(c_bindings)] @@ -541,8 +545,9 @@ where C::Target: chain::Filter, // Completed event. return Ok(()); } - self.pending_monitor_events.lock().unwrap().push((funding_txo, vec![MonitorEvent::Completed { - funding_txo, + let channel_id = monitor_data.monitor.channel_id(); + self.pending_monitor_events.lock().unwrap().push((funding_txo, channel_id, vec![MonitorEvent::Completed { + funding_txo, channel_id, monitor_update_id: monitor_data.monitor.get_latest_update_id(), }], monitor_data.monitor.get_counterparty_node_id())); }, @@ -564,9 +569,14 @@ where C::Target: chain::Filter, #[cfg(any(test, fuzzing))] pub fn force_channel_monitor_updated(&self, funding_txo: OutPoint, monitor_update_id: u64) { let monitors = self.monitors.read().unwrap(); - let counterparty_node_id = monitors.get(&funding_txo).and_then(|m| m.monitor.get_counterparty_node_id()); - self.pending_monitor_events.lock().unwrap().push((funding_txo, vec![MonitorEvent::Completed { + let (counterparty_node_id, channel_id) = if let Some(m) = monitors.get(&funding_txo) { + (m.monitor.get_counterparty_node_id(), m.monitor.channel_id()) + } else { + (None, ChannelId::v1_from_funding_outpoint(funding_txo)) + }; + self.pending_monitor_events.lock().unwrap().push((funding_txo, channel_id, vec![MonitorEvent::Completed { funding_txo, + channel_id, monitor_update_id, }], counterparty_node_id)); self.event_notifier.notify(); @@ -625,6 +635,27 @@ where C::Target: chain::Filter, ) } } + + /// Triggers rebroadcasts of pending claims from force-closed channels after a transaction + /// signature generation failure. + /// + /// `monitor_opt` can be used as a filter to only trigger them for a specific channel monitor. + pub fn signer_unblocked(&self, monitor_opt: Option) { + let monitors = self.monitors.read().unwrap(); + if let Some(funding_txo) = monitor_opt { + if let Some(monitor_holder) = monitors.get(&funding_txo) { + monitor_holder.monitor.signer_unblocked( + &*self.broadcaster, &*self.fee_estimator, &self.logger + ) + } + } else { + for (_, monitor_holder) in &*monitors { + monitor_holder.monitor.signer_unblocked( + &*self.broadcaster, &*self.fee_estimator, &self.logger + ) + } + } + } } impl @@ -741,7 +772,7 @@ where C::Target: chain::Filter, }, } if let Some(ref chain_source) = self.chain_source { - monitor.load_outputs_to_watch(chain_source); + monitor.load_outputs_to_watch(chain_source , &self.logger); } entry.insert(MonitorHolder { monitor, @@ -752,11 +783,15 @@ where C::Target: chain::Filter, } fn update_channel(&self, funding_txo: OutPoint, update: &ChannelMonitorUpdate) -> ChannelMonitorUpdateStatus { + // `ChannelMonitorUpdate`'s `channel_id` is `None` prior to 0.0.121 and all channels in those + // versions are V1-established. For 0.0.121+ the `channel_id` fields is always `Some`. + let channel_id = update.channel_id.unwrap_or(ChannelId::v1_from_funding_outpoint(funding_txo)); // Update the monitor that watches the channel referred to by the given outpoint. let monitors = self.monitors.read().unwrap(); match monitors.get(&funding_txo) { None => { - log_error!(self.logger, "Failed to update channel monitor: no such monitor registered"); + let logger = WithContext::from(&self.logger, update.counterparty_node_id, Some(channel_id)); + log_error!(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 @@ -813,7 +848,7 @@ where C::Target: chain::Filter, } } - fn release_pending_monitor_events(&self) -> Vec<(OutPoint, Vec, Option)> { + fn release_pending_monitor_events(&self) -> Vec<(OutPoint, ChannelId, Vec, Option)> { let mut pending_monitor_events = self.pending_monitor_events.lock().unwrap().split_off(0); for monitor_state in self.monitors.read().unwrap().values() { let logger = WithChannelMonitor::from(&self.logger, &monitor_state.monitor); @@ -827,8 +862,9 @@ where C::Target: chain::Filter, let monitor_events = monitor_state.monitor.get_and_clear_pending_monitor_events(); if monitor_events.len() > 0 { let monitor_outpoint = monitor_state.monitor.get_funding_txo().0; + let monitor_channel_id = monitor_state.monitor.channel_id(); let counterparty_node_id = monitor_state.monitor.get_counterparty_node_id(); - pending_monitor_events.push((monitor_outpoint, monitor_events, counterparty_node_id)); + pending_monitor_events.push((monitor_outpoint, monitor_channel_id, monitor_events, counterparty_node_id)); } } } diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index 6573fa0df65..f998a41fc3b 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -44,7 +44,7 @@ use crate::chain::{BestBlock, WatchedOutput}; use crate::chain::chaininterface::{BroadcasterInterface, FeeEstimator, LowerBoundedFeeEstimator}; use crate::chain::transaction::{OutPoint, TransactionData}; use crate::sign::{ChannelDerivationParameters, HTLCDescriptor, SpendableOutputDescriptor, StaticPaymentOutputDescriptor, DelayedPaymentOutputDescriptor, ecdsa::WriteableEcdsaChannelSigner, SignerProvider, EntropySource}; -use crate::chain::onchaintx::{ClaimEvent, OnchainTxHandler}; +use crate::chain::onchaintx::{ClaimEvent, FeerateStrategy, OnchainTxHandler}; use crate::chain::package::{CounterpartyOfferedHTLCOutput, CounterpartyReceivedHTLCOutput, HolderFundingOutput, HolderHTLCOutput, PackageSolvingData, PackageTemplate, RevokedOutput, RevokedHTLCOutput}; use crate::chain::Filter; use crate::util::logger::{Logger, Record}; @@ -71,6 +71,15 @@ use crate::sync::{Mutex, LockTestExt}; #[must_use] pub struct ChannelMonitorUpdate { pub(crate) updates: Vec, + /// Historically, [`ChannelMonitor`]s didn't know their counterparty node id. However, + /// `ChannelManager` really wants to know it so that it can easily look up the corresponding + /// channel. For now, this results in a temporary map in `ChannelManager` to look up channels + /// by only the funding outpoint. + /// + /// To eventually remove that, we repeat the counterparty node id here so that we can upgrade + /// `ChannelMonitor`s to become aware of the counterparty node id if they were generated prior + /// to when it was stored directly in them. + pub(crate) counterparty_node_id: Option, /// The sequence number of this update. Updates *must* be replayed in-order according to this /// sequence number (and updates may panic if they are not). The update_id values are strictly /// increasing and increase by one for each new update, with two exceptions specified below. @@ -87,6 +96,11 @@ pub struct ChannelMonitorUpdate { /// /// [`ChannelMonitorUpdateStatus::InProgress`]: super::ChannelMonitorUpdateStatus::InProgress pub update_id: u64, + /// The channel ID associated with these updates. + /// + /// Will be `None` for `ChannelMonitorUpdate`s constructed on LDK versions prior to 0.0.121 and + /// always `Some` otherwise. + pub channel_id: Option, } /// The update ID used for a [`ChannelMonitorUpdate`] that is either: @@ -107,7 +121,10 @@ impl Writeable for ChannelMonitorUpdate { for update_step in self.updates.iter() { update_step.write(w)?; } - write_tlv_fields!(w, {}); + write_tlv_fields!(w, { + (1, self.counterparty_node_id, option), + (3, self.channel_id, option), + }); Ok(()) } } @@ -122,8 +139,13 @@ impl Readable for ChannelMonitorUpdate { updates.push(upd); } } - read_tlv_fields!(r, {}); - Ok(Self { update_id, updates }) + let mut counterparty_node_id = None; + let mut channel_id = None; + read_tlv_fields!(r, { + (1, counterparty_node_id, option), + (3, channel_id, option), + }); + Ok(Self { update_id, counterparty_node_id, updates, channel_id }) } } @@ -144,6 +166,8 @@ pub enum MonitorEvent { Completed { /// The funding outpoint of the [`ChannelMonitor`] that was updated funding_txo: OutPoint, + /// The channel ID of the channel associated with the [`ChannelMonitor`] + channel_id: ChannelId, /// The Update ID from [`ChannelMonitorUpdate::update_id`] which was applied or /// [`ChannelMonitor::get_latest_update_id`]. /// @@ -158,6 +182,7 @@ impl_writeable_tlv_based_enum_upgradable!(MonitorEvent, (0, Completed) => { (0, funding_txo, required), (2, monitor_update_id, required), + (4, channel_id, required), }, ; (2, HTLCEvent), @@ -362,7 +387,7 @@ impl OnchainEventEntry { } fn has_reached_confirmation_threshold(&self, best_block: &BestBlock) -> bool { - best_block.height() >= self.confirmation_threshold() + best_block.height >= self.confirmation_threshold() } } @@ -758,6 +783,7 @@ pub(crate) struct ChannelMonitorImpl { channel_keys_id: [u8; 32], holder_revocation_basepoint: RevocationBasepoint, + channel_id: ChannelId, funding_info: (OutPoint, ScriptBuf), current_counterparty_commitment_txid: Option, prev_counterparty_commitment_txid: Option, @@ -1051,8 +1077,8 @@ impl Writeable for ChannelMonitorImpl Writeable for ChannelMonitorImpl WithChannelMonitor<'a, L> where L::Target: Logger { pub(crate) fn from_impl(logger: &'a L, monitor_impl: &ChannelMonitorImpl) -> Self { let peer_id = monitor_impl.counterparty_node_id; - let channel_id = Some(monitor_impl.funding_info.0.to_channel_id()); + let channel_id = Some(monitor_impl.channel_id()); WithChannelMonitor { logger, peer_id, channel_id, } @@ -1167,7 +1194,8 @@ impl ChannelMonitor { funding_redeemscript: ScriptBuf, channel_value_satoshis: u64, commitment_transaction_number_obscure_factor: u64, initial_holder_commitment_tx: HolderCommitmentTransaction, - best_block: BestBlock, counterparty_node_id: PublicKey) -> ChannelMonitor { + best_block: BestBlock, counterparty_node_id: PublicKey, channel_id: ChannelId, + ) -> ChannelMonitor { assert!(commitment_transaction_number_obscure_factor <= (1 << 48)); let counterparty_payment_script = chan_utils::get_counterparty_payment_script( @@ -1207,7 +1235,7 @@ impl ChannelMonitor { channel_parameters.clone(), initial_holder_commitment_tx, secp_ctx ); - let mut outputs_to_watch = HashMap::new(); + let mut outputs_to_watch = new_hash_map(); outputs_to_watch.insert(funding_info.0.txid, vec![(funding_info.0.index as u32, funding_info.1.clone())]); Self::from_impl(ChannelMonitorImpl { @@ -1221,6 +1249,7 @@ impl ChannelMonitor { channel_keys_id, holder_revocation_basepoint, + channel_id, funding_info, current_counterparty_commitment_txid: None, prev_counterparty_commitment_txid: None, @@ -1233,17 +1262,17 @@ impl ChannelMonitor { on_holder_tx_csv: counterparty_channel_parameters.selected_contest_delay, commitment_secrets: CounterpartyCommitmentSecrets::new(), - counterparty_claimable_outpoints: HashMap::new(), - counterparty_commitment_txn_on_chain: HashMap::new(), - counterparty_hash_commitment_number: HashMap::new(), - counterparty_fulfilled_htlcs: HashMap::new(), + counterparty_claimable_outpoints: new_hash_map(), + counterparty_commitment_txn_on_chain: new_hash_map(), + counterparty_hash_commitment_number: new_hash_map(), + counterparty_fulfilled_htlcs: new_hash_map(), prev_holder_signed_commitment_tx: None, current_holder_commitment_tx: holder_commitment_tx, current_counterparty_commitment_number: 1 << 48, current_holder_commitment_number, - payment_preimages: HashMap::new(), + payment_preimages: new_hash_map(), pending_monitor_events: Vec::new(), pending_events: Vec::new(), is_processing_pending_events: false, @@ -1373,6 +1402,11 @@ impl ChannelMonitor { self.inner.lock().unwrap().get_funding_txo().clone() } + /// Gets the channel_id of the channel this ChannelMonitor is monitoring for. + pub fn channel_id(&self) -> ChannelId { + self.inner.lock().unwrap().channel_id() + } + /// Gets a list of txids, with their output scripts (in the order they appear in the /// transaction), which we must learn about spends of via block_connected(). pub fn get_outputs_to_watch(&self) -> Vec<(Txid, Vec<(u32, ScriptBuf)>)> { @@ -1383,15 +1417,22 @@ impl ChannelMonitor { /// Loads the funding txo and outputs to watch into the given `chain::Filter` by repeatedly /// calling `chain::Filter::register_output` and `chain::Filter::register_tx` until all outputs /// have been registered. - pub fn load_outputs_to_watch(&self, filter: &F) where F::Target: chain::Filter { + pub fn load_outputs_to_watch(&self, filter: &F, logger: &L) + where + F::Target: chain::Filter, L::Target: Logger, + { let lock = self.inner.lock().unwrap(); + let logger = WithChannelMonitor::from_impl(logger, &*lock); + log_trace!(&logger, "Registering funding outpoint {}", &lock.get_funding_txo().0); filter.register_tx(&lock.get_funding_txo().0.txid, &lock.get_funding_txo().1); for (txid, outputs) in lock.get_outputs_to_watch().iter() { for (index, script_pubkey) in outputs.iter() { assert!(*index <= u16::max_value() as u32); + let outpoint = OutPoint { txid: *txid, index: *index as u16 }; + log_trace!(logger, "Registering outpoint {} with the filter for monitoring spends", outpoint); filter.register_output(WatchedOutput { block_hash: None, - outpoint: OutPoint { txid: *txid, index: *index as u16 }, + outpoint, script_pubkey: script_pubkey.clone(), }); } @@ -1522,28 +1563,30 @@ impl ChannelMonitor { self.inner.lock().unwrap().counterparty_node_id } - /// 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 + /// You may use this to broadcast the latest local commitment transaction, either because /// 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 + /// Broadcasting these transactions in this manner 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. - /// - /// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager - pub fn get_latest_holder_commitment_txn(&self, logger: &L) -> Vec - where L::Target: Logger { + pub fn broadcast_latest_holder_commitment_txn( + &self, broadcaster: &B, fee_estimator: &F, logger: &L + ) + where + B::Target: BroadcasterInterface, + F::Target: FeeEstimator, + L::Target: Logger + { let mut inner = self.inner.lock().unwrap(); + let fee_estimator = LowerBoundedFeeEstimator::new(&**fee_estimator); let logger = WithChannelMonitor::from_impl(logger, &*inner); - inner.get_latest_holder_commitment_txn(&logger) + inner.queue_latest_holder_commitment_txn_for_broadcast(broadcaster, &fee_estimator, &logger); } - /// Unsafe test-only version of get_latest_holder_commitment_txn used by our test framework + /// Unsafe test-only version of `broadcast_latest_holder_commitment_txn` used by our test framework /// to bypass HolderCommitmentTransaction state update lockdown after signature and generate /// revoked commitment transaction. #[cfg(any(test, feature = "unsafe_revoked_tx_signing"))] @@ -1723,7 +1766,26 @@ impl ChannelMonitor { let logger = WithChannelMonitor::from_impl(logger, &*inner); let current_height = inner.best_block.height; inner.onchain_tx_handler.rebroadcast_pending_claims( - current_height, &broadcaster, &fee_estimator, &logger, + current_height, FeerateStrategy::HighestOfPreviousOrNew, &broadcaster, &fee_estimator, &logger, + ); + } + + /// Triggers rebroadcasts of pending claims from a force-closed channel after a transaction + /// signature generation failure. + pub fn signer_unblocked( + &self, broadcaster: B, fee_estimator: F, logger: &L, + ) + where + B::Target: BroadcasterInterface, + F::Target: FeeEstimator, + L::Target: Logger, + { + let fee_estimator = LowerBoundedFeeEstimator::new(fee_estimator); + let mut inner = self.inner.lock().unwrap(); + let logger = WithChannelMonitor::from_impl(logger, &*inner); + let current_height = inner.best_block.height; + inner.onchain_tx_handler.rebroadcast_pending_claims( + current_height, FeerateStrategy::RetryPrevious, &broadcaster, &fee_estimator, &logger, ); } @@ -1769,6 +1831,12 @@ impl ChannelMonitor { pub fn set_counterparty_payment_script(&self, script: ScriptBuf) { self.inner.lock().unwrap().counterparty_payment_script = script; } + + #[cfg(test)] + pub fn do_signer_call ()>(&self, mut f: F) { + let inner = self.inner.lock().unwrap(); + f(&inner.onchain_tx_handler.signer); + } } impl ChannelMonitorImpl { @@ -2134,7 +2202,7 @@ impl ChannelMonitor { /// HTLCs which were resolved on-chain (i.e. where the final HTLC resolution was done by an /// event from this `ChannelMonitor`). pub(crate) fn get_all_current_outbound_htlcs(&self) -> HashMap)> { - let mut res = HashMap::new(); + let mut res = new_hash_map(); // Just examine the available counterparty commitment transactions. See docs on // `fail_unbroadcast_htlcs`, below, for justification. let us = self.inner.lock().unwrap(); @@ -2186,7 +2254,7 @@ impl ChannelMonitor { return self.get_all_current_outbound_htlcs(); } - let mut res = HashMap::new(); + let mut res = new_hash_map(); macro_rules! walk_htlcs { ($holder_commitment: expr, $htlc_iter: expr) => { for (htlc, source) in $htlc_iter { @@ -2206,7 +2274,7 @@ impl ChannelMonitor { // before considering it "no longer pending" - this matches when we // provide the ChannelManager an HTLC failure event. Some(commitment_tx_output_idx) == htlc.transaction_output_index && - us.best_block.height() >= event.height + ANTI_REORG_DELAY - 1 + us.best_block.height >= event.height + ANTI_REORG_DELAY - 1 } else if let OnchainEvent::HTLCSpendConfirmation { commitment_tx_output_idx, .. } = event.event { // If the HTLC was fulfilled with a preimage, we consider the HTLC // immediately non-pending, matching when we provide ChannelManager @@ -2285,7 +2353,7 @@ macro_rules! fail_unbroadcast_htlcs { // broadcastable commitment transaction has the HTLC in it, but it // cannot currently change after channel initialization, so we don't // need to here. - let confirmed_htlcs_iter: &mut Iterator)> = &mut $confirmed_htlcs_list; + let confirmed_htlcs_iter: &mut dyn Iterator)> = &mut $confirmed_htlcs_list; let mut matched_htlc = false; for (ref broadcast_htlc, ref broadcast_source) in confirmed_htlcs_iter { @@ -2608,7 +2676,7 @@ impl ChannelMonitorImpl { macro_rules! claim_htlcs { ($commitment_number: expr, $txid: expr) => { let (htlc_claim_reqs, _) = self.get_counterparty_output_claim_info($commitment_number, $txid, None); - self.onchain_tx_handler.update_claims_view_from_requests(htlc_claim_reqs, self.best_block.height(), self.best_block.height(), broadcaster, fee_estimator, logger); + self.onchain_tx_handler.update_claims_view_from_requests(htlc_claim_reqs, self.best_block.height, self.best_block.height, broadcaster, fee_estimator, logger); } } if let Some(txid) = self.current_counterparty_commitment_txid { @@ -2655,24 +2723,65 @@ impl ChannelMonitorImpl { // Assume that the broadcasted commitment transaction confirmed in the current best // block. Even if not, its a reasonable metric for the bump criteria on the HTLC // transactions. - let (claim_reqs, _) = self.get_broadcasted_holder_claims(&holder_commitment_tx, self.best_block.height()); - self.onchain_tx_handler.update_claims_view_from_requests(claim_reqs, self.best_block.height(), self.best_block.height(), broadcaster, fee_estimator, logger); + let (claim_reqs, _) = self.get_broadcasted_holder_claims(&holder_commitment_tx, self.best_block.height); + self.onchain_tx_handler.update_claims_view_from_requests(claim_reqs, self.best_block.height, self.best_block.height, broadcaster, fee_estimator, logger); } } } - fn broadcast_latest_holder_commitment_txn(&mut self, broadcaster: &B, logger: &WithChannelMonitor) - where B::Target: BroadcasterInterface, - L::Target: Logger, - { - let commit_txs = self.get_latest_holder_commitment_txn(logger); - let mut txs = vec![]; - for tx in commit_txs.iter() { - log_info!(logger, "Broadcasting local {}", log_tx!(tx)); - txs.push(tx); - } - broadcaster.broadcast_transactions(&txs); + fn generate_claimable_outpoints_and_watch_outputs(&mut self) -> (Vec, Vec) { + 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 + ); + let mut claimable_outpoints = vec![commitment_package]; 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; + let mut watch_outputs = Vec::new(); + // We can't broadcast our HTLC transactions while the commitment transaction is + // unconfirmed. We'll delay doing so until we detect the confirmed commitment in + // `transactions_confirmed`. + if !self.onchain_tx_handler.channel_type_features().supports_anchors_zero_fee_htlc_tx() { + // Because we're broadcasting a commitment transaction, we should construct the package + // assuming it gets confirmed in the next block. Sadly, we have code which considers + // "not yet confirmed" things as discardable, so we cannot do that here. + let (mut new_outpoints, _) = self.get_broadcasted_holder_claims( + &self.current_holder_commitment_tx, self.best_block.height + ); + 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)); + } + claimable_outpoints.append(&mut new_outpoints); + } + (claimable_outpoints, watch_outputs) + } + + pub(crate) fn queue_latest_holder_commitment_txn_for_broadcast( + &mut self, broadcaster: &B, fee_estimator: &LowerBoundedFeeEstimator, logger: &WithChannelMonitor + ) + where + B::Target: BroadcasterInterface, + F::Target: FeeEstimator, + L::Target: Logger, + { + let (claimable_outpoints, _) = self.generate_claimable_outpoints_and_watch_outputs(); + self.onchain_tx_handler.update_claims_view_from_requests( + claimable_outpoints, self.best_block.height, self.best_block.height, broadcaster, + fee_estimator, logger + ); } fn update_monitor( @@ -2692,6 +2801,15 @@ impl ChannelMonitorImpl { log_info!(logger, "Applying update to monitor {}, bringing update_id from {} to {} with {} change(s).", log_funding_info!(self), self.latest_update_id, updates.update_id, updates.updates.len()); } + + if updates.counterparty_node_id.is_some() { + if self.counterparty_node_id.is_none() { + self.counterparty_node_id = updates.counterparty_node_id; + } else { + debug_assert_eq!(self.counterparty_node_id, updates.counterparty_node_id); + } + } + // ChannelMonitor updates may be applied after force close if we receive a preimage for a // broadcasted commitment transaction HTLC output that we'd like to claim on-chain. If this // is the case, we no longer have guaranteed access to the monitor's update ID, so we use a @@ -2762,30 +2880,11 @@ impl ChannelMonitorImpl { log_trace!(logger, "Avoiding commitment broadcast, already detected confirmed spend onchain"); continue; } - self.broadcast_latest_holder_commitment_txn(broadcaster, logger); - // If the channel supports anchor outputs, we'll need to emit an external - // event to be consumed such that a child transaction is broadcast with a - // high enough feerate for the parent commitment transaction to confirm. - if self.onchain_tx_handler.channel_type_features().supports_anchors_zero_fee_htlc_tx() { - let funding_output = HolderFundingOutput::build( - self.funding_redeemscript.clone(), self.channel_value_satoshis, - self.onchain_tx_handler.channel_type_features().clone(), - ); - let best_block_height = self.best_block.height(); - let commitment_package = PackageTemplate::build_package( - self.funding_info.0.txid.clone(), self.funding_info.0.index as u32, - PackageSolvingData::HolderFundingOutput(funding_output), - best_block_height, best_block_height - ); - self.onchain_tx_handler.update_claims_view_from_requests( - vec![commitment_package], best_block_height, best_block_height, - broadcaster, &bounded_fee_estimator, logger, - ); - } + self.queue_latest_holder_commitment_txn_for_broadcast(broadcaster, &bounded_fee_estimator, logger); } 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 {}!", &self.funding_info.0.to_channel_id()); - log_error!(logger, " Read the docs for ChannelMonitor::get_latest_holder_commitment_txn and take manual action!"); + log_error!(logger, " in channel monitor for channel {}!", &self.channel_id()); + log_error!(logger, " Read the docs for ChannelMonitor::broadcast_latest_holder_commitment_txn to take manual action!"); } else { // If we generated a MonitorEvent::HolderForceClosed, the ChannelManager // will still give us a ChannelForceClosed event with !should_broadcast, but we @@ -2830,6 +2929,10 @@ impl ChannelMonitorImpl { &self.funding_info } + pub fn channel_id(&self) -> ChannelId { + self.channel_id + } + fn get_outputs_to_watch(&self) -> &HashMap> { // If we've detected a counterparty commitment tx on chain, we must include it in the set // of outputs to watch for spends of, otherwise we're likely to lose user funds. Because @@ -2857,12 +2960,19 @@ impl ChannelMonitorImpl { ClaimEvent::BumpCommitment { package_target_feerate_sat_per_1000_weight, commitment_tx, anchor_output_idx, } => { + let channel_id = self.channel_id; + // unwrap safety: `ClaimEvent`s are only available for Anchor channels, + // introduced with v0.0.116. counterparty_node_id is guaranteed to be `Some` + // since v0.0.110. + let counterparty_node_id = self.counterparty_node_id.unwrap(); let commitment_txid = commitment_tx.txid(); debug_assert_eq!(self.current_holder_commitment_tx.txid, commitment_txid); let pending_htlcs = self.current_holder_commitment_tx.non_dust_htlcs(); let commitment_tx_fee_satoshis = self.channel_value_satoshis - commitment_tx.output.iter().fold(0u64, |sum, output| sum + output.value); ret.push(Event::BumpTransaction(BumpTransactionEvent::ChannelClose { + channel_id, + counterparty_node_id, claim_id, package_target_feerate_sat_per_1000_weight, commitment_tx, @@ -2884,6 +2994,11 @@ impl ChannelMonitorImpl { ClaimEvent::BumpHTLC { target_feerate_sat_per_1000_weight, htlcs, tx_lock_time, } => { + let channel_id = self.channel_id; + // unwrap safety: `ClaimEvent`s are only available for Anchor channels, + // introduced with v0.0.116. counterparty_node_id is guaranteed to be `Some` + // since v0.0.110. + let counterparty_node_id = self.counterparty_node_id.unwrap(); let mut htlc_descriptors = Vec::with_capacity(htlcs.len()); for htlc in htlcs { htlc_descriptors.push(HTLCDescriptor { @@ -2904,6 +3019,8 @@ impl ChannelMonitorImpl { }); } ret.push(Event::BumpTransaction(BumpTransactionEvent::HTLCResolution { + channel_id, + counterparty_node_id, claim_id, target_feerate_sat_per_1000_weight, htlc_descriptors, @@ -3098,7 +3215,11 @@ impl ChannelMonitorImpl { (htlc, htlc_source.as_ref().map(|htlc_source| htlc_source.as_ref())) ), logger); } else { - debug_assert!(false, "We should have per-commitment option for any recognized old commitment txn"); + // Our fuzzers aren't constrained by pesky things like valid signatures, so can + // spend our funding output with a transaction which doesn't match our past + // commitment transactions. Thus, we can only debug-assert here when not + // fuzzing. + debug_assert!(cfg!(fuzzing), "We should have per-commitment option for any recognized old commitment txn"); fail_unbroadcast_htlcs!(self, "revoked counterparty", commitment_txid, tx, height, block_hash, [].iter().map(|reference| *reference), logger); } @@ -3358,43 +3479,56 @@ impl ChannelMonitorImpl { } } - fn get_latest_holder_commitment_txn( - &mut self, logger: &WithChannelMonitor, - ) -> Vec where L::Target: Logger { - log_debug!(logger, "Getting signed latest holder commitment transaction!"); - self.holder_tx_signed = true; - let commitment_tx = self.onchain_tx_handler.get_fully_signed_holder_tx(&self.funding_redeemscript); - let txid = commitment_tx.txid(); - let mut holder_transactions = vec![commitment_tx]; - // When anchor outputs are present, the HTLC transactions are only valid once the commitment - // transaction confirms. - if self.onchain_tx_handler.channel_type_features().supports_anchors_zero_fee_htlc_tx() { - return holder_transactions; + /// Cancels any existing pending claims for a commitment that previously confirmed and has now + /// been replaced by another. + pub fn cancel_prev_commitment_claims( + &mut self, logger: &L, confirmed_commitment_txid: &Txid + ) where L::Target: Logger { + for (counterparty_commitment_txid, _) in &self.counterparty_commitment_txn_on_chain { + // Cancel any pending claims for counterparty commitments we've seen confirm. + if counterparty_commitment_txid == confirmed_commitment_txid { + continue; + } + for (htlc, _) in self.counterparty_claimable_outpoints.get(counterparty_commitment_txid).unwrap_or(&vec![]) { + log_trace!(logger, "Canceling claims for previously confirmed counterparty commitment {}", + counterparty_commitment_txid); + let mut outpoint = BitcoinOutPoint { txid: *counterparty_commitment_txid, vout: 0 }; + if let Some(vout) = htlc.transaction_output_index { + outpoint.vout = vout; + self.onchain_tx_handler.abandon_claim(&outpoint); + } + } } - for htlc in self.current_holder_commitment_tx.htlc_outputs.iter() { - if let Some(vout) = htlc.0.transaction_output_index { - let preimage = if !htlc.0.offered { - if let Some(preimage) = self.payment_preimages.get(&htlc.0.payment_hash) { Some(preimage.clone()) } else { - // We can't build an HTLC-Success transaction without the preimage - continue; + if self.holder_tx_signed { + // If we've signed, we may have broadcast either commitment (prev or current), and + // attempted to claim from it immediately without waiting for a confirmation. + if self.current_holder_commitment_tx.txid != *confirmed_commitment_txid { + log_trace!(logger, "Canceling claims for previously broadcast holder commitment {}", + self.current_holder_commitment_tx.txid); + let mut outpoint = BitcoinOutPoint { txid: self.current_holder_commitment_tx.txid, vout: 0 }; + for (htlc, _, _) in &self.current_holder_commitment_tx.htlc_outputs { + if let Some(vout) = htlc.transaction_output_index { + outpoint.vout = vout; + self.onchain_tx_handler.abandon_claim(&outpoint); + } + } + } + if let Some(prev_holder_commitment_tx) = &self.prev_holder_signed_commitment_tx { + if prev_holder_commitment_tx.txid != *confirmed_commitment_txid { + log_trace!(logger, "Canceling claims for previously broadcast holder commitment {}", + prev_holder_commitment_tx.txid); + let mut outpoint = BitcoinOutPoint { txid: prev_holder_commitment_tx.txid, vout: 0 }; + for (htlc, _, _) in &prev_holder_commitment_tx.htlc_outputs { + if let Some(vout) = htlc.transaction_output_index { + outpoint.vout = vout; + self.onchain_tx_handler.abandon_claim(&outpoint); + } } - } else if htlc.0.cltv_expiry > self.best_block.height() + 1 { - // Don't broadcast HTLC-Timeout transactions immediately as they don't meet the - // current locktime requirements on-chain. We will broadcast them in - // `block_confirmed` when `should_broadcast_holder_commitment_txn` returns true. - // Note that we add + 1 as transactions are broadcastable when they can be - // confirmed in the next block. - continue; - } else { None }; - if let Some(htlc_tx) = self.onchain_tx_handler.get_fully_signed_htlc_tx( - &::bitcoin::OutPoint { txid, vout }, &preimage) { - holder_transactions.push(htlc_tx); } } + } else { + // No previous claim. } - // We throw away the generated waiting_first_conf data as we aren't (yet) confirmed and we don't actually know what the caller wants to do. - // The data will be re-generated and tracked in check_spend_holder_transaction if we get a confirmation. - holder_transactions } #[cfg(any(test,feature = "unsafe_revoked_tx_signing"))] @@ -3419,9 +3553,12 @@ impl ChannelMonitorImpl { continue; } } else { None }; - if let Some(htlc_tx) = self.onchain_tx_handler.get_fully_signed_htlc_tx( - &::bitcoin::OutPoint { txid, vout }, &preimage) { - holder_transactions.push(htlc_tx); + if let Some(htlc_tx) = self.onchain_tx_handler.get_maybe_signed_htlc_tx( + &::bitcoin::OutPoint { txid, vout }, &preimage + ) { + if htlc_tx.is_fully_signed() { + holder_transactions.push(htlc_tx.0); + } } } } @@ -3458,11 +3595,13 @@ impl ChannelMonitorImpl { { let block_hash = header.block_hash(); - if height > self.best_block.height() { + if height > self.best_block.height { self.best_block = BestBlock::new(block_hash, height); + log_trace!(logger, "Connecting new block {} at height {}", block_hash, height); self.block_confirmed(height, block_hash, vec![], vec![], vec![], &broadcaster, &fee_estimator, logger) - } else if block_hash != self.best_block.block_hash() { + } else if block_hash != self.best_block.block_hash { self.best_block = BestBlock::new(block_hash, height); + log_trace!(logger, "Best block re-orged, replaced with new block {} at height {}", block_hash, height); self.onchain_events_awaiting_threshold_conf.retain(|ref entry| entry.height <= height); self.onchain_tx_handler.block_disconnected(height + 1, broadcaster, fee_estimator, logger); Vec::new() @@ -3499,6 +3638,7 @@ impl ChannelMonitorImpl { let mut claimable_outpoints = Vec::new(); 'tx_iter: for tx in &txn_matched { let txid = tx.txid(); + log_trace!(logger, "Transaction {} confirmed in block {}", txid , block_hash); // If a transaction has already been confirmed, ensure we don't bother processing it duplicatively. if Some(txid) == self.funding_spend_confirmed { log_debug!(logger, "Skipping redundant processing of funding-spend tx {} as it was previously confirmed", txid); @@ -3537,7 +3677,7 @@ impl ChannelMonitorImpl { 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 {}.", - &self.funding_info.0.to_channel_id(), txid); + &self.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.to_consensus_u32() >> 8*3) as u8 == 0x20 { @@ -3550,6 +3690,7 @@ impl ChannelMonitorImpl { claimable_outpoints.append(&mut new_outpoints); if new_outpoints.is_empty() { if let Some((mut new_outpoints, new_outputs)) = self.check_spend_holder_transaction(&tx, height, &block_hash, &logger) { + #[cfg(not(fuzzing))] debug_assert!(commitment_tx_to_counterparty_output.is_none(), "A commitment transaction matched as both a counterparty and local commitment tx?"); if !new_outputs.1.is_empty() { @@ -3570,6 +3711,10 @@ impl ChannelMonitorImpl { commitment_tx_to_counterparty_output, }, }); + // Now that we've detected a confirmed commitment transaction, attempt to cancel + // pending claims for any commitments that were previously confirmed such that + // we don't continue claiming inputs that no longer exist. + self.cancel_prev_commitment_claims(&logger, &txid); } } if tx.input.len() >= 1 { @@ -3599,7 +3744,7 @@ impl ChannelMonitorImpl { } } - if height > self.best_block.height() { + if height > self.best_block.height { self.best_block = BestBlock::new(block_hash, height); } @@ -3631,33 +3776,13 @@ impl ChannelMonitorImpl { L::Target: Logger, { log_trace!(logger, "Processing {} matched transactions for block at height {}.", txn_matched.len(), conf_height); - debug_assert!(self.best_block.height() >= conf_height); + debug_assert!(self.best_block.height >= conf_height); let should_broadcast = self.should_broadcast_holder_commitment_txn(logger); if should_broadcast { - 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::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 - // `transactions_confirmed`. - if !self.onchain_tx_handler.channel_type_features().supports_anchors_zero_fee_htlc_tx() { - // Because we're broadcasting a commitment transaction, we should construct the package - // assuming it gets confirmed in the next block. Sadly, we have code which considers - // "not yet confirmed" things as discardable, so we cannot do that here. - let (mut new_outpoints, _) = self.get_broadcasted_holder_claims(&self.current_holder_commitment_tx, self.best_block.height()); - 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)); - } - claimable_outpoints.append(&mut new_outpoints); - } + let (mut new_outpoints, mut new_outputs) = self.generate_claimable_outpoints_and_watch_outputs(); + claimable_outpoints.append(&mut new_outpoints); + watch_outputs.append(&mut new_outputs); } // Find which on-chain events have reached their confirmation threshold. @@ -3723,7 +3848,7 @@ impl ChannelMonitorImpl { log_debug!(logger, "Descriptor {} has got enough confirmations to be passed upstream", log_spendable!(descriptor)); self.pending_events.push(Event::SpendableOutputs { outputs: vec![descriptor], - channel_id: Some(self.funding_info.0.to_channel_id()), + channel_id: Some(self.channel_id()), }); self.spendable_txids_confirmed.push(entry.txid); }, @@ -3742,8 +3867,8 @@ impl ChannelMonitorImpl { } } - self.onchain_tx_handler.update_claims_view_from_requests(claimable_outpoints, conf_height, self.best_block.height(), broadcaster, fee_estimator, logger); - self.onchain_tx_handler.update_claims_view_from_matched_txn(&txn_matched, conf_height, conf_hash, self.best_block.height(), broadcaster, fee_estimator, logger); + self.onchain_tx_handler.update_claims_view_from_requests(claimable_outpoints, conf_height, self.best_block.height, broadcaster, fee_estimator, logger); + self.onchain_tx_handler.update_claims_view_from_matched_txn(&txn_matched, conf_height, conf_hash, self.best_block.height, broadcaster, fee_estimator, logger); // Determine new outputs to watch by comparing against previously known outputs to watch, // updating the latter in the process. @@ -3822,7 +3947,7 @@ impl ChannelMonitorImpl { /// Filters a block's `txdata` for transactions spending watched outputs or for any child /// transactions thereof. fn filter_block<'a>(&self, txdata: &TransactionData<'a>) -> Vec<&'a Transaction> { - let mut matched_txn = HashSet::new(); + let mut matched_txn = new_hash_set(); txdata.iter().filter(|&&(_, tx)| { let mut matches = self.spends_watched_output(tx); for input in tx.input.iter() { @@ -3894,7 +4019,7 @@ impl ChannelMonitorImpl { // to the source, and if we don't fail the channel we will have to ensure that the next // updates that peer sends us are update_fails, failing the channel if not. It's probably // easier to just fail the channel as this case should be rare enough anyway. - let height = self.best_block.height(); + let height = self.best_block.height; macro_rules! scan_commitment { ($htlcs: expr, $holder_tx: expr) => { for ref htlc in $htlcs { @@ -4337,7 +4462,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP } let counterparty_claimable_outpoints_len: u64 = Readable::read(reader)?; - let mut counterparty_claimable_outpoints = HashMap::with_capacity(cmp::min(counterparty_claimable_outpoints_len as usize, MAX_ALLOC_SIZE / 64)); + let mut counterparty_claimable_outpoints = hash_map_with_capacity(cmp::min(counterparty_claimable_outpoints_len as usize, MAX_ALLOC_SIZE / 64)); for _ in 0..counterparty_claimable_outpoints_len { let txid: Txid = Readable::read(reader)?; let htlcs_count: u64 = Readable::read(reader)?; @@ -4351,7 +4476,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP } let counterparty_commitment_txn_on_chain_len: u64 = Readable::read(reader)?; - let mut counterparty_commitment_txn_on_chain = HashMap::with_capacity(cmp::min(counterparty_commitment_txn_on_chain_len as usize, MAX_ALLOC_SIZE / 32)); + let mut counterparty_commitment_txn_on_chain = hash_map_with_capacity(cmp::min(counterparty_commitment_txn_on_chain_len as usize, MAX_ALLOC_SIZE / 32)); for _ in 0..counterparty_commitment_txn_on_chain_len { let txid: Txid = Readable::read(reader)?; let commitment_number = ::read(reader)?.0; @@ -4361,7 +4486,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP } let counterparty_hash_commitment_number_len: u64 = Readable::read(reader)?; - let mut counterparty_hash_commitment_number = HashMap::with_capacity(cmp::min(counterparty_hash_commitment_number_len as usize, MAX_ALLOC_SIZE / 32)); + let mut counterparty_hash_commitment_number = hash_map_with_capacity(cmp::min(counterparty_hash_commitment_number_len as usize, MAX_ALLOC_SIZE / 32)); for _ in 0..counterparty_hash_commitment_number_len { let payment_hash: PaymentHash = Readable::read(reader)?; let commitment_number = ::read(reader)?.0; @@ -4384,7 +4509,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP let current_holder_commitment_number = ::read(reader)?.0; let payment_preimages_len: u64 = Readable::read(reader)?; - let mut payment_preimages = HashMap::with_capacity(cmp::min(payment_preimages_len as usize, MAX_ALLOC_SIZE / 32)); + let mut payment_preimages = hash_map_with_capacity(cmp::min(payment_preimages_len as usize, MAX_ALLOC_SIZE / 32)); for _ in 0..payment_preimages_len { let preimage: PaymentPreimage = Readable::read(reader)?; let hash = PaymentHash(Sha256::hash(&preimage.0[..]).to_byte_array()); @@ -4424,7 +4549,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP } let outputs_to_watch_len: u64 = Readable::read(reader)?; - let mut outputs_to_watch = HashMap::with_capacity(cmp::min(outputs_to_watch_len as usize, MAX_ALLOC_SIZE / (mem::size_of::() + mem::size_of::() + mem::size_of::>()))); + let mut outputs_to_watch = hash_map_with_capacity(cmp::min(outputs_to_watch_len as usize, MAX_ALLOC_SIZE / (mem::size_of::() + mem::size_of::() + mem::size_of::>()))); for _ in 0..outputs_to_watch_len { let txid = Readable::read(reader)?; let outputs_len: u64 = Readable::read(reader)?; @@ -4466,8 +4591,9 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP let mut counterparty_node_id = None; 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 counterparty_fulfilled_htlcs = Some(new_hash_map()); let mut initial_counterparty_commitment_info = None; + let mut channel_id = None; read_tlv_fields!(reader, { (1, funding_spend_confirmed, option), (3, htlcs_resolved_on_chain, optional_vec), @@ -4478,6 +4604,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP (13, spendable_txids_confirmed, optional_vec), (15, counterparty_fulfilled_htlcs, option), (17, initial_counterparty_commitment_info, option), + (19, channel_id, option), }); // Monitors for anchor outputs channels opened in v0.0.116 suffered from a bug in which the @@ -4491,7 +4618,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP chan_utils::get_to_countersignatory_with_anchors_redeemscript(&payment_point).to_v0_p2wsh(); } - Ok((best_block.block_hash(), ChannelMonitor::from_impl(ChannelMonitorImpl { + Ok((best_block.block_hash, ChannelMonitor::from_impl(ChannelMonitorImpl { latest_update_id, commitment_transaction_number_obscure_factor, @@ -4502,6 +4629,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP channel_keys_id, holder_revocation_basepoint, + channel_id: channel_id.unwrap_or(ChannelId::v1_from_funding_outpoint(outpoint)), funding_info, current_counterparty_commitment_txid, prev_counterparty_commitment_txid, @@ -4576,7 +4704,7 @@ mod tests { 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::ln::{PaymentPreimage, PaymentHash}; + use crate::ln::{PaymentPreimage, PaymentHash, ChannelId}; use crate::ln::channel_keys::{DelayedPaymentBasepoint, DelayedPaymentKey, HtlcBasepoint, RevocationBasepoint, RevocationKey}; use crate::ln::chan_utils::{self,HTLCOutputInCommitment, ChannelPublicKeys, ChannelTransactionParameters, HolderCommitmentTransaction, CounterpartyChannelTransactionParameters}; use crate::ln::channelmanager::{PaymentSendFailure, PaymentId, RecipientOnionFields}; @@ -4719,7 +4847,7 @@ mod tests { preimages_slice_to_htlcs!($preimages_slice).into_iter().map(|(htlc, _)| (htlc, None)).collect() } } - let dummy_sig = crate::util::crypto::sign(&secp_ctx, + let dummy_sig = crate::crypto::utils::sign(&secp_ctx, &bitcoin::secp256k1::Message::from_slice(&[42; 32]).unwrap(), &SecretKey::from_slice(&[42; 32]).unwrap()); @@ -4752,6 +4880,7 @@ mod tests { htlc_basepoint: HtlcBasepoint::from(PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[48; 32]).unwrap())) }; let funding_outpoint = OutPoint { txid: Txid::all_zeros(), index: u16::max_value() }; + let channel_id = ChannelId::v1_from_funding_outpoint(funding_outpoint); let channel_parameters = ChannelTransactionParameters { holder_pubkeys: keys.holder_channel_pubkeys.clone(), holder_selected_contest_delay: 66, @@ -4771,7 +4900,7 @@ mod tests { Some(ShutdownScript::new_p2wpkh_from_pubkey(shutdown_pubkey).into_inner()), 0, &ScriptBuf::new(), (OutPoint { txid: Txid::from_slice(&[43; 32]).unwrap(), index: 0 }, ScriptBuf::new()), &channel_parameters, ScriptBuf::new(), 46, 0, HolderCommitmentTransaction::dummy(&mut Vec::new()), - best_block, dummy_key); + best_block, dummy_key, channel_id); let mut htlcs = preimages_slice_to_htlcs!(preimages[0..10]); let dummy_commitment_tx = HolderCommitmentTransaction::dummy(&mut htlcs); @@ -5001,6 +5130,7 @@ mod tests { htlc_basepoint: HtlcBasepoint::from(PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[48; 32]).unwrap())), }; let funding_outpoint = OutPoint { txid: Txid::all_zeros(), index: u16::max_value() }; + let channel_id = ChannelId::v1_from_funding_outpoint(funding_outpoint); let channel_parameters = ChannelTransactionParameters { holder_pubkeys: keys.holder_channel_pubkeys.clone(), holder_selected_contest_delay: 66, @@ -5018,9 +5148,9 @@ mod tests { Some(ShutdownScript::new_p2wpkh_from_pubkey(shutdown_pubkey).into_inner()), 0, &ScriptBuf::new(), (OutPoint { txid: Txid::from_slice(&[43; 32]).unwrap(), index: 0 }, ScriptBuf::new()), &channel_parameters, ScriptBuf::new(), 46, 0, HolderCommitmentTransaction::dummy(&mut Vec::new()), - best_block, dummy_key); + best_block, dummy_key, channel_id); - let chan_id = monitor.inner.lock().unwrap().funding_info.0.to_channel_id().clone(); + let chan_id = monitor.inner.lock().unwrap().channel_id(); let context_logger = WithChannelMonitor::from(&logger, &monitor); log_error!(context_logger, "This is an error"); log_warn!(context_logger, "This is an error"); diff --git a/lightning/src/chain/mod.rs b/lightning/src/chain/mod.rs index 2a9583f2edf..356520b5cba 100644 --- a/lightning/src/chain/mod.rs +++ b/lightning/src/chain/mod.rs @@ -17,6 +17,7 @@ use bitcoin::network::constants::Network; use bitcoin::secp256k1::PublicKey; use crate::chain::channelmonitor::{ChannelMonitor, ChannelMonitorUpdate, MonitorEvent}; +use crate::ln::ChannelId; use crate::sign::ecdsa::WriteableEcdsaChannelSigner; use crate::chain::transaction::{OutPoint, TransactionData}; @@ -30,10 +31,12 @@ pub(crate) mod onchaintx; pub(crate) mod package; /// The best known block as identified by its hash and height. -#[derive(Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] pub struct BestBlock { - block_hash: BlockHash, - height: u32, + /// The block's hash + pub block_hash: BlockHash, + /// The height at which the block was confirmed. + pub height: u32, } impl BestBlock { @@ -50,12 +53,6 @@ impl BestBlock { pub fn new(block_hash: BlockHash, height: u32) -> Self { BestBlock { block_hash, height } } - - /// Returns the best block hash. - pub fn block_hash(&self) -> BlockHash { self.block_hash } - - /// Returns the best block height. - pub fn height(&self) -> u32 { self.height } } @@ -297,7 +294,7 @@ pub trait Watch { /// /// For details on asynchronous [`ChannelMonitor`] updating and returning /// [`MonitorEvent::Completed`] here, see [`ChannelMonitorUpdateStatus::InProgress`]. - fn release_pending_monitor_events(&self) -> Vec<(OutPoint, Vec, Option)>; + fn release_pending_monitor_events(&self) -> Vec<(OutPoint, ChannelId, Vec, Option)>; } /// The `Filter` trait defines behavior for indicating chain activity of interest pertaining to @@ -357,7 +354,7 @@ pub struct WatchedOutput { pub script_pubkey: ScriptBuf, } -impl Listen for core::ops::Deref { +impl Listen for dyn core::ops::Deref { fn filtered_block_connected(&self, header: &Header, txdata: &TransactionData, height: u32) { (**self).filtered_block_connected(header, txdata, height); } diff --git a/lightning/src/chain/onchaintx.rs b/lightning/src/chain/onchaintx.rs index bbed782bb57..94d6aa35746 100644 --- a/lightning/src/chain/onchaintx.rs +++ b/lightning/src/chain/onchaintx.rs @@ -31,6 +31,7 @@ use crate::chain::ClaimId; use crate::chain::chaininterface::{ConfirmationTarget, FeeEstimator, BroadcasterInterface, LowerBoundedFeeEstimator}; use crate::chain::channelmonitor::{ANTI_REORG_DELAY, CLTV_SHARED_CLAIM_BUFFER}; use crate::chain::package::{PackageSolvingData, PackageTemplate}; +use crate::chain::transaction::MaybeSignedTransaction; use crate::util::logger::Logger; use crate::util::ser::{Readable, ReadableArgs, MaybeReadable, UpgradableRequired, Writer, Writeable, VecWriter}; @@ -204,12 +205,23 @@ pub(crate) enum ClaimEvent { /// control) onchain. pub(crate) enum OnchainClaim { /// A finalized transaction pending confirmation spending the output to claim. - Tx(Transaction), + Tx(MaybeSignedTransaction), /// An event yielded externally to signal additional inputs must be added to a transaction /// pending confirmation spending the output to claim. Event(ClaimEvent), } +/// Represents the different feerate strategies a pending request can use when generating a claim. +pub(crate) enum FeerateStrategy { + /// We must reuse the most recently used feerate, if any. + RetryPrevious, + /// We must pick the highest between the most recently used and the current feerate estimate. + HighestOfPreviousOrNew, + /// We must force a bump of the most recently used feerate, either by using the current feerate + /// estimate if it's higher, or manually bumping. + ForceBump, +} + /// OnchainTxHandler receives claiming requests, aggregates them if it's sound, broadcast and /// do RBF bumping if possible. #[derive(Clone)] @@ -374,13 +386,13 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP signer.provide_channel_parameters(&channel_parameters); let pending_claim_requests_len: u64 = Readable::read(reader)?; - let mut pending_claim_requests = HashMap::with_capacity(cmp::min(pending_claim_requests_len as usize, MAX_ALLOC_SIZE / 128)); + let mut pending_claim_requests = hash_map_with_capacity(cmp::min(pending_claim_requests_len as usize, MAX_ALLOC_SIZE / 128)); for _ in 0..pending_claim_requests_len { pending_claim_requests.insert(Readable::read(reader)?, Readable::read(reader)?); } let claimable_outpoints_len: u64 = Readable::read(reader)?; - let mut claimable_outpoints = HashMap::with_capacity(cmp::min(pending_claim_requests_len as usize, MAX_ALLOC_SIZE / 128)); + let mut claimable_outpoints = hash_map_with_capacity(cmp::min(pending_claim_requests_len as usize, MAX_ALLOC_SIZE / 128)); for _ in 0..claimable_outpoints_len { let outpoint = Readable::read(reader)?; let ancestor_claim_txid = Readable::read(reader)?; @@ -445,8 +457,8 @@ impl OnchainTxHandler prev_holder_commitment: None, signer, channel_transaction_parameters: channel_parameters, - pending_claim_requests: HashMap::new(), - claimable_outpoints: HashMap::new(), + pending_claim_requests: new_hash_map(), + claimable_outpoints: new_hash_map(), locktimed_packages: BTreeMap::new(), onchain_events_awaiting_threshold_conf: Vec::new(), pending_claim_events: Vec::new(), @@ -474,8 +486,8 @@ impl OnchainTxHandler /// invoking this every 30 seconds, or lower if running in an environment with spotty /// connections, like on mobile. pub(super) fn rebroadcast_pending_claims( - &mut self, current_height: u32, broadcaster: &B, fee_estimator: &LowerBoundedFeeEstimator, - logger: &L, + &mut self, current_height: u32, feerate_strategy: FeerateStrategy, broadcaster: &B, + fee_estimator: &LowerBoundedFeeEstimator, logger: &L, ) where B::Target: BroadcasterInterface, @@ -488,7 +500,7 @@ impl OnchainTxHandler bump_requests.push((*claim_id, request.clone())); } for (claim_id, request) in bump_requests { - self.generate_claim(current_height, &request, false /* force_feerate_bump */, fee_estimator, logger) + self.generate_claim(current_height, &request, &feerate_strategy, fee_estimator, logger) .map(|(_, new_feerate, claim)| { let mut bumped_feerate = false; if let Some(mut_request) = self.pending_claim_requests.get_mut(&claim_id) { @@ -497,9 +509,13 @@ impl OnchainTxHandler } match claim { OnchainClaim::Tx(tx) => { - let log_start = if bumped_feerate { "Broadcasting RBF-bumped" } else { "Rebroadcasting" }; - log_info!(logger, "{} onchain {}", log_start, log_tx!(tx)); - broadcaster.broadcast_transactions(&[&tx]); + if tx.is_fully_signed() { + let log_start = if bumped_feerate { "Broadcasting RBF-bumped" } else { "Rebroadcasting" }; + log_info!(logger, "{} onchain {}", log_start, log_tx!(tx.0)); + broadcaster.broadcast_transactions(&[&tx.0]); + } else { + log_info!(logger, "Waiting for signature of unsigned onchain transaction {}", tx.0.txid()); + } }, OnchainClaim::Event(event) => { let log_start = if bumped_feerate { "Yielding fee-bumped" } else { "Replaying" }; @@ -528,7 +544,7 @@ impl OnchainTxHandler /// Panics if there are signing errors, because signing operations in reaction to on-chain /// events are not expected to fail, and if they do, we may lose funds. fn generate_claim( - &mut self, cur_height: u32, cached_request: &PackageTemplate, force_feerate_bump: bool, + &mut self, cur_height: u32, cached_request: &PackageTemplate, feerate_strategy: &FeerateStrategy, fee_estimator: &LowerBoundedFeeEstimator, logger: &L, ) -> Option<(u32, u64, OnchainClaim)> where F::Target: FeeEstimator, @@ -577,7 +593,7 @@ impl OnchainTxHandler if cached_request.is_malleable() { if cached_request.requires_external_funding() { let target_feerate_sat_per_1000_weight = cached_request.compute_package_feerate( - fee_estimator, ConfirmationTarget::OnChainSweep, force_feerate_bump + fee_estimator, ConfirmationTarget::OnChainSweep, feerate_strategy, ); if let Some(htlcs) = cached_request.construct_malleable_package_with_external_funding(self) { return Some(( @@ -597,15 +613,14 @@ impl OnchainTxHandler let predicted_weight = cached_request.package_weight(&self.destination_script); if let Some((output_value, new_feerate)) = cached_request.compute_package_output( predicted_weight, self.destination_script.dust_value().to_sat(), - force_feerate_bump, fee_estimator, logger, + feerate_strategy, fee_estimator, logger, ) { assert!(new_feerate != 0); - let transaction = cached_request.finalize_malleable_package( + let transaction = cached_request.maybe_finalize_malleable_package( cur_height, self, output_value, self.destination_script.clone(), logger ).unwrap(); - log_trace!(logger, "...with timer {} and feerate {}", new_timer, new_feerate); - assert!(predicted_weight >= transaction.weight().to_wu()); + assert!(predicted_weight >= transaction.0.weight().to_wu()); return Some((new_timer, new_feerate, OnchainClaim::Tx(transaction))); } } else { @@ -614,7 +629,7 @@ impl OnchainTxHandler // which require external funding. let mut inputs = cached_request.inputs(); debug_assert_eq!(inputs.len(), 1); - let tx = match cached_request.finalize_untractable_package(self, logger) { + let tx = match cached_request.maybe_finalize_untractable_package(self, logger) { Some(tx) => tx, None => return None, }; @@ -625,19 +640,19 @@ impl OnchainTxHandler // Commitment inputs with anchors support are the only untractable inputs supported // thus far that require external funding. PackageSolvingData::HolderFundingOutput(output) => { - debug_assert_eq!(tx.txid(), self.holder_commitment.trust().txid(), + debug_assert_eq!(tx.0.txid(), self.holder_commitment.trust().txid(), "Holder commitment transaction mismatch"); let conf_target = ConfirmationTarget::OnChainSweep; let package_target_feerate_sat_per_1000_weight = cached_request - .compute_package_feerate(fee_estimator, conf_target, force_feerate_bump); + .compute_package_feerate(fee_estimator, conf_target, feerate_strategy); if let Some(input_amount_sat) = output.funding_amount { - let fee_sat = input_amount_sat - tx.output.iter().map(|output| output.value).sum::(); + let fee_sat = input_amount_sat - tx.0.output.iter().map(|output| output.value).sum::(); let commitment_tx_feerate_sat_per_1000_weight = - compute_feerate_sat_per_1000_weight(fee_sat, tx.weight().to_wu()); + compute_feerate_sat_per_1000_weight(fee_sat, tx.0.weight().to_wu()); 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, + log_debug!(logger, "Pre-signed commitment {} already has feerate {} sat/kW above required {} sat/kW", + tx.0.txid(), commitment_tx_feerate_sat_per_1000_weight, package_target_feerate_sat_per_1000_weight); return Some((new_timer, 0, OnchainClaim::Tx(tx.clone()))); } @@ -645,7 +660,7 @@ impl OnchainTxHandler // 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) { + match chan_utils::get_anchor_output(&tx.0, funding_pubkey) { // An anchor output was found, so we should yield a funding event externally. Some((idx, _)) => { // TODO: Use a lower confirmation target when both our and the @@ -655,7 +670,7 @@ impl OnchainTxHandler package_target_feerate_sat_per_1000_weight as u64, OnchainClaim::Event(ClaimEvent::BumpCommitment { package_target_feerate_sat_per_1000_weight, - commitment_tx: tx.clone(), + commitment_tx: tx.0.clone(), anchor_output_idx: idx, }), )) @@ -676,6 +691,25 @@ impl OnchainTxHandler None } + pub fn abandon_claim(&mut self, outpoint: &BitcoinOutPoint) { + let claim_id = self.claimable_outpoints.get(outpoint).map(|(claim_id, _)| *claim_id) + .or_else(|| { + self.pending_claim_requests.iter() + .find(|(_, claim)| claim.outpoints().iter().any(|claim_outpoint| *claim_outpoint == outpoint)) + .map(|(claim_id, _)| *claim_id) + }); + if let Some(claim_id) = claim_id { + if let Some(claim) = self.pending_claim_requests.remove(&claim_id) { + for outpoint in claim.outpoints() { + self.claimable_outpoints.remove(outpoint); + } + } + } else { + self.locktimed_packages.values_mut().for_each(|claims| + claims.retain(|claim| !claim.outpoints().iter().any(|claim_outpoint| *claim_outpoint == outpoint))); + } + } + /// Upon channelmonitor.block_connected(..) or upon provision of a preimage on the forward link /// for this channel, provide new relevant on-chain transactions and/or new claim requests. /// Together with `update_claims_view_from_matched_txn` this used to be named @@ -692,7 +726,10 @@ impl OnchainTxHandler B::Target: BroadcasterInterface, F::Target: FeeEstimator, { - log_debug!(logger, "Updating claims view at height {} with {} claim requests", cur_height, requests.len()); + if !requests.is_empty() { + log_debug!(logger, "Updating claims view at height {} with {} claim requests", cur_height, requests.len()); + } + let mut preprocessed_requests = Vec::with_capacity(requests.len()); let mut aggregated_request = None; @@ -738,6 +775,12 @@ impl OnchainTxHandler // Claim everything up to and including `cur_height` let remaining_locked_packages = self.locktimed_packages.split_off(&(cur_height + 1)); + if !self.locktimed_packages.is_empty() { + log_debug!(logger, + "Updating claims view at height {} with {} locked packages available for claim", + cur_height, + self.locktimed_packages.len()); + } for (pop_height, mut entry) in self.locktimed_packages.iter_mut() { log_trace!(logger, "Restoring delayed claim of package(s) at their timelock at {}.", pop_height); preprocessed_requests.append(&mut entry); @@ -748,7 +791,7 @@ impl OnchainTxHandler // height timer expiration (i.e in how many blocks we're going to take action). for mut req in preprocessed_requests { if let Some((new_timer, new_feerate, claim)) = self.generate_claim( - cur_height, &req, true /* force_feerate_bump */, &*fee_estimator, &*logger, + cur_height, &req, &FeerateStrategy::ForceBump, &*fee_estimator, &*logger, ) { req.set_timer(new_timer); req.set_feerate(new_feerate); @@ -757,9 +800,13 @@ impl OnchainTxHandler // `OnchainClaim`. let claim_id = match claim { OnchainClaim::Tx(tx) => { - log_info!(logger, "Broadcasting onchain {}", log_tx!(tx)); - broadcaster.broadcast_transactions(&[&tx]); - ClaimId(tx.txid().to_byte_array()) + if tx.is_fully_signed() { + log_info!(logger, "Broadcasting onchain {}", log_tx!(tx.0)); + broadcaster.broadcast_transactions(&[&tx.0]); + } else { + log_info!(logger, "Waiting for signature of unsigned onchain transaction {}", tx.0.txid()); + } + ClaimId(tx.0.txid().to_byte_array()) }, OnchainClaim::Event(claim_event) => { log_info!(logger, "Yielding onchain event to spend inputs {:?}", req.outpoints()); @@ -787,7 +834,9 @@ impl OnchainTxHandler claim_id }, }; - debug_assert!(self.pending_claim_requests.get(&claim_id).is_none()); + // Because fuzzing can cause hash collisions, we can end up with conflicting claim + // ids here, so we only assert when not fuzzing. + debug_assert!(cfg!(fuzzing) || self.pending_claim_requests.get(&claim_id).is_none()); for k in req.outpoints() { log_info!(logger, "Registering claiming request for {}:{}", k.txid, k.vout); self.claimable_outpoints.insert(k.clone(), (claim_id, conf_height)); @@ -812,8 +861,15 @@ impl OnchainTxHandler B::Target: BroadcasterInterface, F::Target: FeeEstimator, { - log_debug!(logger, "Updating claims view at height {} with {} matched transactions in block {}", cur_height, txn_matched.len(), conf_height); - let mut bump_candidates = HashMap::new(); + let mut have_logged_intro = false; + let mut maybe_log_intro = || { + if !have_logged_intro { + log_debug!(logger, "Updating claims view at height {} with {} matched transactions in block {}", cur_height, txn_matched.len(), conf_height); + have_logged_intro = true; + } + }; + let mut bump_candidates = new_hash_map(); + if !txn_matched.is_empty() { maybe_log_intro(); } for tx in txn_matched { // Scan all input to verify is one of the outpoint spent is of interest for us let mut claimed_outputs_material = Vec::new(); @@ -906,6 +962,7 @@ impl OnchainTxHandler self.onchain_events_awaiting_threshold_conf.drain(..).collect::>(); for entry in onchain_events_awaiting_threshold_conf { if entry.has_reached_confirmation_threshold(cur_height) { + maybe_log_intro(); match entry.event { OnchainEvent::Claim { claim_id } => { // We may remove a whole set of claim outpoints here, as these one may have @@ -943,15 +1000,24 @@ impl OnchainTxHandler } // Build, bump and rebroadcast tx accordingly - log_trace!(logger, "Bumping {} candidates", bump_candidates.len()); + if !bump_candidates.is_empty() { + maybe_log_intro(); + log_trace!(logger, "Bumping {} candidates", bump_candidates.len()); + } + for (claim_id, request) in bump_candidates.iter() { if let Some((new_timer, new_feerate, bump_claim)) = self.generate_claim( - cur_height, &request, true /* force_feerate_bump */, &*fee_estimator, &*logger, + cur_height, &request, &FeerateStrategy::ForceBump, &*fee_estimator, &*logger, ) { match bump_claim { OnchainClaim::Tx(bump_tx) => { - log_info!(logger, "Broadcasting RBF-bumped onchain {}", log_tx!(bump_tx)); - broadcaster.broadcast_transactions(&[&bump_tx]); + if bump_tx.is_fully_signed() { + log_info!(logger, "Broadcasting RBF-bumped onchain {}", log_tx!(bump_tx.0)); + broadcaster.broadcast_transactions(&[&bump_tx.0]); + } else { + log_info!(logger, "Waiting for signature of RBF-bumped unsigned onchain transaction {}", + bump_tx.0.txid()); + } }, OnchainClaim::Event(claim_event) => { log_info!(logger, "Yielding RBF-bumped onchain event to spend inputs {:?}", request.outpoints()); @@ -999,7 +1065,7 @@ impl OnchainTxHandler where B::Target: BroadcasterInterface, F::Target: FeeEstimator, { - let mut bump_candidates = HashMap::new(); + let mut bump_candidates = new_hash_map(); let onchain_events_awaiting_threshold_conf = self.onchain_events_awaiting_threshold_conf.drain(..).collect::>(); for entry in onchain_events_awaiting_threshold_conf { @@ -1027,14 +1093,18 @@ impl OnchainTxHandler // `height` is the height being disconnected, so our `current_height` is 1 lower. let current_height = height - 1; if let Some((new_timer, new_feerate, bump_claim)) = self.generate_claim( - current_height, &request, true /* force_feerate_bump */, fee_estimator, logger + current_height, &request, &FeerateStrategy::ForceBump, fee_estimator, logger ) { request.set_timer(new_timer); request.set_feerate(new_feerate); match bump_claim { OnchainClaim::Tx(bump_tx) => { - log_info!(logger, "Broadcasting onchain {}", log_tx!(bump_tx)); - broadcaster.broadcast_transactions(&[&bump_tx]); + if bump_tx.is_fully_signed() { + log_info!(logger, "Broadcasting onchain {}", log_tx!(bump_tx.0)); + broadcaster.broadcast_transactions(&[&bump_tx.0]); + } else { + log_info!(logger, "Waiting for signature of unsigned onchain transaction {}", bump_tx.0.txid()); + } }, OnchainClaim::Event(claim_event) => { log_info!(logger, "Yielding onchain event after reorg to spend inputs {:?}", request.outpoints()); @@ -1087,13 +1157,11 @@ impl OnchainTxHandler &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 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 { - let sig = self.signer.sign_holder_commitment(&self.holder_commitment, &self.secp_ctx).expect("signing holder commitment"); - self.holder_commitment.add_holder_sig(funding_redeemscript, sig) + pub(crate) fn get_maybe_signed_holder_tx(&mut self, funding_redeemscript: &Script) -> MaybeSignedTransaction { + let tx = self.signer.sign_holder_commitment(&self.holder_commitment, &self.secp_ctx) + .map(|sig| self.holder_commitment.add_holder_sig(funding_redeemscript, sig)) + .unwrap_or_else(|_| self.get_unsigned_holder_commitment_tx().clone()); + MaybeSignedTransaction(tx) } #[cfg(any(test, feature="unsafe_revoked_tx_signing"))] @@ -1102,7 +1170,7 @@ impl OnchainTxHandler self.holder_commitment.add_holder_sig(funding_redeemscript, sig) } - pub(crate) fn get_fully_signed_htlc_tx(&mut self, outp: &::bitcoin::OutPoint, preimage: &Option) -> Option { + pub(crate) fn get_maybe_signed_htlc_tx(&mut self, outp: &::bitcoin::OutPoint, preimage: &Option) -> Option { let get_signed_htlc_tx = |holder_commitment: &HolderCommitmentTransaction| { let trusted_tx = holder_commitment.trust(); if trusted_tx.txid() != outp.txid { @@ -1130,11 +1198,12 @@ impl OnchainTxHandler preimage: preimage.clone(), counterparty_sig: counterparty_htlc_sig.clone(), }; - let htlc_sig = self.signer.sign_holder_htlc_transaction(&htlc_tx, 0, &htlc_descriptor, &self.secp_ctx).unwrap(); - htlc_tx.input[0].witness = trusted_tx.build_htlc_input_witness( - htlc_idx, &counterparty_htlc_sig, &htlc_sig, preimage, - ); - Some(htlc_tx) + if let Ok(htlc_sig) = self.signer.sign_holder_htlc_transaction(&htlc_tx, 0, &htlc_descriptor, &self.secp_ctx) { + htlc_tx.input[0].witness = trusted_tx.build_htlc_input_witness( + htlc_idx, &counterparty_htlc_sig, &htlc_sig, preimage, + ); + } + Some(MaybeSignedTransaction(htlc_tx)) }; // Check if the HTLC spends from the current holder commitment first, or the previous. diff --git a/lightning/src/chain/package.rs b/lightning/src/chain/package.rs index efc32bf7d40..e304b16ef3e 100644 --- a/lightning/src/chain/package.rs +++ b/lightning/src/chain/package.rs @@ -28,8 +28,9 @@ use crate::ln::features::ChannelTypeFeatures; use crate::ln::channel_keys::{DelayedPaymentBasepoint, HtlcBasepoint}; use crate::ln::msgs::DecodeError; use crate::chain::chaininterface::{FeeEstimator, ConfirmationTarget, MIN_RELAY_FEE_SAT_PER_1000_WEIGHT, compute_feerate_sat_per_1000_weight, FEERATE_FLOOR_SATS_PER_KW}; +use crate::chain::transaction::MaybeSignedTransaction; use crate::sign::ecdsa::WriteableEcdsaChannelSigner; -use crate::chain::onchaintx::{ExternalHTLCClaim, OnchainTxHandler}; +use crate::chain::onchaintx::{FeerateStrategy, ExternalHTLCClaim, OnchainTxHandler}; use crate::util::logger::Logger; use crate::util::ser::{Readable, Writer, Writeable, RequiredWrapper}; @@ -633,14 +634,14 @@ impl PackageSolvingData { } true } - fn get_finalized_tx(&self, outpoint: &BitcoinOutPoint, onchain_handler: &mut OnchainTxHandler) -> Option { + fn get_maybe_finalized_tx(&self, outpoint: &BitcoinOutPoint, onchain_handler: &mut OnchainTxHandler) -> Option { match self { PackageSolvingData::HolderHTLCOutput(ref outp) => { debug_assert!(!outp.channel_type_features.supports_anchors_zero_fee_htlc_tx()); - return onchain_handler.get_fully_signed_htlc_tx(outpoint, &outp.preimage); + onchain_handler.get_maybe_signed_htlc_tx(outpoint, &outp.preimage) } PackageSolvingData::HolderFundingOutput(ref outp) => { - return Some(onchain_handler.get_fully_signed_holder_tx(&outp.funding_redeemscript)); + Some(onchain_handler.get_maybe_signed_holder_tx(&outp.funding_redeemscript)) } _ => { panic!("API Error!"); } } @@ -908,10 +909,10 @@ impl PackageTemplate { } htlcs } - pub(crate) fn finalize_malleable_package( + pub(crate) fn maybe_finalize_malleable_package( &self, current_height: u32, onchain_handler: &mut OnchainTxHandler, value: u64, destination_script: ScriptBuf, logger: &L - ) -> Option { + ) -> Option { debug_assert!(self.is_malleable()); let mut bumped_tx = Transaction { version: 2, @@ -927,19 +928,17 @@ impl PackageTemplate { } for (i, (outpoint, out)) in self.inputs.iter().enumerate() { log_debug!(logger, "Adding claiming input for outpoint {}:{}", outpoint.txid, outpoint.vout); - if !out.finalize_input(&mut bumped_tx, i, onchain_handler) { return None; } + if !out.finalize_input(&mut bumped_tx, i, onchain_handler) { continue; } } - log_debug!(logger, "Finalized transaction {} ready to broadcast", bumped_tx.txid()); - Some(bumped_tx) + Some(MaybeSignedTransaction(bumped_tx)) } - pub(crate) fn finalize_untractable_package( + pub(crate) fn maybe_finalize_untractable_package( &self, onchain_handler: &mut OnchainTxHandler, logger: &L, - ) -> Option { + ) -> Option { debug_assert!(!self.is_malleable()); if let Some((outpoint, outp)) = self.inputs.first() { - if let Some(final_tx) = outp.get_finalized_tx(outpoint, onchain_handler) { + if let Some(final_tx) = outp.get_maybe_finalized_tx(outpoint, onchain_handler) { log_debug!(logger, "Adding claiming input for outpoint {}:{}", outpoint.txid, outpoint.vout); - log_debug!(logger, "Finalized transaction {} ready to broadcast", final_tx.txid()); return Some(final_tx); } return None; @@ -963,7 +962,7 @@ impl PackageTemplate { /// which was used to generate the value. Will not return less than `dust_limit_sats` for the /// value. pub(crate) fn compute_package_output( - &self, predicted_weight: u64, dust_limit_sats: u64, force_feerate_bump: bool, + &self, predicted_weight: u64, dust_limit_sats: u64, feerate_strategy: &FeerateStrategy, fee_estimator: &LowerBoundedFeeEstimator, logger: &L, ) -> Option<(u64, u64)> where F::Target: FeeEstimator, @@ -974,7 +973,7 @@ impl PackageTemplate { // If old feerate is 0, first iteration of this claim, use normal fee calculation if self.feerate_previous != 0 { if let Some((new_fee, feerate)) = feerate_bump( - predicted_weight, input_amounts, self.feerate_previous, force_feerate_bump, + predicted_weight, input_amounts, self.feerate_previous, feerate_strategy, fee_estimator, logger, ) { return Some((cmp::max(input_amounts as i64 - new_fee as i64, dust_limit_sats as i64) as u64, feerate)); @@ -987,32 +986,32 @@ impl PackageTemplate { None } - /// Computes a feerate based on the given confirmation target. If a previous feerate was used, - /// the new feerate is below it, and `force_feerate_bump` is set, we'll use a 25% increase of - /// the previous feerate instead of the new feerate. + /// Computes a feerate based on the given confirmation target and feerate strategy. pub(crate) fn compute_package_feerate( &self, fee_estimator: &LowerBoundedFeeEstimator, conf_target: ConfirmationTarget, - force_feerate_bump: bool, + feerate_strategy: &FeerateStrategy, ) -> u32 where F::Target: FeeEstimator { let feerate_estimate = fee_estimator.bounded_sat_per_1000_weight(conf_target); if self.feerate_previous != 0 { - // 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 { - // 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 + let previous_feerate = self.feerate_previous.try_into().unwrap_or(u32::max_value()); + match feerate_strategy { + FeerateStrategy::RetryPrevious => previous_feerate, + FeerateStrategy::HighestOfPreviousOrNew => cmp::max(previous_feerate, feerate_estimate), + FeerateStrategy::ForceBump => if feerate_estimate > previous_feerate { + feerate_estimate + } else { + // 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 @@ -1128,12 +1127,12 @@ fn compute_fee_from_spent_amounts(input_amounts: u64, predi /// Attempt to propose a bumping fee for a transaction from its spent output's values and predicted /// weight. If feerates proposed by the fee-estimator have been increasing since last fee-bumping -/// attempt, use them. If `force_feerate_bump` is set, we bump the feerate by 25% of the previous -/// feerate, or just use the previous feerate otherwise. If a feerate bump did happen, we also -/// verify that those bumping heuristics respect BIP125 rules 3) and 4) and if required adjust the -/// new fee to meet the RBF policy requirement. +/// attempt, use them. If we need to force a feerate bump, we manually bump the feerate by 25% of +/// the previous feerate. If a feerate bump did happen, we also verify that those bumping heuristics +/// respect BIP125 rules 3) and 4) and if required adjust the new fee to meet the RBF policy +/// requirement. fn feerate_bump( - predicted_weight: u64, input_amounts: u64, previous_feerate: u64, force_feerate_bump: bool, + predicted_weight: u64, input_amounts: u64, previous_feerate: u64, feerate_strategy: &FeerateStrategy, fee_estimator: &LowerBoundedFeeEstimator, logger: &L, ) -> Option<(u64, u64)> where @@ -1141,20 +1140,29 @@ where { // If old feerate inferior to actual one given back by Fee Estimator, use it to compute new fee... let (new_fee, new_feerate) = if let Some((new_fee, new_feerate)) = compute_fee_from_spent_amounts(input_amounts, predicted_weight, fee_estimator, logger) { - if new_feerate > previous_feerate { - (new_fee, new_feerate) - } else if !force_feerate_bump { - let previous_fee = previous_feerate * predicted_weight / 1000; - (previous_fee, previous_feerate) - } else { - // ...else just increase the previous feerate by 25% (because that's a nice number) - let bumped_feerate = previous_feerate + (previous_feerate / 4); - let bumped_fee = bumped_feerate * predicted_weight / 1000; - if input_amounts <= bumped_fee { - log_warn!(logger, "Can't 25% bump new claiming tx, amount {} is too small", input_amounts); - return None; - } - (bumped_fee, bumped_feerate) + match feerate_strategy { + FeerateStrategy::RetryPrevious => { + let previous_fee = previous_feerate * predicted_weight / 1000; + (previous_fee, previous_feerate) + }, + FeerateStrategy::HighestOfPreviousOrNew => if new_feerate > previous_feerate { + (new_fee, new_feerate) + } else { + let previous_fee = previous_feerate * predicted_weight / 1000; + (previous_fee, previous_feerate) + }, + FeerateStrategy::ForceBump => if new_feerate > previous_feerate { + (new_fee, new_feerate) + } else { + // ...else just increase the previous feerate by 25% (because that's a nice number) + let bumped_feerate = previous_feerate + (previous_feerate / 4); + let bumped_fee = bumped_feerate * predicted_weight / 1000; + if input_amounts <= bumped_fee { + log_warn!(logger, "Can't 25% bump new claiming tx, amount {} is too small", input_amounts); + return None; + } + (bumped_fee, bumped_feerate) + }, } } else { log_warn!(logger, "Can't new-estimation bump new claiming tx, amount {} is too small", input_amounts); diff --git a/lightning/src/chain/transaction.rs b/lightning/src/chain/transaction.rs index 5bef97792d3..7a447dd5d90 100644 --- a/lightning/src/chain/transaction.rs +++ b/lightning/src/chain/transaction.rs @@ -9,9 +9,7 @@ //! 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; @@ -58,14 +56,9 @@ pub struct OutPoint { } impl OutPoint { - /// Convert an `OutPoint` to a lightning channel id. - pub fn to_channel_id(&self) -> ChannelId { - ChannelId::v1_from_funding_txid(self.txid.as_byte_array(), self.index) - } - /// Converts this OutPoint into the OutPoint field as used by rust-bitcoin /// - /// This is not exported to bindings users as the same type is used universally in the C bindings + /// This is not exported to bindings users as the same type is used universally in the C bindings /// for all outpoints pub fn into_bitcoin_outpoint(self) -> BitcoinOutPoint { BitcoinOutPoint { @@ -83,9 +76,19 @@ impl core::fmt::Display for OutPoint { impl_writeable!(OutPoint, { txid, index }); +#[derive(Debug, Clone)] +pub(crate) struct MaybeSignedTransaction(pub Transaction); + +impl MaybeSignedTransaction { + pub fn is_fully_signed(&self) -> bool { + !self.0.input.iter().any(|input| input.witness.is_empty()) + } +} + #[cfg(test)] mod tests { use crate::chain::transaction::OutPoint; + use crate::ln::ChannelId; use bitcoin::blockdata::transaction::Transaction; use bitcoin::consensus::encode; @@ -94,13 +97,13 @@ mod tests { #[test] fn test_channel_id_calculation() { let tx: Transaction = encode::deserialize(&>::from_hex("020000000001010e0adef48412e4361325ac1c6e36411299ab09d4f083b9d8ddb55fbc06e1b0c00000000000feffffff0220a1070000000000220020f81d95e040bd0a493e38bae27bff52fe2bb58b93b293eb579c01c31b05c5af1dc072cfee54a3000016001434b1d6211af5551905dc2642d05f5b04d25a8fe80247304402207f570e3f0de50546aad25a872e3df059d277e776dda4269fa0d2cc8c2ee6ec9a022054e7fae5ca94d47534c86705857c24ceea3ad51c69dd6051c5850304880fc43a012103cb11a1bacc223d98d91f1946c6752e358a5eb1a1c983b3e6fb15378f453b76bd00000000").unwrap()[..]).unwrap(); - assert_eq!(&OutPoint { + assert_eq!(&ChannelId::v1_from_funding_outpoint(OutPoint { txid: tx.txid(), index: 0 - }.to_channel_id().0[..], &>::from_hex("3e88dd7165faf7be58b3c5bb2c9c452aebef682807ea57080f62e6f6e113c25e").unwrap()[..]); - assert_eq!(&OutPoint { + }).0[..], &>::from_hex("3e88dd7165faf7be58b3c5bb2c9c452aebef682807ea57080f62e6f6e113c25e").unwrap()[..]); + assert_eq!(&ChannelId::v1_from_funding_outpoint(OutPoint { txid: tx.txid(), index: 1 - }.to_channel_id().0[..], &>::from_hex("3e88dd7165faf7be58b3c5bb2c9c452aebef682807ea57080f62e6f6e113c25f").unwrap()[..]); + }).0[..], &>::from_hex("3e88dd7165faf7be58b3c5bb2c9c452aebef682807ea57080f62e6f6e113c25f").unwrap()[..]); } } diff --git a/lightning/src/util/chacha20.rs b/lightning/src/crypto/chacha20.rs similarity index 98% rename from lightning/src/util/chacha20.rs rename to lightning/src/crypto/chacha20.rs index 865a09fa040..d6fd3a7dee0 100644 --- a/lightning/src/util/chacha20.rs +++ b/lightning/src/crypto/chacha20.rs @@ -9,8 +9,6 @@ // You may not use this file except in accordance with one or both of these // licenses. -use crate::io; - #[cfg(not(fuzzing))] mod real_chacha { use core::cmp; @@ -335,27 +333,14 @@ mod fuzzy_chacha { #[cfg(fuzzing)] pub use self::fuzzy_chacha::ChaCha20; -pub(crate) struct ChaChaReader<'a, R: io::Read> { - pub chacha: &'a mut ChaCha20, - pub read: R, -} -impl<'a, R: io::Read> io::Read for ChaChaReader<'a, R> { - fn read(&mut self, dest: &mut [u8]) -> Result { - let res = self.read.read(dest)?; - if res > 0 { - self.chacha.process_in_place(&mut dest[0..res]); - } - Ok(res) - } -} - #[cfg(test)] mod test { - use crate::prelude::*; + use alloc::vec; + use alloc::vec::{Vec}; + use core::convert::TryInto; use core::iter::repeat; use super::ChaCha20; - use std::convert::TryInto; #[test] fn test_chacha20_256_tls_vectors() { diff --git a/lightning/src/crypto/chacha20poly1305rfc.rs b/lightning/src/crypto/chacha20poly1305rfc.rs new file mode 100644 index 00000000000..4ca974421c6 --- /dev/null +++ b/lightning/src/crypto/chacha20poly1305rfc.rs @@ -0,0 +1,237 @@ +// ring has a garbage API so its use is avoided, but rust-crypto doesn't have RFC-variant poly1305 +// Instead, we steal rust-crypto's implementation and tweak it to match the RFC. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. +// +// This is a port of Andrew Moons poly1305-donna +// https://github.com/floodyberry/poly1305-donna + +#[cfg(not(fuzzing))] +mod real_chachapoly { + use super::super::chacha20::ChaCha20; + use super::super::poly1305::Poly1305; + use super::super::fixed_time_eq; + + #[derive(Clone, Copy)] + pub struct ChaCha20Poly1305RFC { + cipher: ChaCha20, + mac: Poly1305, + finished: bool, + data_len: usize, + aad_len: u64, + } + + impl ChaCha20Poly1305RFC { + #[inline] + fn pad_mac_16(mac: &mut Poly1305, len: usize) { + if len % 16 != 0 { + mac.input(&[0; 16][0..16 - (len % 16)]); + } + } + pub fn new(key: &[u8], nonce: &[u8], aad: &[u8]) -> ChaCha20Poly1305RFC { + assert!(key.len() == 16 || key.len() == 32); + assert!(nonce.len() == 12); + + // Ehh, I'm too lazy to *also* tweak ChaCha20 to make it RFC-compliant + assert!(nonce[0] == 0 && nonce[1] == 0 && nonce[2] == 0 && nonce[3] == 0); + + let mut cipher = ChaCha20::new(key, &nonce[4..]); + let mut mac_key = [0u8; 64]; + let zero_key = [0u8; 64]; + cipher.process(&zero_key, &mut mac_key); + + let mut mac = Poly1305::new(&mac_key[..32]); + mac.input(aad); + ChaCha20Poly1305RFC::pad_mac_16(&mut mac, aad.len()); + + ChaCha20Poly1305RFC { + cipher, + mac, + finished: false, + data_len: 0, + aad_len: aad.len() as u64, + } + } + + pub fn encrypt(&mut self, input: &[u8], output: &mut [u8], out_tag: &mut [u8]) { + assert!(input.len() == output.len()); + assert!(self.finished == false); + self.cipher.process(input, output); + self.data_len += input.len(); + self.mac.input(output); + ChaCha20Poly1305RFC::pad_mac_16(&mut self.mac, self.data_len); + self.finished = true; + self.mac.input(&self.aad_len.to_le_bytes()); + self.mac.input(&(self.data_len as u64).to_le_bytes()); + self.mac.raw_result(out_tag); + } + + pub fn encrypt_full_message_in_place(&mut self, input_output: &mut [u8], out_tag: &mut [u8]) { + self.encrypt_in_place(input_output); + self.finish_and_get_tag(out_tag); + } + + // Encrypt `input_output` in-place. To finish and calculate the tag, use `finish_and_get_tag` + // below. + pub(in super::super) fn encrypt_in_place(&mut self, input_output: &mut [u8]) { + debug_assert!(self.finished == false); + self.cipher.process_in_place(input_output); + self.data_len += input_output.len(); + self.mac.input(input_output); + } + + // If we were previously encrypting with `encrypt_in_place`, this method can be used to finish + // encrypting and calculate the tag. + pub(in super::super) fn finish_and_get_tag(&mut self, out_tag: &mut [u8]) { + debug_assert!(self.finished == false); + ChaCha20Poly1305RFC::pad_mac_16(&mut self.mac, self.data_len); + self.finished = true; + self.mac.input(&self.aad_len.to_le_bytes()); + self.mac.input(&(self.data_len as u64).to_le_bytes()); + self.mac.raw_result(out_tag); + } + + /// Decrypt the `input`, checking the given `tag` prior to writing the decrypted contents + /// into `output`. Note that, because `output` is not touched until the `tag` is checked, + /// this decryption is *variable time*. + pub fn variable_time_decrypt(&mut self, input: &[u8], output: &mut [u8], tag: &[u8]) -> Result<(), ()> { + assert!(input.len() == output.len()); + assert!(self.finished == false); + + self.finished = true; + + self.mac.input(input); + + self.data_len += input.len(); + ChaCha20Poly1305RFC::pad_mac_16(&mut self.mac, self.data_len); + self.mac.input(&self.aad_len.to_le_bytes()); + self.mac.input(&(self.data_len as u64).to_le_bytes()); + + let mut calc_tag = [0u8; 16]; + self.mac.raw_result(&mut calc_tag); + if fixed_time_eq(&calc_tag, tag) { + self.cipher.process(input, output); + Ok(()) + } else { + Err(()) + } + } + + pub fn check_decrypt_in_place(&mut self, input_output: &mut [u8], tag: &[u8]) -> Result<(), ()> { + self.decrypt_in_place(input_output); + if self.finish_and_check_tag(tag) { Ok(()) } else { Err(()) } + } + + /// Decrypt in place, without checking the tag. Use `finish_and_check_tag` to check it + /// later when decryption finishes. + /// + /// Should never be `pub` because the public API should always enforce tag checking. + pub(in super::super) fn decrypt_in_place(&mut self, input_output: &mut [u8]) { + debug_assert!(self.finished == false); + self.mac.input(input_output); + self.data_len += input_output.len(); + self.cipher.process_in_place(input_output); + } + + /// If we were previously decrypting with `just_decrypt_in_place`, this method must be used + /// to check the tag. Returns whether or not the tag is valid. + pub(in super::super) fn finish_and_check_tag(&mut self, tag: &[u8]) -> bool { + debug_assert!(self.finished == false); + self.finished = true; + ChaCha20Poly1305RFC::pad_mac_16(&mut self.mac, self.data_len); + self.mac.input(&self.aad_len.to_le_bytes()); + self.mac.input(&(self.data_len as u64).to_le_bytes()); + + let mut calc_tag = [0u8; 16]; + self.mac.raw_result(&mut calc_tag); + if fixed_time_eq(&calc_tag, tag) { + true + } else { + false + } + } + } +} +#[cfg(not(fuzzing))] +pub use self::real_chachapoly::ChaCha20Poly1305RFC; + +#[cfg(fuzzing)] +mod fuzzy_chachapoly { + #[derive(Clone, Copy)] + pub struct ChaCha20Poly1305RFC { + tag: [u8; 16], + finished: bool, + } + impl ChaCha20Poly1305RFC { + pub fn new(key: &[u8], nonce: &[u8], _aad: &[u8]) -> ChaCha20Poly1305RFC { + assert!(key.len() == 16 || key.len() == 32); + assert!(nonce.len() == 12); + + // Ehh, I'm too lazy to *also* tweak ChaCha20 to make it RFC-compliant + assert!(nonce[0] == 0 && nonce[1] == 0 && nonce[2] == 0 && nonce[3] == 0); + + let mut tag = [0; 16]; + tag.copy_from_slice(&key[0..16]); + + ChaCha20Poly1305RFC { + tag, + finished: false, + } + } + + pub fn encrypt(&mut self, input: &[u8], output: &mut [u8], out_tag: &mut [u8]) { + assert!(input.len() == output.len()); + assert!(self.finished == false); + + output.copy_from_slice(&input); + out_tag.copy_from_slice(&self.tag); + self.finished = true; + } + + pub fn encrypt_full_message_in_place(&mut self, input_output: &mut [u8], out_tag: &mut [u8]) { + self.encrypt_in_place(input_output); + self.finish_and_get_tag(out_tag); + } + + pub(in super::super) fn encrypt_in_place(&mut self, _input_output: &mut [u8]) { + assert!(self.finished == false); + } + + pub(in super::super) fn finish_and_get_tag(&mut self, out_tag: &mut [u8]) { + assert!(self.finished == false); + out_tag.copy_from_slice(&self.tag); + self.finished = true; + } + + pub fn variable_time_decrypt(&mut self, input: &[u8], output: &mut [u8], tag: &[u8]) -> Result<(), ()> { + assert!(input.len() == output.len()); + assert!(self.finished == false); + + if tag[..] != self.tag[..] { return Err(()); } + output.copy_from_slice(input); + self.finished = true; + Ok(()) + } + + pub fn check_decrypt_in_place(&mut self, input_output: &mut [u8], tag: &[u8]) -> Result<(), ()> { + self.decrypt_in_place(input_output); + if self.finish_and_check_tag(tag) { Ok(()) } else { Err(()) } + } + + pub(in super::super) fn decrypt_in_place(&mut self, _input: &mut [u8]) { + assert!(self.finished == false); + } + + pub(in super::super) fn finish_and_check_tag(&mut self, tag: &[u8]) -> bool { + if tag[..] != self.tag[..] { return false; } + self.finished = true; + true + } + } +} +#[cfg(fuzzing)] +pub use self::fuzzy_chachapoly::ChaCha20Poly1305RFC; diff --git a/lightning/src/crypto/mod.rs b/lightning/src/crypto/mod.rs new file mode 100644 index 00000000000..b4f5af8f119 --- /dev/null +++ b/lightning/src/crypto/mod.rs @@ -0,0 +1,9 @@ +#[cfg(not(fuzzing))] +use bitcoin::hashes::cmp::fixed_time_eq; + +pub(crate) mod chacha20; +#[cfg(not(fuzzing))] +pub(crate) mod poly1305; +pub(crate) mod chacha20poly1305rfc; +pub(crate) mod streams; +pub(crate) mod utils; diff --git a/lightning/src/util/poly1305.rs b/lightning/src/crypto/poly1305.rs similarity index 99% rename from lightning/src/util/poly1305.rs rename to lightning/src/crypto/poly1305.rs index 1abda74b6c2..a1b9fbac516 100644 --- a/lightning/src/util/poly1305.rs +++ b/lightning/src/crypto/poly1305.rs @@ -205,10 +205,10 @@ impl Poly1305 { #[cfg(test)] mod test { - use crate::prelude::*; use core::iter::repeat; + use alloc::vec::Vec; - use crate::util::poly1305::Poly1305; + use super::Poly1305; fn poly1305(key: &[u8], msg: &[u8], mac: &mut [u8]) { let mut poly = Poly1305::new(key); diff --git a/lightning/src/crypto/streams.rs b/lightning/src/crypto/streams.rs new file mode 100644 index 00000000000..14921a38612 --- /dev/null +++ b/lightning/src/crypto/streams.rs @@ -0,0 +1,211 @@ +use crate::crypto::chacha20::ChaCha20; +use crate::crypto::chacha20poly1305rfc::ChaCha20Poly1305RFC; + +use crate::ln::msgs::DecodeError; +use crate::util::ser::{FixedLengthReader, LengthRead, LengthReadableArgs, Readable, Writeable, Writer}; +use crate::io::{self, Read, Write}; + +pub(crate) struct ChaChaReader<'a, R: io::Read> { + pub chacha: &'a mut ChaCha20, + pub read: R, +} +impl<'a, R: io::Read> io::Read for ChaChaReader<'a, R> { + fn read(&mut self, dest: &mut [u8]) -> Result { + let res = self.read.read(dest)?; + if res > 0 { + self.chacha.process_in_place(&mut dest[0..res]); + } + Ok(res) + } +} + +/// Enables the use of the serialization macros for objects that need to be simultaneously encrypted and +/// serialized. This allows us to avoid an intermediate Vec allocation. +pub(crate) struct ChaChaPolyWriteAdapter<'a, W: Writeable> { + pub rho: [u8; 32], + pub writeable: &'a W, +} + +impl<'a, W: Writeable> ChaChaPolyWriteAdapter<'a, W> { + #[allow(unused)] // This will be used for onion messages soon + pub fn new(rho: [u8; 32], writeable: &'a W) -> ChaChaPolyWriteAdapter<'a, W> { + Self { rho, writeable } + } +} + +impl<'a, T: Writeable> Writeable for ChaChaPolyWriteAdapter<'a, T> { + // Simultaneously write and encrypt Self::writeable. + fn write(&self, w: &mut W) -> Result<(), io::Error> { + let mut chacha = ChaCha20Poly1305RFC::new(&self.rho, &[0; 12], &[]); + let mut chacha_stream = ChaChaPolyWriter { chacha: &mut chacha, write: w }; + self.writeable.write(&mut chacha_stream)?; + let mut tag = [0 as u8; 16]; + chacha.finish_and_get_tag(&mut tag); + tag.write(w)?; + + Ok(()) + } +} + +/// Enables the use of the serialization macros for objects that need to be simultaneously decrypted and +/// deserialized. This allows us to avoid an intermediate Vec allocation. +pub(crate) struct ChaChaPolyReadAdapter { + pub readable: R, +} + +impl LengthReadableArgs<[u8; 32]> for ChaChaPolyReadAdapter { + // Simultaneously read and decrypt an object from a LengthRead, storing it in Self::readable. + // LengthRead must be used instead of std::io::Read because we need the total length to separate + // out the tag at the end. + fn read(mut r: &mut R, secret: [u8; 32]) -> Result { + if r.total_bytes() < 16 { return Err(DecodeError::InvalidValue) } + + let mut chacha = ChaCha20Poly1305RFC::new(&secret, &[0; 12], &[]); + let decrypted_len = r.total_bytes() - 16; + let s = FixedLengthReader::new(&mut r, decrypted_len); + let mut chacha_stream = ChaChaPolyReader { chacha: &mut chacha, read: s }; + let readable: T = Readable::read(&mut chacha_stream)?; + chacha_stream.read.eat_remaining()?; + + let mut tag = [0 as u8; 16]; + r.read_exact(&mut tag)?; + if !chacha.finish_and_check_tag(&tag) { + return Err(DecodeError::InvalidValue) + } + + Ok(Self { readable }) + } +} + + +/// Enables simultaneously reading and decrypting a ChaCha20Poly1305RFC stream from a std::io::Read. +struct ChaChaPolyReader<'a, R: Read> { + pub chacha: &'a mut ChaCha20Poly1305RFC, + pub read: R, +} + +impl<'a, R: Read> Read for ChaChaPolyReader<'a, R> { + // Decrypt bytes from Self::read into `dest`. + // `ChaCha20Poly1305RFC::finish_and_check_tag` must be called to check the tag after all reads + // complete. + fn read(&mut self, dest: &mut [u8]) -> Result { + let res = self.read.read(dest)?; + if res > 0 { + self.chacha.decrypt_in_place(&mut dest[0..res]); + } + Ok(res) + } +} + +/// Enables simultaneously writing and encrypting a byte stream into a Writer. +struct ChaChaPolyWriter<'a, W: Writer> { + pub chacha: &'a mut ChaCha20Poly1305RFC, + pub write: &'a mut W, +} + +impl<'a, W: Writer> Writer for ChaChaPolyWriter<'a, W> { + // Encrypt then write bytes from `src` into Self::write. + // `ChaCha20Poly1305RFC::finish_and_get_tag` can be called to retrieve the tag after all writes + // complete. + fn write_all(&mut self, src: &[u8]) -> Result<(), io::Error> { + let mut src_idx = 0; + while src_idx < src.len() { + let mut write_buffer = [0; 8192]; + let bytes_written = (&mut write_buffer[..]).write(&src[src_idx..]).expect("In-memory writes can't fail"); + self.chacha.encrypt_in_place(&mut write_buffer[..bytes_written]); + self.write.write_all(&write_buffer[..bytes_written])?; + src_idx += bytes_written; + } + Ok(()) + } +} + + +#[cfg(test)] +mod tests { + use crate::ln::msgs::DecodeError; + use super::{ChaChaPolyReadAdapter, ChaChaPolyWriteAdapter}; + use crate::util::ser::{self, FixedLengthReader, LengthReadableArgs, Writeable}; + + // Used for for testing various lengths of serialization. + #[derive(Debug, PartialEq, Eq)] + struct TestWriteable { + field1: Vec, + field2: Vec, + field3: Vec, + } + impl_writeable_tlv_based!(TestWriteable, { + (1, field1, required_vec), + (2, field2, required_vec), + (3, field3, required_vec), + }); + + #[test] + fn test_chacha_stream_adapters() { + // Check that ChaChaPolyReadAdapter and ChaChaPolyWriteAdapter correctly encode and decode an + // encrypted object. + macro_rules! check_object_read_write { + ($obj: expr) => { + // First, serialize the object, encrypted with ChaCha20Poly1305. + let rho = [42; 32]; + let writeable_len = $obj.serialized_length() as u64 + 16; + let write_adapter = ChaChaPolyWriteAdapter::new(rho, &$obj); + let encrypted_writeable_bytes = write_adapter.encode(); + let encrypted_writeable = &encrypted_writeable_bytes[..]; + + // Now deserialize the object back and make sure it matches the original. + let mut rd = FixedLengthReader::new(encrypted_writeable, writeable_len); + let read_adapter = >::read(&mut rd, rho).unwrap(); + assert_eq!($obj, read_adapter.readable); + }; + } + + // Try a big object that will require multiple write buffers. + let big_writeable = TestWriteable { + field1: vec![43], + field2: vec![44; 4192], + field3: vec![45; 4192 + 1], + }; + check_object_read_write!(big_writeable); + + // Try a small object that fits into one write buffer. + let small_writeable = TestWriteable { + field1: vec![43], + field2: vec![44], + field3: vec![45], + }; + check_object_read_write!(small_writeable); + } + + fn do_chacha_stream_adapters_ser_macros() -> Result<(), DecodeError> { + let writeable = TestWriteable { + field1: vec![43], + field2: vec![44; 4192], + field3: vec![45; 4192 + 1], + }; + + // First, serialize the object into a TLV stream, encrypted with ChaCha20Poly1305. + let rho = [42; 32]; + let write_adapter = ChaChaPolyWriteAdapter::new(rho, &writeable); + let mut writer = ser::VecWriter(Vec::new()); + encode_tlv_stream!(&mut writer, { + (1, write_adapter, required), + }); + + // Now deserialize the object back and make sure it matches the original. + let mut read_adapter: Option> = None; + decode_tlv_stream!(&writer.0[..], { + (1, read_adapter, (option: LengthReadableArgs, rho)), + }); + assert_eq!(writeable, read_adapter.unwrap().readable); + + Ok(()) + } + + #[test] + fn chacha_stream_adapters_ser_macros() { + // Test that our stream adapters work as expected with the TLV macros. + // This also serves to test the `option: $trait` variant of the `_decode_tlv` ser macro. + do_chacha_stream_adapters_ser_macros().unwrap() + } +} diff --git a/lightning/src/util/crypto.rs b/lightning/src/crypto/utils.rs similarity index 98% rename from lightning/src/util/crypto.rs rename to lightning/src/crypto/utils.rs index bb5903b2c8d..98963c7c2bd 100644 --- a/lightning/src/util/crypto.rs +++ b/lightning/src/crypto/utils.rs @@ -64,6 +64,7 @@ pub fn sign(ctx: &Secp256k1, msg: &Message, sk: &SecretKey) -> Si } #[inline] +#[allow(unused_variables)] pub fn sign_with_aux_rand( ctx: &Secp256k1, msg: &Message, sk: &SecretKey, entropy_source: &ES ) -> Signature where ES::Target: EntropySource { diff --git a/lightning/src/events/bump_transaction.rs b/lightning/src/events/bump_transaction.rs index e799800adc1..8b52af69fe3 100644 --- a/lightning/src/events/bump_transaction.rs +++ b/lightning/src/events/bump_transaction.rs @@ -18,6 +18,7 @@ use crate::chain::chaininterface::{BroadcasterInterface, fee_for_weight}; use crate::chain::ClaimId; use crate::io_extras::sink; use crate::ln::channel::ANCHOR_OUTPUT_VALUE_SATOSHI; +use crate::ln::ChannelId; use crate::ln::chan_utils; use crate::ln::chan_utils::{ ANCHOR_INPUT_WITNESS_WEIGHT, HTLC_SUCCESS_INPUT_ANCHOR_WITNESS_WEIGHT, @@ -35,8 +36,9 @@ use bitcoin::{OutPoint, PubkeyHash, Sequence, ScriptBuf, Transaction, TxIn, TxOu use bitcoin::blockdata::constants::WITNESS_SCALE_FACTOR; use bitcoin::blockdata::locktime::absolute::LockTime; use bitcoin::consensus::Encodable; +use bitcoin::psbt::PartiallySignedTransaction; use bitcoin::secp256k1; -use bitcoin::secp256k1::Secp256k1; +use bitcoin::secp256k1::{PublicKey, Secp256k1}; use bitcoin::secp256k1::ecdsa::Signature; pub(crate) const EMPTY_SCRIPT_SIG_WEIGHT: u64 = 1 /* empty script_sig */ * WITNESS_SCALE_FACTOR as u64; @@ -146,6 +148,10 @@ pub enum BumpTransactionEvent { /// [`EcdsaChannelSigner::sign_holder_anchor_input`]: crate::sign::ecdsa::EcdsaChannelSigner::sign_holder_anchor_input /// [`build_anchor_input_witness`]: crate::ln::chan_utils::build_anchor_input_witness ChannelClose { + /// The `channel_id` of the channel which has been closed. + channel_id: ChannelId, + /// Counterparty in the closed channel. + counterparty_node_id: PublicKey, /// The unique identifier for the claim of the anchor output in the commitment transaction. /// /// The identifier must map to the set of external UTXOs assigned to the claim, such that @@ -199,6 +205,10 @@ pub enum BumpTransactionEvent { /// [`EcdsaChannelSigner`]: crate::sign::ecdsa::EcdsaChannelSigner /// [`EcdsaChannelSigner::sign_holder_htlc_transaction`]: crate::sign::ecdsa::EcdsaChannelSigner::sign_holder_htlc_transaction HTLCResolution { + /// The `channel_id` of the channel which has been closed. + channel_id: ChannelId, + /// Counterparty in the closed channel. + counterparty_node_id: PublicKey, /// The unique identifier for the claim of the HTLCs in the confirmed commitment /// transaction. /// @@ -343,7 +353,10 @@ pub trait CoinSelectionSource { ) -> Result; /// 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: Transaction) -> Result; + /// + /// If your wallet does not support signing PSBTs you can call `psbt.extract_tx()` to get the + /// unsigned transaction and then sign it with your wallet. + fn sign_psbt(&self, psbt: PartiallySignedTransaction) -> Result; } /// An alternative to [`CoinSelectionSource`] that can be implemented and used along [`Wallet`] to @@ -357,7 +370,10 @@ pub trait WalletSource { /// 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: Transaction) -> Result; + /// + /// If your wallet does not support signing PSBTs you can call `psbt.extract_tx()` to get the + /// unsigned transaction and then sign it with your wallet. + fn sign_psbt(&self, psbt: PartiallySignedTransaction) -> Result; } /// A wrapper over [`WalletSource`] that implements [`CoinSelection`] by preferring UTXOs that would @@ -384,7 +400,7 @@ where /// Returns a new instance backed by the given [`WalletSource`] that serves as an implementation /// of [`CoinSelectionSource`]. pub fn new(source: W, logger: L) -> Self { - Self { source, logger, locked_utxos: Mutex::new(HashMap::new()) } + Self { source, logger, locked_utxos: Mutex::new(new_hash_map()) } } /// Performs coin selection on the set of UTXOs obtained from @@ -504,8 +520,8 @@ where .or_else(|_| do_coin_selection(true, true)) } - fn sign_tx(&self, tx: Transaction) -> Result { - self.source.sign_tx(tx) + fn sign_psbt(&self, psbt: PartiallySignedTransaction) -> Result { + self.source.sign_psbt(psbt) } } @@ -549,8 +565,8 @@ where } /// Updates a transaction with the result of a successful coin selection attempt. - fn process_coin_selection(&self, tx: &mut Transaction, mut coin_selection: CoinSelection) { - for utxo in coin_selection.confirmed_utxos.drain(..) { + fn process_coin_selection(&self, tx: &mut Transaction, coin_selection: &CoinSelection) { + for utxo in coin_selection.confirmed_utxos.iter() { tx.input.push(TxIn { previous_output: utxo.outpoint, script_sig: ScriptBuf::new(), @@ -558,7 +574,7 @@ where witness: Witness::new(), }); } - if let Some(change_output) = coin_selection.change_output.take() { + if let Some(change_output) = coin_selection.change_output.clone() { tx.output.push(change_output); } else if tx.output.is_empty() { // We weren't provided a change output, likely because the input set was a perfect @@ -595,7 +611,7 @@ where 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( + let coin_selection: CoinSelection = self.utxo_source.select_confirmed_utxos( claim_id, must_spend, &[], package_target_feerate_sat_per_1000_weight, )?; @@ -613,15 +629,29 @@ where let total_input_amount = must_spend_amount + coin_selection.confirmed_utxos.iter().map(|utxo| utxo.output.value).sum::(); - self.process_coin_selection(&mut anchor_tx, coin_selection); + self.process_coin_selection(&mut anchor_tx, &coin_selection); let anchor_txid = anchor_tx.txid(); - debug_assert_eq!(anchor_tx.output.len(), 1); + // construct psbt + let mut anchor_psbt = PartiallySignedTransaction::from_unsigned_tx(anchor_tx).unwrap(); + // add witness_utxo to anchor input + anchor_psbt.inputs[0].witness_utxo = Some(anchor_descriptor.previous_utxo()); + // add witness_utxo to remaining inputs + for (idx, utxo) in coin_selection.confirmed_utxos.into_iter().enumerate() { + // add 1 to skip the anchor input + let index = idx + 1; + debug_assert_eq!(anchor_psbt.unsigned_tx.input[index].previous_output, utxo.outpoint); + if utxo.output.script_pubkey.is_witness_program() { + anchor_psbt.inputs[index].witness_utxo = Some(utxo.output); + } + } + + debug_assert_eq!(anchor_psbt.unsigned_tx.output.len(), 1); #[cfg(debug_assertions)] - let unsigned_tx_weight = anchor_tx.weight().to_wu() - (anchor_tx.input.len() as u64 * EMPTY_SCRIPT_SIG_WEIGHT); + let unsigned_tx_weight = anchor_psbt.unsigned_tx.weight().to_wu() - (anchor_psbt.unsigned_tx.input.len() as u64 * EMPTY_SCRIPT_SIG_WEIGHT); log_debug!(self.logger, "Signing anchor transaction {}", anchor_txid); - anchor_tx = self.utxo_source.sign_tx(anchor_tx)?; + anchor_tx = self.utxo_source.sign_psbt(anchor_psbt)?; let signer = anchor_descriptor.derive_channel_signer(&self.signer_provider); let anchor_sig = signer.sign_holder_anchor_input(&anchor_tx, 0, &self.secp)?; @@ -690,7 +720,7 @@ where #[cfg(debug_assertions)] let must_spend_amount = must_spend.iter().map(|input| input.previous_utxo.value).sum::(); - let coin_selection = self.utxo_source.select_confirmed_utxos( + let coin_selection: CoinSelection = self.utxo_source.select_confirmed_utxos( claim_id, must_spend, &htlc_tx.output, target_feerate_sat_per_1000_weight, )?; @@ -701,13 +731,30 @@ where let total_input_amount = must_spend_amount + coin_selection.confirmed_utxos.iter().map(|utxo| utxo.output.value).sum::(); - self.process_coin_selection(&mut htlc_tx, coin_selection); + self.process_coin_selection(&mut htlc_tx, &coin_selection); + + // construct psbt + let mut htlc_psbt = PartiallySignedTransaction::from_unsigned_tx(htlc_tx).unwrap(); + // add witness_utxo to htlc inputs + for (i, htlc_descriptor) in htlc_descriptors.iter().enumerate() { + debug_assert_eq!(htlc_psbt.unsigned_tx.input[i].previous_output, htlc_descriptor.outpoint()); + htlc_psbt.inputs[i].witness_utxo = Some(htlc_descriptor.previous_utxo(&self.secp)); + } + // add witness_utxo to remaining inputs + for (idx, utxo) in coin_selection.confirmed_utxos.into_iter().enumerate() { + // offset to skip the htlc inputs + let index = idx + htlc_descriptors.len(); + debug_assert_eq!(htlc_psbt.unsigned_tx.input[index].previous_output, utxo.outpoint); + if utxo.output.script_pubkey.is_witness_program() { + htlc_psbt.inputs[index].witness_utxo = Some(utxo.output); + } + } #[cfg(debug_assertions)] - let unsigned_tx_weight = htlc_tx.weight().to_wu() - (htlc_tx.input.len() as u64 * EMPTY_SCRIPT_SIG_WEIGHT); + let unsigned_tx_weight = htlc_psbt.unsigned_tx.weight().to_wu() - (htlc_psbt.unsigned_tx.input.len() as u64 * EMPTY_SCRIPT_SIG_WEIGHT); - log_debug!(self.logger, "Signing HTLC transaction {}", htlc_tx.txid()); - htlc_tx = self.utxo_source.sign_tx(htlc_tx)?; + log_debug!(self.logger, "Signing HTLC transaction {}", htlc_psbt.unsigned_tx.txid()); + htlc_tx = self.utxo_source.sign_psbt(htlc_psbt)?; let mut signers = BTreeMap::new(); for (idx, htlc_descriptor) in htlc_descriptors.iter().enumerate() { @@ -759,7 +806,7 @@ where } } BumpTransactionEvent::HTLCResolution { - claim_id, target_feerate_sat_per_1000_weight, htlc_descriptors, tx_lock_time, + 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()))); diff --git a/lightning/src/events/mod.rs b/lightning/src/events/mod.rs index 5938a6cbd3b..082f8afba6e 100644 --- a/lightning/src/events/mod.rs +++ b/lightning/src/events/mod.rs @@ -24,6 +24,7 @@ use crate::ln::channel::FUNDING_CONF_DEADLINE_BLOCKS; use crate::ln::features::ChannelTypeFeatures; use crate::ln::msgs; use crate::ln::{ChannelId, PaymentPreimage, PaymentHash, PaymentSecret}; +use crate::chain::transaction; use crate::routing::gossip::NetworkUpdate; use crate::util::errors::APIError; use crate::util::ser::{BigSize, FixedLengthReader, Writeable, Writer, MaybeReadable, Readable, RequiredWrapper, UpgradableRequired, WithoutLength}; @@ -183,8 +184,20 @@ pub enum ClosureReason { HolderForceClosed, /// The channel was closed after negotiating a cooperative close and we've now broadcasted /// the cooperative close transaction. Note the shutdown may have been initiated by us. - //TODO: split between CounterpartyInitiated/LocallyInitiated - CooperativeClosure, + /// + /// This was only set in versions of LDK prior to 0.0.122. + // Can be removed once we disallow downgrading to 0.0.121 + LegacyCooperativeClosure, + /// The channel was closed after negotiating a cooperative close and we've now broadcasted + /// the cooperative close transaction. This indicates that the shutdown was initiated by our + /// counterparty. + /// + /// In rare cases where we initiated closure immediately prior to shutting down without + /// persisting, this value may be provided for channels we initiated closure for. + CounterpartyInitiatedCooperativeClosure, + /// The channel was closed after negotiating a cooperative close and we've now broadcasted + /// the cooperative close transaction. This indicates that the shutdown was initiated by us. + LocallyInitiatedCooperativeClosure, /// A commitment transaction was confirmed on chain, closing the channel. Most likely this /// commitment transaction came from our counterparty, but it may also have come from /// a copy of our own `ChannelMonitor`. @@ -229,7 +242,9 @@ impl core::fmt::Display for ClosureReason { f.write_fmt(format_args!("counterparty force-closed with message: {}", peer_msg)) }, ClosureReason::HolderForceClosed => f.write_str("user manually force-closed the channel"), - ClosureReason::CooperativeClosure => f.write_str("the channel was cooperatively closed"), + ClosureReason::LegacyCooperativeClosure => f.write_str("the channel was cooperatively closed"), + ClosureReason::CounterpartyInitiatedCooperativeClosure => f.write_str("the channel was cooperatively closed by our peer"), + ClosureReason::LocallyInitiatedCooperativeClosure => f.write_str("the channel was cooperatively closed by us"), ClosureReason::CommitmentTxConfirmed => f.write_str("commitment or closing transaction was confirmed on chain."), ClosureReason::FundingTimedOut => write!(f, "funding transaction failed to confirm within {} blocks", FUNDING_CONF_DEADLINE_BLOCKS), ClosureReason::ProcessingError { err } => { @@ -249,12 +264,14 @@ impl_writeable_tlv_based_enum_upgradable!(ClosureReason, (1, FundingTimedOut) => {}, (2, HolderForceClosed) => {}, (6, CommitmentTxConfirmed) => {}, - (4, CooperativeClosure) => {}, + (4, LegacyCooperativeClosure) => {}, (8, ProcessingError) => { (1, err, required) }, (10, DisconnectedPeer) => {}, (12, OutdatedChannelManager) => {}, (13, CounterpartyCoopClosedUnfundedChannel) => {}, - (15, FundingBatchClosure) => {} + (15, FundingBatchClosure) => {}, + (17, CounterpartyInitiatedCooperativeClosure) => {}, + (19, LocallyInitiatedCooperativeClosure) => {}, ); /// Intended destination of a failed HTLC as indicated in [`Event::HTLCHandlingFailed`]. @@ -351,6 +368,10 @@ pub enum PaymentFailureReason { /// [`PaymentParameters::expiry_time`]: crate::routing::router::PaymentParameters::expiry_time PaymentExpired, /// We failed to find a route while retrying the payment. + /// + /// Note that this generally indicates that we've exhausted the available set of possible + /// routes - we tried the payment over a few routes but were not able to find any further + /// candidate routes beyond those. RouteNotFound, /// This error should generally never happen. This likely means that there is a problem with /// your router. @@ -540,8 +561,8 @@ pub enum Event { /// replies. Handlers should connect to the node otherwise any buffered messages may be lost. /// /// [`OnionMessage`]: msgs::OnionMessage - /// [`MessageRouter`]: crate::onion_message::MessageRouter - /// [`Destination`]: crate::onion_message::Destination + /// [`MessageRouter`]: crate::onion_message::messenger::MessageRouter + /// [`Destination`]: crate::onion_message::messenger::Destination /// [`OnionMessageHandler`]: crate::ln::msgs::OnionMessageHandler ConnectionNeeded { /// The node id for the node needing a connection. @@ -782,7 +803,7 @@ pub enum Event { /// 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, - /// The fee, in milli-satoshis, which was earned as a result of the payment. + /// The total 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 /// was pending, the amount the next hop claimed will have been rounded down to the nearest @@ -793,15 +814,29 @@ pub enum Event { /// If the channel which sent us the payment has been force-closed, we will claim the funds /// via an on-chain transaction. In that case we do not yet know the on-chain transaction /// fees which we will spend and will instead set this to `None`. It is possible duplicate - /// `PaymentForwarded` events are generated for the same payment iff `fee_earned_msat` is + /// `PaymentForwarded` events are generated for the same payment iff `total_fee_earned_msat` is /// `None`. - fee_earned_msat: Option, + total_fee_earned_msat: Option, + /// The share of the total fee, in milli-satoshis, which was withheld in addition to the + /// forwarding fee. + /// + /// This will only be `Some` if we forwarded an intercepted HTLC with less than the + /// expected amount. This means our counterparty accepted to receive less than the invoice + /// amount, e.g., by claiming the payment featuring a corresponding + /// [`PaymentClaimable::counterparty_skimmed_fee_msat`]. + /// + /// Will also always be `None` for events serialized with LDK prior to version 0.0.122. + /// + /// The caveat described above the `total_fee_earned_msat` field applies here as well. + /// + /// [`PaymentClaimable::counterparty_skimmed_fee_msat`]: Self::PaymentClaimable::counterparty_skimmed_fee_msat + skimmed_fee_msat: Option, /// If this is `true`, the forwarded HTLC was claimed by our counterparty via an on-chain /// transaction. claim_from_onchain_tx: bool, /// The final amount forwarded, in milli-satoshis, after the fee is deducted. /// - /// The caveat described above the `fee_earned_msat` field applies here as well. + /// The caveat described above the `total_fee_earned_msat` field applies here as well. outbound_amount_forwarded_msat: Option, }, /// Used to indicate that a channel with the given `channel_id` is being opened and pending @@ -830,6 +865,10 @@ pub enum Event { counterparty_node_id: PublicKey, /// The outpoint of the channel's funding transaction. funding_txo: OutPoint, + /// The features that this channel will operate with. + /// + /// Will be `None` for channels created prior to LDK version 0.0.122. + channel_type: Option, }, /// Used to indicate that a channel with the given `channel_id` is ready to /// be used. This event is emitted either when the funding transaction has been confirmed @@ -861,7 +900,7 @@ pub enum Event { /// /// [`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 { + 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: ChannelId, @@ -886,6 +925,10 @@ pub enum Event { /// /// This field will be `None` for objects serialized prior to LDK 0.0.117. channel_capacity_sats: Option, + /// The original channel funding TXO; this helps checking for the existence and confirmation + /// status of the closing tx. + /// Note that for instances serialized in v0.0.119 or prior this will be missing (None). + channel_funding_txo: Option, }, /// Used to indicate to the user that they can abandon the funding transaction and recycle the /// inputs for another purpose. @@ -965,6 +1008,7 @@ pub enum Event { /// [`ChannelManager::accept_inbound_channel_with_contribution`]: crate::ln::channelmanager::ChannelManager::accept_inbound_channel_with_contribution /// [`ChannelManager::force_close_without_broadcasting_txn`]: crate::ln::channelmanager::ChannelManager::force_close_without_broadcasting_txn /// [`UserConfig::manually_accept_inbound_channels`]: crate::util::config::UserConfig::manually_accept_inbound_channels + #[cfg(dual_funding)] OpenChannelV2Request { /// The temporary channel ID of the channel requested to be opened. /// @@ -1044,6 +1088,7 @@ pub enum Event { /// /// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager /// [`ChannelManager::contribute_funding_inputs`]: crate::ln::channelmanager::ChannelManager::contribute_funding_inputs + #[cfg(dual_funding)] FundingInputsContributionReady { /// The channel_id of the channel that requires funding inputs which you'll need to pass into /// [`ChannelManager::contribute_funding_inputs`]. @@ -1249,20 +1294,21 @@ impl Writeable for Event { }); } &Event::PaymentForwarded { - fee_earned_msat, prev_channel_id, claim_from_onchain_tx, - next_channel_id, outbound_amount_forwarded_msat + total_fee_earned_msat, prev_channel_id, claim_from_onchain_tx, + next_channel_id, outbound_amount_forwarded_msat, skimmed_fee_msat, } => { 7u8.write(writer)?; write_tlv_fields!(writer, { - (0, fee_earned_msat, option), + (0, total_fee_earned_msat, option), (1, prev_channel_id, option), (2, claim_from_onchain_tx, required), (3, next_channel_id, option), (5, outbound_amount_forwarded_msat, option), + (7, skimmed_fee_msat, option), }); }, &Event::ChannelClosed { ref channel_id, ref user_channel_id, ref reason, - ref counterparty_node_id, ref channel_capacity_sats + ref counterparty_node_id, ref channel_capacity_sats, ref channel_funding_txo } => { 9u8.write(writer)?; // `user_channel_id` used to be a single u64 value. In order to remain backwards @@ -1277,6 +1323,7 @@ impl Writeable for Event { (3, user_channel_id_high, required), (5, counterparty_node_id, option), (7, channel_capacity_sats, option), + (9, channel_funding_txo, option), }); }, &Event::DiscardFunding { ref channel_id, ref transaction } => { @@ -1364,10 +1411,14 @@ impl Writeable for Event { (6, channel_type, required), }); }, - &Event::ChannelPending { ref channel_id, ref user_channel_id, ref former_temporary_channel_id, ref counterparty_node_id, ref funding_txo } => { + &Event::ChannelPending { ref channel_id, ref user_channel_id, + ref former_temporary_channel_id, ref counterparty_node_id, ref funding_txo, + ref channel_type + } => { 31u8.write(writer)?; write_tlv_fields!(writer, { (0, channel_id, required), + (1, channel_type, option), (2, user_channel_id, required), (4, former_temporary_channel_id, required), (6, counterparty_node_id, required), @@ -1403,6 +1454,7 @@ impl Writeable for Event { // drop any channels which have not yet exchanged the initial commitment_signed in V2 channel // establishment. }, + #[cfg(dual_funding)] &Event::OpenChannelV2Request { .. } => { 39u8.write(writer)?; // We never write the OpenChannelV2Request events as, upon disconnection, peers @@ -1582,21 +1634,23 @@ impl MaybeReadable for Event { }, 7u8 => { let f = || { - let mut fee_earned_msat = None; + let mut total_fee_earned_msat = None; let mut prev_channel_id = None; let mut claim_from_onchain_tx = false; let mut next_channel_id = None; let mut outbound_amount_forwarded_msat = None; + let mut skimmed_fee_msat = None; read_tlv_fields!(reader, { - (0, fee_earned_msat, option), + (0, total_fee_earned_msat, option), (1, prev_channel_id, option), (2, claim_from_onchain_tx, required), (3, next_channel_id, option), (5, outbound_amount_forwarded_msat, option), + (7, skimmed_fee_msat, option), }); Ok(Some(Event::PaymentForwarded { - fee_earned_msat, prev_channel_id, claim_from_onchain_tx, next_channel_id, - outbound_amount_forwarded_msat + total_fee_earned_msat, prev_channel_id, claim_from_onchain_tx, next_channel_id, + outbound_amount_forwarded_msat, skimmed_fee_msat, })) }; f() @@ -1609,6 +1663,7 @@ impl MaybeReadable for Event { let mut user_channel_id_high_opt: Option = None; let mut counterparty_node_id = None; let mut channel_capacity_sats = None; + let mut channel_funding_txo = None; read_tlv_fields!(reader, { (0, channel_id, required), (1, user_channel_id_low_opt, option), @@ -1616,6 +1671,7 @@ impl MaybeReadable for Event { (3, user_channel_id_high_opt, option), (5, counterparty_node_id, option), (7, channel_capacity_sats, option), + (9, channel_funding_txo, option), }); // `user_channel_id` used to be a single u64 value. In order to remain @@ -1625,7 +1681,7 @@ impl MaybeReadable for Event { ((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), - counterparty_node_id, channel_capacity_sats })) + counterparty_node_id, channel_capacity_sats, channel_funding_txo })) }; f() }, @@ -1785,8 +1841,10 @@ impl MaybeReadable for Event { let mut former_temporary_channel_id = None; let mut counterparty_node_id = RequiredWrapper(None); let mut funding_txo = RequiredWrapper(None); + let mut channel_type = None; read_tlv_fields!(reader, { (0, channel_id, required), + (1, channel_type, option), (2, user_channel_id, required), (4, former_temporary_channel_id, required), (6, counterparty_node_id, required), @@ -1798,7 +1856,8 @@ impl MaybeReadable for Event { user_channel_id, former_temporary_channel_id, counterparty_node_id: counterparty_node_id.0.unwrap(), - funding_txo: funding_txo.0.unwrap() + funding_txo: funding_txo.0.unwrap(), + channel_type, })) }; f() diff --git a/lightning/src/lib.rs b/lightning/src/lib.rs index df1bb1a2a2f..1adf3786b76 100644 --- a/lightning/src/lib.rs +++ b/lightning/src/lib.rs @@ -40,9 +40,8 @@ #![cfg_attr(not(any(test, fuzzing, feature = "_test_utils")), deny(missing_docs))] #![cfg_attr(not(any(test, feature = "_test_utils")), forbid(unsafe_code))] -// 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)] +#![deny(rustdoc::broken_intra_doc_links)] +#![deny(rustdoc::private_intra_doc_links)] // In general, rust is absolutely horrid at supporting users doing things like, // for example, compiling Rust code for real environments. Disable useless lints @@ -70,6 +69,7 @@ extern crate hex; #[cfg(any(test, feature = "_test_utils"))] extern crate regex; #[cfg(not(feature = "std"))] extern crate core2; +#[cfg(not(feature = "std"))] extern crate libm; #[cfg(ldk_bench)] extern crate criterion; @@ -84,6 +84,8 @@ pub mod onion_message; pub mod blinded_path; pub mod events; +pub(crate) mod crypto; + #[cfg(feature = "std")] /// Re-export of either `core2::io` or `std::io`, depending on the `std` feature flag. pub use std::io; @@ -163,17 +165,12 @@ mod io_extras { } mod prelude { - #[cfg(feature = "hashbrown")] - extern crate hashbrown; - pub use alloc::{vec, vec::Vec, string::String, collections::VecDeque, boxed::Box}; - #[cfg(not(feature = "hashbrown"))] - pub use std::collections::{HashMap, HashSet, hash_map}; - #[cfg(feature = "hashbrown")] - pub use self::hashbrown::{HashMap, HashSet, hash_map}; pub use alloc::borrow::ToOwned; pub use alloc::string::ToString; + + pub(crate) use crate::util::hash_tables::*; } #[cfg(all(not(ldk_bench), feature = "backtrace", feature = "std", test))] diff --git a/lightning/src/ln/async_signer_tests.rs b/lightning/src/ln/async_signer_tests.rs index 0f51bea0d36..613df570d4e 100644 --- a/lightning/src/ln/async_signer_tests.rs +++ b/lightning/src/ln/async_signer_tests.rs @@ -10,7 +10,12 @@ //! Tests for asynchronous signing. These tests verify that the channel state machine behaves //! properly with a signer implementation that asynchronously derives signatures. -use crate::events::{Event, MessageSendEvent, MessageSendEventsProvider}; +use bitcoin::{Transaction, TxOut, TxIn, Amount}; +use bitcoin::blockdata::locktime::absolute::LockTime; + +use crate::chain::channelmonitor::LATENCY_GRACE_PERIOD_BLOCKS; +use crate::events::bump_transaction::WalletSource; +use crate::events::{Event, MessageSendEvent, MessageSendEventsProvider, ClosureReason}; use crate::ln::functional_test_utils::*; use crate::ln::msgs::ChannelMessageHandler; use crate::ln::channelmanager::{PaymentId, RecipientOnionFields}; @@ -197,7 +202,7 @@ fn test_async_commitment_signature_for_funding_signed_0conf() { // nodes[0] <-- accept_channel --- nodes[1] let accept_channel = get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, nodes[0].node.get_our_node_id()); - assert_eq!(accept_channel.minimum_depth, 0, "Expected minimum depth of 0"); + assert_eq!(accept_channel.common_fields.minimum_depth, 0, "Expected minimum depth of 0"); nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), &accept_channel); // nodes[0] --- funding_created --> nodes[1] @@ -321,3 +326,124 @@ fn test_async_commitment_signature_for_peer_disconnect() { }; } } + +fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) { + // Ensures that we can obtain holder signatures for commitment and HTLC transactions + // asynchronously by allowing their retrieval to fail and retrying via + // `ChannelMonitor::signer_unblocked`. + let mut config = test_default_channel_config(); + if anchors { + config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true; + config.manually_accept_inbound_channels = true; + } + + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config), Some(config)]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let closing_node = if remote_commitment { &nodes[1] } else { &nodes[0] }; + let coinbase_tx = Transaction { + version: 2, + lock_time: LockTime::ZERO, + input: vec![TxIn { ..Default::default() }], + output: vec![ + TxOut { + value: Amount::ONE_BTC.to_sat(), + script_pubkey: closing_node.wallet_source.get_change_script().unwrap(), + }, + ], + }; + if anchors { + *nodes[0].fee_estimator.sat_per_kw.lock().unwrap() *= 2; + *nodes[1].fee_estimator.sat_per_kw.lock().unwrap() *= 2; + closing_node.wallet_source.add_utxo(bitcoin::OutPoint { txid: coinbase_tx.txid(), vout: 0 }, coinbase_tx.output[0].value); + } + + // Route an HTLC and set the signer as unavailable. + let (_, _, chan_id, funding_tx) = create_announced_chan_between_nodes(&nodes, 0, 1); + route_payment(&nodes[0], &[&nodes[1]], 1_000_000); + + nodes[0].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &chan_id, false); + + if remote_commitment { + // Make the counterparty broadcast its latest commitment. + nodes[1].node.force_close_broadcasting_latest_txn(&chan_id, &nodes[0].node.get_our_node_id()).unwrap(); + check_added_monitors(&nodes[1], 1); + check_closed_broadcast(&nodes[1], 1, true); + check_closed_event(&nodes[1], 1, ClosureReason::HolderForceClosed, false, &[nodes[0].node.get_our_node_id()], 100_000); + } else { + // We'll connect blocks until the sender has to go onchain to time out the HTLC. + connect_blocks(&nodes[0], TEST_FINAL_CLTV + LATENCY_GRACE_PERIOD_BLOCKS + 1); + + // No transaction should be broadcast since the signer is not available yet. + assert!(nodes[0].tx_broadcaster.txn_broadcast().is_empty()); + assert!(nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events().is_empty()); + + // Mark it as available now, we should see the signed commitment transaction. + nodes[0].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &chan_id, true); + get_monitor!(nodes[0], chan_id).signer_unblocked(nodes[0].tx_broadcaster, nodes[0].fee_estimator, &nodes[0].logger); + } + + let commitment_tx = { + let mut txn = closing_node.tx_broadcaster.txn_broadcast(); + if anchors || remote_commitment { + assert_eq!(txn.len(), 1); + check_spends!(txn[0], funding_tx); + txn.remove(0) + } else { + assert_eq!(txn.len(), 2); + if txn[0].input[0].previous_output.txid == funding_tx.txid() { + check_spends!(txn[0], funding_tx); + check_spends!(txn[1], txn[0]); + txn.remove(0) + } else { + check_spends!(txn[1], funding_tx); + check_spends!(txn[0], txn[1]); + txn.remove(1) + } + } + }; + + // Mark it as unavailable again to now test the HTLC transaction. We'll mine the commitment such + // that the HTLC transaction is retried. + nodes[0].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &chan_id, false); + mine_transaction(&nodes[0], &commitment_tx); + + check_added_monitors(&nodes[0], 1); + check_closed_broadcast(&nodes[0], 1, true); + check_closed_event(&nodes[0], 1, ClosureReason::CommitmentTxConfirmed, false, &[nodes[1].node.get_our_node_id()], 100_000); + + // If the counterparty broadcast its latest commitment, we need to mine enough blocks for the + // HTLC timeout. + if remote_commitment { + connect_blocks(&nodes[0], TEST_FINAL_CLTV); + } + + // No HTLC transaction should be broadcast as the signer is not available yet. + if anchors && !remote_commitment { + handle_bump_htlc_event(&nodes[0], 1); + } + assert!(nodes[0].tx_broadcaster.txn_broadcast().is_empty()); + + // Mark it as available now, we should see the signed HTLC transaction. + nodes[0].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &chan_id, true); + get_monitor!(nodes[0], chan_id).signer_unblocked(nodes[0].tx_broadcaster, nodes[0].fee_estimator, &nodes[0].logger); + + if anchors && !remote_commitment { + handle_bump_htlc_event(&nodes[0], 1); + } + { + let txn = nodes[0].tx_broadcaster.txn_broadcast(); + assert_eq!(txn.len(), 1); + check_spends!(txn[0], commitment_tx, coinbase_tx); + } +} + +#[test] +fn test_async_holder_signatures() { + do_test_async_holder_signatures(false, false); + do_test_async_holder_signatures(false, true); + do_test_async_holder_signatures(true, false); + do_test_async_holder_signatures(true, true); +} diff --git a/lightning/src/ln/blinded_payment_tests.rs b/lightning/src/ln/blinded_payment_tests.rs index 635057deab2..7adac5472b5 100644 --- a/lightning/src/ln/blinded_payment_tests.rs +++ b/lightning/src/ln/blinded_payment_tests.rs @@ -10,7 +10,7 @@ use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; use crate::blinded_path::BlindedPath; use crate::blinded_path::payment::{ForwardNode, ForwardTlvs, PaymentConstraints, PaymentRelay, ReceiveTlvs}; -use crate::events::{HTLCDestination, MessageSendEventsProvider}; +use crate::events::{Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider, PaymentFailureReason}; use crate::ln::PaymentSecret; use crate::ln::channelmanager; use crate::ln::channelmanager::{PaymentId, RecipientOnionFields}; @@ -20,18 +20,22 @@ use crate::ln::msgs; use crate::ln::msgs::ChannelMessageHandler; use crate::ln::onion_utils; use crate::ln::onion_utils::INVALID_ONION_BLINDING; -use crate::ln::outbound_payment::Retry; +use crate::ln::outbound_payment::{Retry, IDEMPOTENCY_TIMEOUT_TICKS}; +use crate::offers::invoice::BlindedPayInfo; use crate::prelude::*; -use crate::routing::router::{PaymentParameters, RouteParameters}; +use crate::routing::router::{Payee, PaymentParameters, RouteParameters}; use crate::util::config::UserConfig; use crate::util::test_utils; -pub fn get_blinded_route_parameters( - amt_msat: u64, payment_secret: PaymentSecret, node_ids: Vec, - channel_upds: &[&msgs::UnsignedChannelUpdate], keys_manager: &test_utils::TestKeysInterface -) -> RouteParameters { +fn blinded_payment_path( + payment_secret: PaymentSecret, intro_node_min_htlc: u64, intro_node_max_htlc: u64, + node_ids: Vec, channel_upds: &[&msgs::UnsignedChannelUpdate], + keys_manager: &test_utils::TestKeysInterface +) -> (BlindedPayInfo, BlindedPath) { let mut intermediate_nodes = Vec::new(); - for (node_id, chan_upd) in node_ids.iter().zip(channel_upds) { + let mut intro_node_min_htlc_opt = Some(intro_node_min_htlc); + let mut intro_node_max_htlc_opt = Some(intro_node_max_htlc); + for (idx, (node_id, chan_upd)) in node_ids.iter().zip(channel_upds).enumerate() { intermediate_nodes.push(ForwardNode { node_id: *node_id, tlvs: ForwardTlvs { @@ -43,28 +47,43 @@ pub fn get_blinded_route_parameters( }, payment_constraints: PaymentConstraints { max_cltv_expiry: u32::max_value(), - htlc_minimum_msat: chan_upd.htlc_minimum_msat, + htlc_minimum_msat: intro_node_min_htlc_opt.take() + .unwrap_or_else(|| channel_upds[idx - 1].htlc_minimum_msat), }, features: BlindedHopFeatures::empty(), }, - htlc_maximum_msat: chan_upd.htlc_maximum_msat, + htlc_maximum_msat: intro_node_max_htlc_opt.take() + .unwrap_or_else(|| channel_upds[idx - 1].htlc_maximum_msat), }); } let payee_tlvs = ReceiveTlvs { payment_secret, payment_constraints: PaymentConstraints { max_cltv_expiry: u32::max_value(), - htlc_minimum_msat: channel_upds.last().unwrap().htlc_minimum_msat, + htlc_minimum_msat: + intro_node_min_htlc_opt.unwrap_or_else(|| channel_upds.last().unwrap().htlc_minimum_msat), }, }; let mut secp_ctx = Secp256k1::new(); - let blinded_path = BlindedPath::new_for_payment( + BlindedPath::new_for_payment( &intermediate_nodes[..], *node_ids.last().unwrap(), payee_tlvs, - channel_upds.last().unwrap().htlc_maximum_msat, keys_manager, &secp_ctx - ).unwrap(); + intro_node_max_htlc_opt.unwrap_or_else(|| channel_upds.last().unwrap().htlc_maximum_msat), + TEST_FINAL_CLTV as u16, keys_manager, &secp_ctx + ).unwrap() +} +pub fn get_blinded_route_parameters( + amt_msat: u64, payment_secret: PaymentSecret, intro_node_min_htlc: u64, intro_node_max_htlc: u64, + node_ids: Vec, channel_upds: &[&msgs::UnsignedChannelUpdate], + keys_manager: &test_utils::TestKeysInterface +) -> RouteParameters { RouteParameters::from_payment_params_and_value( - PaymentParameters::blinded(vec![blinded_path]), amt_msat + PaymentParameters::blinded(vec![ + blinded_payment_path( + payment_secret, intro_node_min_htlc, intro_node_max_htlc, node_ids, channel_upds, + keys_manager + ) + ]), amt_msat ) } @@ -92,7 +111,8 @@ fn do_one_hop_blinded_path(success: bool) { }; 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 + nodes[1].node.get_our_node_id(), payee_tlvs, TEST_FINAL_CLTV as u16, + &chanmon_cfgs[1].keys_manager, &secp_ctx ).unwrap(); let route_params = RouteParameters::from_payment_params_and_value( @@ -133,7 +153,8 @@ fn mpp_to_one_hop_blinded_path() { }, }; let blinded_path = BlindedPath::one_hop_for_payment( - nodes[3].node.get_our_node_id(), payee_tlvs, &chanmon_cfgs[3].keys_manager, &secp_ctx + nodes[3].node.get_our_node_id(), payee_tlvs, TEST_FINAL_CLTV as u16, + &chanmon_cfgs[3].keys_manager, &secp_ctx ).unwrap(); let bolt12_features = @@ -159,6 +180,70 @@ fn mpp_to_one_hop_blinded_path() { claim_payment_along_route(&nodes[0], expected_route, false, payment_preimage); } +#[test] +fn mpp_to_three_hop_blinded_paths() { + let chanmon_cfgs = create_chanmon_cfgs(6); + let node_cfgs = create_node_cfgs(6, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(6, &node_cfgs, &[None, None, None, None, None, None]); + let nodes = create_network(6, &node_cfgs, &node_chanmgrs); + + // Create this network topology so node 0 MPP's over 2 3-hop blinded paths: + // n1 -- n3 + // / \ + // n0 n5 + // \ / + // n2 -- n4 + 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; + let chan_upd_2_4 = create_announced_chan_between_nodes(&nodes, 2, 4).0.contents; + let chan_upd_3_5 = create_announced_chan_between_nodes(&nodes, 3, 5).0.contents; + let chan_upd_4_5 = create_announced_chan_between_nodes(&nodes, 4, 5).0.contents; + + let amt_msat = 15_000_000; + let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[5], Some(amt_msat), None); + let route_params = { + let path_1_params = get_blinded_route_parameters( + amt_msat, payment_secret, 1, 1_0000_0000, vec![ + nodes[1].node.get_our_node_id(), nodes[3].node.get_our_node_id(), + nodes[5].node.get_our_node_id() + ], &[&chan_upd_1_3, &chan_upd_3_5], &chanmon_cfgs[5].keys_manager + ); + let path_2_params = get_blinded_route_parameters( + amt_msat, payment_secret, 1, 1_0000_0000, vec![ + nodes[2].node.get_our_node_id(), nodes[4].node.get_our_node_id(), + nodes[5].node.get_our_node_id() + ], &[&chan_upd_2_4, &chan_upd_4_5], &chanmon_cfgs[5].keys_manager + ); + let pay_params = PaymentParameters::blinded( + vec![ + path_1_params.payment_params.payee.blinded_route_hints()[0].clone(), + path_2_params.payment_params.payee.blinded_route_hints()[0].clone() + ] + ) + .with_bolt12_features(channelmanager::provided_bolt12_invoice_features(&UserConfig::default())) + .unwrap(); + RouteParameters::from_payment_params_and_value(pay_params, 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[5]], &[&nodes[2], &nodes[4], &nodes[5]]]; + 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); +} + enum ForwardCheckFail { // Fail a check on the inbound onion payload. In this case, we underflow when calculating the // outgoing cltv_expiry. @@ -171,63 +256,108 @@ enum ForwardCheckFail { #[test] fn forward_checks_failure() { - do_forward_checks_failure(ForwardCheckFail::InboundOnionCheck); - do_forward_checks_failure(ForwardCheckFail::ForwardPayloadEncodedAsReceive); - do_forward_checks_failure(ForwardCheckFail::OutboundChannelCheck); + do_forward_checks_failure(ForwardCheckFail::InboundOnionCheck, true); + do_forward_checks_failure(ForwardCheckFail::InboundOnionCheck, false); + do_forward_checks_failure(ForwardCheckFail::ForwardPayloadEncodedAsReceive, true); + do_forward_checks_failure(ForwardCheckFail::ForwardPayloadEncodedAsReceive, false); + do_forward_checks_failure(ForwardCheckFail::OutboundChannelCheck, true); + do_forward_checks_failure(ForwardCheckFail::OutboundChannelCheck, false); } -fn do_forward_checks_failure(check: ForwardCheckFail) { +fn do_forward_checks_failure(check: ForwardCheckFail, intro_fails: bool) { // Ensure we'll fail backwards properly if a forwarding check fails on initial update_add // receipt. - let chanmon_cfgs = create_chanmon_cfgs(3); - let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); - let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]); - let mut nodes = create_network(3, &node_cfgs, &node_chanmgrs); + let 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 mut nodes = create_network(4, &node_cfgs, &node_chanmgrs); // We need the session priv to construct a bogus onion packet later. *nodes[0].keys_manager.override_random_bytes.lock().unwrap() = Some([3; 32]); create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); let chan_upd_1_2 = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0).0.contents; + let chan_upd_2_3 = create_announced_chan_between_nodes_with_value(&nodes, 2, 3, 1_000_000, 0).0.contents; let amt_msat = 5000; - let (_, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), None); - let route_params = get_blinded_route_parameters(amt_msat, payment_secret, - nodes.iter().skip(1).map(|n| n.node.get_our_node_id()).collect(), &[&chan_upd_1_2], - &chanmon_cfgs[2].keys_manager); + let (_, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[3], Some(amt_msat), None); + let route_params = get_blinded_route_parameters(amt_msat, payment_secret, 1, 1_0000_0000, + nodes.iter().skip(1).map(|n| n.node.get_our_node_id()).collect(), + &[&chan_upd_1_2, &chan_upd_2_3], &chanmon_cfgs[3].keys_manager); let route = get_route(&nodes[0], &route_params).unwrap(); node_cfgs[0].router.expect_find_route(route_params.clone(), Ok(route.clone())); 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); - let mut events = nodes[0].node.get_and_clear_pending_msg_events(); - assert_eq!(events.len(), 1); - 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); + macro_rules! cause_error { + ($src_node_idx: expr, $target_node_idx: expr, $update_add: expr) => { + match check { + ForwardCheckFail::InboundOnionCheck => { + $update_add.cltv_expiry = 10; // causes outbound CLTV expiry to underflow + }, + ForwardCheckFail::ForwardPayloadEncodedAsReceive => { + 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 cur_height = nodes[0].best_block_info().1; + let (mut onion_payloads, ..) = onion_utils::build_onion_payloads( + &route.paths[0], amt_msat, RecipientOnionFields::spontaneous_empty(), cur_height, &None).unwrap(); + // Remove the receive payload so the blinded forward payload is encoded as a final payload + // (i.e. next_hop_hmac == [0; 32]) + onion_payloads.pop(); + if $target_node_idx + 1 < nodes.len() { + onion_payloads.pop(); + } + $update_add.onion_routing_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &payment_hash).unwrap(); + }, + ForwardCheckFail::OutboundChannelCheck => { + // The intro node will see that the next-hop peer is disconnected and fail the HTLC backwards. + nodes[$src_node_idx].node.peer_disconnected(&nodes[$target_node_idx].node.get_our_node_id()); + } + } + } + } - let mut update_add = &mut payment_event.msgs[0]; - match check { - ForwardCheckFail::InboundOnionCheck => { - update_add.cltv_expiry = 10; // causes outbound CLTV expiry to underflow - }, - ForwardCheckFail::ForwardPayloadEncodedAsReceive => { - 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 cur_height = nodes[0].best_block_info().1; - let (mut onion_payloads, ..) = onion_utils::build_onion_payloads( - &route.paths[0], amt_msat, RecipientOnionFields::spontaneous_empty(), cur_height, &None).unwrap(); - // Remove the receive payload so the blinded forward payload is encoded as a final payload - // (i.e. next_hop_hmac == [0; 32]) - onion_payloads.pop(); - update_add.onion_routing_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &payment_hash).unwrap(); - }, - ForwardCheckFail::OutboundChannelCheck => { - // The intro node will see that the next-hop peer is disconnected and fail the HTLC backwards. - nodes[1].node.peer_disconnected(&nodes[2].node.get_our_node_id()); - }, + let mut updates_0_1 = get_htlc_update_msgs!(nodes[0], nodes[1].node.get_our_node_id()); + let update_add = &mut updates_0_1.update_add_htlcs[0]; + + if intro_fails { + cause_error!(1, 2, update_add); } - nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &payment_event.msgs[0]); + + nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &update_add); check_added_monitors!(nodes[1], 0); - do_commitment_signed_dance(&nodes[1], &nodes[0], &payment_event.commitment_msg, true, true); + do_commitment_signed_dance(&nodes[1], &nodes[0], &updates_0_1.commitment_signed, true, true); + + if intro_fails { + let mut 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]); + do_commitment_signed_dance(&nodes[0], &nodes[1], &updates.commitment_signed, false, false); + expect_payment_failed_conditions(&nodes[0], payment_hash, false, + PaymentFailedConditions::new().expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 32])); + return + } + + expect_pending_htlcs_forwardable!(nodes[1]); + check_added_monitors!(nodes[1], 1); + + let mut updates_1_2 = get_htlc_update_msgs!(nodes[1], nodes[2].node.get_our_node_id()); + let mut update_add = &mut updates_1_2.update_add_htlcs[0]; + + cause_error!(2, 3, update_add); + + nodes[2].node.handle_update_add_htlc(&nodes[1].node.get_our_node_id(), &update_add); + check_added_monitors!(nodes[2], 0); + do_commitment_signed_dance(&nodes[2], &nodes[1], &updates_1_2.commitment_signed, true, true); + + let mut updates = get_htlc_update_msgs!(nodes[2], nodes[1].node.get_our_node_id()); + let update_malformed = &mut updates.update_fail_malformed_htlcs[0]; + assert_eq!(update_malformed.failure_code, INVALID_ONION_BLINDING); + assert_eq!(update_malformed.sha256_of_onion, [0; 32]); + + // Ensure the intro node will properly blind the error if its downstream node failed to do so. + update_malformed.sha256_of_onion = [1; 32]; + update_malformed.failure_code = INVALID_ONION_BLINDING ^ 1; + nodes[1].node.handle_update_fail_malformed_htlc(&nodes[2].node.get_our_node_id(), update_malformed); + do_commitment_signed_dance(&nodes[1], &nodes[2], &updates.commitment_signed, true, false); let mut 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]); @@ -249,7 +379,7 @@ fn failed_backwards_to_intro_node() { let amt_msat = 5000; let (_, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), None); - let route_params = get_blinded_route_parameters(amt_msat, payment_secret, + let route_params = get_blinded_route_parameters(amt_msat, payment_secret, 1, 1_0000_0000, nodes.iter().skip(1).map(|n| n.node.get_our_node_id()).collect(), &[&chan_upd_1_2], &chanmon_cfgs[2].keys_manager); @@ -281,7 +411,12 @@ fn failed_backwards_to_intro_node() { let mut updates = get_htlc_update_msgs!(nodes[2], nodes[1].node.get_our_node_id()); let mut update_malformed = &mut updates.update_fail_malformed_htlcs[0]; - // Ensure the final hop does not correctly blind their error. + // Check that the final node encodes its failure correctly. + assert_eq!(update_malformed.failure_code, INVALID_ONION_BLINDING); + assert_eq!(update_malformed.sha256_of_onion, [0; 32]); + + // Modify such the final hop does not correctly blind their error so we can ensure the intro node + // converts it to the correct error. update_malformed.sha256_of_onion = [1; 32]; nodes[1].node.handle_update_fail_malformed_htlc(&nodes[2].node.get_our_node_id(), update_malformed); do_commitment_signed_dance(&nodes[1], &nodes[2], &updates.commitment_signed, true, false); @@ -300,26 +435,32 @@ enum ProcessPendingHTLCsCheck { #[test] fn forward_fail_in_process_pending_htlc_fwds() { - do_forward_fail_in_process_pending_htlc_fwds(ProcessPendingHTLCsCheck::FwdPeerDisconnected); - do_forward_fail_in_process_pending_htlc_fwds(ProcessPendingHTLCsCheck::FwdChannelClosed); + do_forward_fail_in_process_pending_htlc_fwds(ProcessPendingHTLCsCheck::FwdPeerDisconnected, true); + do_forward_fail_in_process_pending_htlc_fwds(ProcessPendingHTLCsCheck::FwdPeerDisconnected, false); + do_forward_fail_in_process_pending_htlc_fwds(ProcessPendingHTLCsCheck::FwdChannelClosed, true); + do_forward_fail_in_process_pending_htlc_fwds(ProcessPendingHTLCsCheck::FwdChannelClosed, false); } -fn do_forward_fail_in_process_pending_htlc_fwds(check: ProcessPendingHTLCsCheck) { +fn do_forward_fail_in_process_pending_htlc_fwds(check: ProcessPendingHTLCsCheck, intro_fails: bool) { // Ensure the intro node will error backwards properly if the HTLC fails in // process_pending_htlc_forwards. - let chanmon_cfgs = create_chanmon_cfgs(3); - let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); - let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]); - let mut nodes = create_network(3, &node_cfgs, &node_chanmgrs); + let 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 mut nodes = create_network(4, &node_cfgs, &node_chanmgrs); create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); - let (chan_upd_1_2, channel_id) = { + let (chan_upd_1_2, chan_id_1_2) = { let chan = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0); (chan.0.contents, chan.2) }; + let (chan_upd_2_3, chan_id_2_3) = { + let chan = create_announced_chan_between_nodes_with_value(&nodes, 2, 3, 1_000_000, 0); + (chan.0.contents, chan.2) + }; let amt_msat = 5000; let (_, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), None); - let route_params = get_blinded_route_parameters(amt_msat, payment_secret, - nodes.iter().skip(1).map(|n| n.node.get_our_node_id()).collect(), &[&chan_upd_1_2], + let route_params = get_blinded_route_parameters(amt_msat, payment_secret, 1, 1_0000_0000, + nodes.iter().skip(1).map(|n| n.node.get_our_node_id()).collect(), &[&chan_upd_1_2, &chan_upd_2_3], &chanmon_cfgs[2].keys_manager); nodes[0].node.send_payment(payment_hash, RecipientOnionFields::spontaneous_empty(), PaymentId(payment_hash.0), route_params, Retry::Attempts(0)).unwrap(); @@ -334,49 +475,90 @@ fn do_forward_fail_in_process_pending_htlc_fwds(check: ProcessPendingHTLCsCheck) check_added_monitors!(nodes[1], 0); do_commitment_signed_dance(&nodes[1], &nodes[0], &payment_event.commitment_msg, false, false); - match check { - ProcessPendingHTLCsCheck::FwdPeerDisconnected => { - // Disconnect the next-hop peer so when we go to forward in process_pending_htlc_forwards, the - // intro node will error backwards. - nodes[1].node.peer_disconnected(&nodes[2].node.get_our_node_id()); - expect_pending_htlcs_forwardable!(nodes[1]); - expect_pending_htlcs_forwardable_and_htlc_handling_failed_ignore!(nodes[1], - vec![HTLCDestination::NextHopChannel { node_id: Some(nodes[2].node.get_our_node_id()), channel_id }]); - }, - ProcessPendingHTLCsCheck::FwdChannelClosed => { - // Force close the next-hop channel so when we go to forward in process_pending_htlc_forwards, - // the intro node will error backwards. - nodes[1].node.force_close_broadcasting_latest_txn(&channel_id, &nodes[2].node.get_our_node_id()).unwrap(); - let events = nodes[1].node.get_and_clear_pending_events(); - match events[0] { - crate::events::Event::PendingHTLCsForwardable { .. } => {}, - _ => panic!("Unexpected event {:?}", events), - }; - match events[1] { - crate::events::Event::ChannelClosed { .. } => {}, - _ => panic!("Unexpected event {:?}", events), + macro_rules! cause_error { + ($prev_node: expr, $curr_node: expr, $next_node: expr, $failed_chan_id: expr, $failed_scid: expr) => { + match check { + ProcessPendingHTLCsCheck::FwdPeerDisconnected => { + // Disconnect the next-hop peer so when we go to forward in process_pending_htlc_forwards, the + // intro node will error backwards. + $curr_node.node.peer_disconnected(&$next_node.node.get_our_node_id()); + expect_pending_htlcs_forwardable!($curr_node); + expect_pending_htlcs_forwardable_and_htlc_handling_failed_ignore!($curr_node, + vec![HTLCDestination::NextHopChannel { node_id: Some($next_node.node.get_our_node_id()), channel_id: $failed_chan_id }]); + }, + ProcessPendingHTLCsCheck::FwdChannelClosed => { + // Force close the next-hop channel so when we go to forward in process_pending_htlc_forwards, + // the intro node will error backwards. + $curr_node.node.force_close_broadcasting_latest_txn(&$failed_chan_id, &$next_node.node.get_our_node_id()).unwrap(); + let events = $curr_node.node.get_and_clear_pending_events(); + match events[0] { + crate::events::Event::PendingHTLCsForwardable { .. } => {}, + _ => panic!("Unexpected event {:?}", events), + }; + match events[1] { + crate::events::Event::ChannelClosed { .. } => {}, + _ => panic!("Unexpected event {:?}", events), + } + + $curr_node.node.process_pending_htlc_forwards(); + expect_pending_htlcs_forwardable_and_htlc_handling_failed_ignore!($curr_node, + vec![HTLCDestination::UnknownNextHop { requested_forward_scid: $failed_scid }]); + check_closed_broadcast(&$curr_node, 1, true); + check_added_monitors!($curr_node, 1); + $curr_node.node.process_pending_htlc_forwards(); + }, } + } + } - nodes[1].node.process_pending_htlc_forwards(); - expect_pending_htlcs_forwardable_and_htlc_handling_failed_ignore!(nodes[1], - vec![HTLCDestination::UnknownNextHop { requested_forward_scid: chan_upd_1_2.short_channel_id }]); - check_closed_broadcast(&nodes[1], 1, true); - check_added_monitors!(nodes[1], 1); - nodes[1].node.process_pending_htlc_forwards(); - }, + if intro_fails { + cause_error!(nodes[0], nodes[1], nodes[2], chan_id_1_2, chan_upd_1_2.short_channel_id); + let mut 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[1], 1); + do_commitment_signed_dance(&nodes[0], &nodes[1], &updates.commitment_signed, false, false); + + expect_payment_failed_conditions(&nodes[0], payment_hash, false, + PaymentFailedConditions::new().expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 32])); + return } + expect_pending_htlcs_forwardable!(nodes[1]); + check_added_monitors!(nodes[1], 1); + + let mut updates_1_2 = get_htlc_update_msgs!(nodes[1], nodes[2].node.get_our_node_id()); + let mut update_add = &mut updates_1_2.update_add_htlcs[0]; + nodes[2].node.handle_update_add_htlc(&nodes[1].node.get_our_node_id(), &update_add); + check_added_monitors!(nodes[2], 0); + do_commitment_signed_dance(&nodes[2], &nodes[1], &updates_1_2.commitment_signed, true, true); + + cause_error!(nodes[1], nodes[2], nodes[3], chan_id_2_3, chan_upd_2_3.short_channel_id); + check_added_monitors!(nodes[2], 1); + + let mut updates = get_htlc_update_msgs!(nodes[2], nodes[1].node.get_our_node_id()); + let update_malformed = &mut updates.update_fail_malformed_htlcs[0]; + assert_eq!(update_malformed.failure_code, INVALID_ONION_BLINDING); + assert_eq!(update_malformed.sha256_of_onion, [0; 32]); + + // Ensure the intro node will properly blind the error if its downstream node failed to do so. + update_malformed.sha256_of_onion = [1; 32]; + update_malformed.failure_code = INVALID_ONION_BLINDING ^ 1; + nodes[1].node.handle_update_fail_malformed_htlc(&nodes[2].node.get_our_node_id(), update_malformed); + do_commitment_signed_dance(&nodes[1], &nodes[2], &updates.commitment_signed, true, false); + let mut 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[1], 1); do_commitment_signed_dance(&nodes[0], &nodes[1], &updates.commitment_signed, false, false); - expect_payment_failed_conditions(&nodes[0], payment_hash, false, PaymentFailedConditions::new().expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 32])); } #[test] fn blinded_intercept_payment() { + do_blinded_intercept_payment(true); + do_blinded_intercept_payment(false); +} +fn do_blinded_intercept_payment(intercept_node_fails: bool) { let chanmon_cfgs = create_chanmon_cfgs(3); let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); let mut intercept_forwards_config = test_default_channel_config(); @@ -384,14 +566,17 @@ fn blinded_intercept_payment() { let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, Some(intercept_forwards_config), None]); let nodes = create_network(3, &node_cfgs, &node_chanmgrs); create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); - let chan_upd = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0).0.contents; + let (channel_id, chan_upd) = { + let chan = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0); + (chan.2, chan.0.contents) + }; let amt_msat = 5000; - let (_, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), None); + let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), None); let intercept_scid = nodes[1].node.get_intercept_scid(); let mut intercept_chan_upd = chan_upd; intercept_chan_upd.short_channel_id = intercept_scid; - let route_params = get_blinded_route_parameters(amt_msat, payment_secret, + let route_params = get_blinded_route_parameters(amt_msat, payment_secret, 1, 1_0000_0000, nodes.iter().skip(1).map(|n| n.node.get_our_node_id()).collect(), &[&intercept_chan_upd], &chanmon_cfgs[2].keys_manager); @@ -408,27 +593,593 @@ fn blinded_intercept_payment() { let events = nodes[1].node.get_and_clear_pending_events(); assert_eq!(events.len(), 1); - let intercept_id = match events[0] { + let (intercept_id, expected_outbound_amount_msat) = match events[0] { crate::events::Event::HTLCIntercepted { intercept_id, payment_hash: pmt_hash, - requested_next_hop_scid: short_channel_id, .. + requested_next_hop_scid: short_channel_id, expected_outbound_amount_msat, .. } => { assert_eq!(pmt_hash, payment_hash); assert_eq!(short_channel_id, intercept_scid); - intercept_id + (intercept_id, expected_outbound_amount_msat) }, _ => panic!() }; - nodes[1].node.fail_intercepted_htlc(intercept_id).unwrap(); - expect_pending_htlcs_forwardable_and_htlc_handling_failed_ignore!(nodes[1], vec![HTLCDestination::UnknownNextHop { requested_forward_scid: intercept_scid }]); - nodes[1].node.process_pending_htlc_forwards(); - let update_fail = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()); + if intercept_node_fails { + nodes[1].node.fail_intercepted_htlc(intercept_id).unwrap(); + expect_pending_htlcs_forwardable_and_htlc_handling_failed_ignore!(nodes[1], vec![HTLCDestination::UnknownNextHop { requested_forward_scid: intercept_scid }]); + nodes[1].node.process_pending_htlc_forwards(); + let update_fail = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()); + check_added_monitors!(&nodes[1], 1); + assert!(update_fail.update_fail_htlcs.len() == 1); + let fail_msg = update_fail.update_fail_htlcs[0].clone(); + nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &fail_msg); + commitment_signed_dance!(nodes[0], nodes[1], update_fail.commitment_signed, false); + expect_payment_failed_conditions(&nodes[0], payment_hash, false, + PaymentFailedConditions::new().expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 32])); + return + } + + nodes[1].node.forward_intercepted_htlc(intercept_id, &channel_id, nodes[2].node.get_our_node_id(), expected_outbound_amount_msat).unwrap(); + expect_pending_htlcs_forwardable!(nodes[1]); + + let payment_event = { + { + let mut added_monitors = nodes[1].chain_monitor.added_monitors.lock().unwrap(); + assert_eq!(added_monitors.len(), 1); + added_monitors.clear(); + } + let mut events = nodes[1].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + SendEvent::from_event(events.remove(0)) + }; + nodes[2].node.handle_update_add_htlc(&nodes[1].node.get_our_node_id(), &payment_event.msgs[0]); + commitment_signed_dance!(nodes[2], nodes[1], &payment_event.commitment_msg, false, true); + expect_pending_htlcs_forwardable!(nodes[2]); + + expect_payment_claimable!(&nodes[2], payment_hash, payment_secret, amt_msat, None, nodes[2].node.get_our_node_id()); + do_claim_payment_along_route(&nodes[0], &vec!(&vec!(&nodes[1], &nodes[2])[..]), false, payment_preimage); + expect_payment_sent(&nodes[0], payment_preimage, Some(Some(1000)), true, true); +} + +#[test] +fn two_hop_blinded_path_success() { + 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_with_value(&nodes, 0, 1, 1_000_000, 0); + let chan_upd_1_2 = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0).0.contents; + + let amt_msat = 5000; + let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), None); + let route_params = get_blinded_route_parameters(amt_msat, payment_secret, 1, 1_0000_0000, + nodes.iter().skip(1).map(|n| n.node.get_our_node_id()).collect(), &[&chan_upd_1_2], + &chanmon_cfgs[2].keys_manager); + + 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], &nodes[2]]], amt_msat, payment_hash, payment_secret); + claim_payment(&nodes[0], &[&nodes[1], &nodes[2]], payment_preimage); +} + +#[test] +fn three_hop_blinded_path_success() { + let chanmon_cfgs = create_chanmon_cfgs(5); + let node_cfgs = create_node_cfgs(5, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(5, &node_cfgs, &[None, None, None, None, None]); + let mut nodes = create_network(5, &node_cfgs, &node_chanmgrs); + create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); + create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0); + let chan_upd_2_3 = create_announced_chan_between_nodes_with_value(&nodes, 2, 3, 1_000_000, 0).0.contents; + let chan_upd_3_4 = create_announced_chan_between_nodes_with_value(&nodes, 3, 4, 1_000_000, 0).0.contents; + + // Get all our nodes onto the same height so payments don't fail for CLTV violations. + connect_blocks(&nodes[0], nodes[4].best_block_info().1 - nodes[0].best_block_info().1); + connect_blocks(&nodes[1], nodes[4].best_block_info().1 - nodes[1].best_block_info().1); + connect_blocks(&nodes[2], nodes[4].best_block_info().1 - nodes[2].best_block_info().1); + assert_eq!(nodes[4].best_block_info().1, nodes[3].best_block_info().1); + + let amt_msat = 5000; + let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[4], Some(amt_msat), None); + let route_params = get_blinded_route_parameters(amt_msat, payment_secret, 1, 1_0000_0000, + nodes.iter().skip(2).map(|n| n.node.get_our_node_id()).collect(), + &[&chan_upd_2_3, &chan_upd_3_4], &chanmon_cfgs[4].keys_manager); + + 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], &nodes[2], &nodes[3], &nodes[4]]], amt_msat, payment_hash, payment_secret); + claim_payment(&nodes[0], &[&nodes[1], &nodes[2], &nodes[3], &nodes[4]], payment_preimage); +} + +#[test] +fn three_hop_blinded_path_fail() { + // Test that an intermediate blinded forwarding node gets failed back to with + // malformed and also fails back themselves with malformed. + 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 mut nodes = create_network(4, &node_cfgs, &node_chanmgrs); + create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); + let chan_upd_1_2 = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0).0.contents; + let chan_upd_2_3 = create_announced_chan_between_nodes_with_value(&nodes, 2, 3, 1_000_000, 0).0.contents; + + let amt_msat = 5000; + let (_, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[3], Some(amt_msat), None); + let route_params = get_blinded_route_parameters(amt_msat, payment_secret, 1, 1_0000_0000, + nodes.iter().skip(1).map(|n| n.node.get_our_node_id()).collect(), + &[&chan_upd_1_2, &chan_upd_2_3], &chanmon_cfgs[3].keys_manager); + + 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], &nodes[2], &nodes[3]]], amt_msat, payment_hash, payment_secret); + + nodes[3].node.fail_htlc_backwards(&payment_hash); + expect_pending_htlcs_forwardable_conditions( + nodes[3].node.get_and_clear_pending_events(), &[HTLCDestination::FailedPayment { payment_hash }] + ); + nodes[3].node.process_pending_htlc_forwards(); + check_added_monitors!(nodes[3], 1); + + let updates_3_2 = get_htlc_update_msgs!(nodes[3], nodes[2].node.get_our_node_id()); + assert_eq!(updates_3_2.update_fail_malformed_htlcs.len(), 1); + let update_malformed = &updates_3_2.update_fail_malformed_htlcs[0]; + assert_eq!(update_malformed.sha256_of_onion, [0; 32]); + assert_eq!(update_malformed.failure_code, INVALID_ONION_BLINDING); + nodes[2].node.handle_update_fail_malformed_htlc(&nodes[3].node.get_our_node_id(), update_malformed); + do_commitment_signed_dance(&nodes[2], &nodes[3], &updates_3_2.commitment_signed, true, false); + + let updates_2_1 = get_htlc_update_msgs!(nodes[2], nodes[1].node.get_our_node_id()); + assert_eq!(updates_2_1.update_fail_malformed_htlcs.len(), 1); + let update_malformed = &updates_2_1.update_fail_malformed_htlcs[0]; + assert_eq!(update_malformed.sha256_of_onion, [0; 32]); + assert_eq!(update_malformed.failure_code, INVALID_ONION_BLINDING); + nodes[1].node.handle_update_fail_malformed_htlc(&nodes[2].node.get_our_node_id(), update_malformed); + do_commitment_signed_dance(&nodes[1], &nodes[2], &updates_2_1.commitment_signed, true, false); + + let updates_1_0 = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()); + assert_eq!(updates_1_0.update_fail_htlcs.len(), 1); + nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &updates_1_0.update_fail_htlcs[0]); + do_commitment_signed_dance(&nodes[0], &nodes[1], &updates_1_0.commitment_signed, false, false); + expect_payment_failed_conditions(&nodes[0], payment_hash, false, + PaymentFailedConditions::new().expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 32])); +} + +#[derive(PartialEq)] +enum ReceiveCheckFail { + // The recipient fails the payment upon `PaymentClaimable`. + RecipientFail, + // Failure to decode the recipient's onion payload. + OnionDecodeFail, + // The incoming HTLC did not satisfy our requirements; in this case it underpaid us according to + // the expected receive amount in the onion. + ReceiveRequirements, + // The incoming HTLC errors when added to the Channel, in this case due to the HTLC being + // delivered out-of-order with a shutdown message. + ChannelCheck, + // The HTLC is successfully added to the inbound channel but fails receive checks in + // process_pending_htlc_forwards. + ProcessPendingHTLCsCheck, + // The HTLC violates the `PaymentConstraints` contained within the receiver's encrypted payload. + PaymentConstraints, +} + +#[test] +fn multi_hop_receiver_fail() { + do_multi_hop_receiver_fail(ReceiveCheckFail::RecipientFail); + do_multi_hop_receiver_fail(ReceiveCheckFail::OnionDecodeFail); + do_multi_hop_receiver_fail(ReceiveCheckFail::ReceiveRequirements); + do_multi_hop_receiver_fail(ReceiveCheckFail::ChannelCheck); + do_multi_hop_receiver_fail(ReceiveCheckFail::ProcessPendingHTLCsCheck); + do_multi_hop_receiver_fail(ReceiveCheckFail::PaymentConstraints); +} + +fn do_multi_hop_receiver_fail(check: ReceiveCheckFail) { + // Test that the receiver to a multihop blinded path fails back correctly. + 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); + // We need the session priv to construct an invalid onion packet later. + let session_priv = [3; 32]; + *nodes[0].keys_manager.override_random_bytes.lock().unwrap() = Some(session_priv); + create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); + let (chan_upd_1_2, chan_id_1_2) = { + let (chan_upd, _, channel_id, ..) = create_announced_chan_between_nodes_with_value( + &nodes, 1, 2, 1_000_000, 0 + ); + (chan_upd.contents, channel_id) + }; + + let amt_msat = 5000; + let excess_final_cltv_delta_opt = if check == ReceiveCheckFail::ProcessPendingHTLCsCheck { + // Set the final CLTV expiry too low to trigger the failure in process_pending_htlc_forwards. + Some(TEST_FINAL_CLTV as u16 - 2) + } else { None }; + let (_, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), excess_final_cltv_delta_opt); + let mut route_params = get_blinded_route_parameters(amt_msat, payment_secret, 1, 1_0000_0000, + nodes.iter().skip(1).map(|n| n.node.get_our_node_id()).collect(), &[&chan_upd_1_2], + &chanmon_cfgs[2].keys_manager); + + let route = if check == ReceiveCheckFail::ProcessPendingHTLCsCheck { + let mut route = get_route(&nodes[0], &route_params).unwrap(); + // Set the final CLTV expiry too low to trigger the failure in process_pending_htlc_forwards. + route.paths[0].hops.last_mut().map(|h| h.cltv_expiry_delta += excess_final_cltv_delta_opt.unwrap() as u32); + route.paths[0].blinded_tail.as_mut().map(|bt| bt.excess_final_cltv_expiry_delta = excess_final_cltv_delta_opt.unwrap() as u32); + route + } else if check == ReceiveCheckFail::PaymentConstraints { + // Create a blinded path where the receiver's encrypted payload has an htlc_minimum_msat that is + // violated by `amt_msat`, and stick it in the route_params without changing the corresponding + // BlindedPayInfo (to ensure pathfinding still succeeds). + let high_htlc_min_bp = { + let mut high_htlc_minimum_upd = chan_upd_1_2.clone(); + high_htlc_minimum_upd.htlc_minimum_msat = amt_msat + 1000; + let high_htlc_min_params = get_blinded_route_parameters(amt_msat, payment_secret, 1, 1_0000_0000, + nodes.iter().skip(1).map(|n| n.node.get_our_node_id()).collect(), &[&high_htlc_minimum_upd], + &chanmon_cfgs[2].keys_manager); + if let Payee::Blinded { route_hints, .. } = high_htlc_min_params.payment_params.payee { + route_hints[0].1.clone() + } else { panic!() } + }; + if let Payee::Blinded { ref mut route_hints, .. } = route_params.payment_params.payee { + route_hints[0].1 = high_htlc_min_bp; + } else { panic!() } + find_route(&nodes[0], &route_params).unwrap() + } else { + find_route(&nodes[0], &route_params).unwrap() + }; + node_cfgs[0].router.expect_find_route(route_params.clone(), Ok(route.clone())); + 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); + + let mut payment_event_0_1 = { + let mut events = nodes[0].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + let ev = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events); + SendEvent::from_event(ev) + }; + nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &payment_event_0_1.msgs[0]); + check_added_monitors!(nodes[1], 0); + do_commitment_signed_dance(&nodes[1], &nodes[0], &payment_event_0_1.commitment_msg, false, false); + expect_pending_htlcs_forwardable!(nodes[1]); check_added_monitors!(&nodes[1], 1); - assert!(update_fail.update_fail_htlcs.len() == 1); - let fail_msg = update_fail.update_fail_htlcs[0].clone(); - nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &fail_msg); - commitment_signed_dance!(nodes[0], nodes[1], update_fail.commitment_signed, false); + + let mut payment_event_1_2 = { + let mut events = nodes[1].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + let ev = remove_first_msg_event_to_node(&nodes[2].node.get_our_node_id(), &mut events); + SendEvent::from_event(ev) + }; + + match check { + ReceiveCheckFail::RecipientFail => { + nodes[2].node.handle_update_add_htlc(&nodes[1].node.get_our_node_id(), &payment_event_1_2.msgs[0]); + check_added_monitors!(nodes[2], 0); + do_commitment_signed_dance(&nodes[2], &nodes[1], &payment_event_1_2.commitment_msg, true, true); + expect_pending_htlcs_forwardable!(nodes[2]); + check_payment_claimable( + &nodes[2].node.get_and_clear_pending_events()[0], payment_hash, payment_secret, amt_msat, + None, nodes[2].node.get_our_node_id() + ); + nodes[2].node.fail_htlc_backwards(&payment_hash); + expect_pending_htlcs_forwardable_conditions( + nodes[2].node.get_and_clear_pending_events(), &[HTLCDestination::FailedPayment { payment_hash }] + ); + nodes[2].node.process_pending_htlc_forwards(); + check_added_monitors!(nodes[2], 1); + }, + ReceiveCheckFail::OnionDecodeFail => { + let session_priv = SecretKey::from_slice(&session_priv).unwrap(); + let mut onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap(); + let cur_height = nodes[0].best_block_info().1; + let (mut onion_payloads, ..) = onion_utils::build_onion_payloads( + &route.paths[0], amt_msat, RecipientOnionFields::spontaneous_empty(), cur_height, &None).unwrap(); + + let update_add = &mut payment_event_1_2.msgs[0]; + onion_payloads.last_mut().map(|p| { + if let msgs::OutboundOnionPayload::BlindedReceive { ref mut intro_node_blinding_point, .. } = p { + // The receiver should error if both the update_add blinding_point and the + // intro_node_blinding_point are set. + assert!(intro_node_blinding_point.is_none() && update_add.blinding_point.is_some()); + *intro_node_blinding_point = Some(PublicKey::from_slice(&[2; 33]).unwrap()); + } else { panic!() } + }); + update_add.onion_routing_packet = onion_utils::construct_onion_packet( + vec![onion_payloads.pop().unwrap()], vec![onion_keys.pop().unwrap()], [0; 32], + &payment_hash + ).unwrap(); + nodes[2].node.handle_update_add_htlc(&nodes[1].node.get_our_node_id(), update_add); + check_added_monitors!(nodes[2], 0); + do_commitment_signed_dance(&nodes[2], &nodes[1], &payment_event_1_2.commitment_msg, true, true); + }, + ReceiveCheckFail::ReceiveRequirements => { + let update_add = &mut payment_event_1_2.msgs[0]; + update_add.amount_msat -= 1; + nodes[2].node.handle_update_add_htlc(&nodes[1].node.get_our_node_id(), update_add); + check_added_monitors!(nodes[2], 0); + do_commitment_signed_dance(&nodes[2], &nodes[1], &payment_event_1_2.commitment_msg, true, true); + }, + ReceiveCheckFail::ChannelCheck => { + nodes[2].node.close_channel(&chan_id_1_2, &nodes[1].node.get_our_node_id()).unwrap(); + let node_2_shutdown = get_event_msg!(nodes[2], MessageSendEvent::SendShutdown, nodes[1].node.get_our_node_id()); + nodes[1].node.handle_shutdown(&nodes[2].node.get_our_node_id(), &node_2_shutdown); + let node_1_shutdown = get_event_msg!(nodes[1], MessageSendEvent::SendShutdown, nodes[2].node.get_our_node_id()); + + nodes[2].node.handle_update_add_htlc(&nodes[1].node.get_our_node_id(), &payment_event_1_2.msgs[0]); + nodes[2].node.handle_commitment_signed(&nodes[1].node.get_our_node_id(), &payment_event_1_2.commitment_msg); + check_added_monitors!(nodes[2], 1); + + nodes[2].node.handle_shutdown(&nodes[1].node.get_our_node_id(), &node_1_shutdown); + commitment_signed_dance!(nodes[2], nodes[1], (), false, true, false, false); + }, + ReceiveCheckFail::ProcessPendingHTLCsCheck => { + assert_eq!(payment_event_1_2.msgs[0].cltv_expiry, nodes[0].best_block_info().1 + 1 + excess_final_cltv_delta_opt.unwrap() as u32 + TEST_FINAL_CLTV); + nodes[2].node.handle_update_add_htlc(&nodes[1].node.get_our_node_id(), &payment_event_1_2.msgs[0]); + check_added_monitors!(nodes[2], 0); + do_commitment_signed_dance(&nodes[2], &nodes[1], &payment_event_1_2.commitment_msg, true, true); + expect_pending_htlcs_forwardable!(nodes[2]); + expect_pending_htlcs_forwardable_and_htlc_handling_failed_ignore!(nodes[2], + vec![HTLCDestination::FailedPayment { payment_hash }]); + check_added_monitors!(nodes[2], 1); + }, + ReceiveCheckFail::PaymentConstraints => { + nodes[2].node.handle_update_add_htlc(&nodes[1].node.get_our_node_id(), &payment_event_1_2.msgs[0]); + check_added_monitors!(nodes[2], 0); + do_commitment_signed_dance(&nodes[2], &nodes[1], &payment_event_1_2.commitment_msg, true, true); + } + } + + let updates_2_1 = get_htlc_update_msgs!(nodes[2], nodes[1].node.get_our_node_id()); + assert_eq!(updates_2_1.update_fail_malformed_htlcs.len(), 1); + let update_malformed = &updates_2_1.update_fail_malformed_htlcs[0]; + assert_eq!(update_malformed.sha256_of_onion, [0; 32]); + assert_eq!(update_malformed.failure_code, INVALID_ONION_BLINDING); + nodes[1].node.handle_update_fail_malformed_htlc(&nodes[2].node.get_our_node_id(), update_malformed); + do_commitment_signed_dance(&nodes[1], &nodes[2], &updates_2_1.commitment_signed, true, false); + + let updates_1_0 = if check == ReceiveCheckFail::ChannelCheck { + let events = nodes[1].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 2); + events.into_iter().find_map(|ev| { + match ev { + MessageSendEvent:: UpdateHTLCs { node_id, updates } => { + assert_eq!(node_id, nodes[0].node.get_our_node_id()); + return Some(updates) + }, + MessageSendEvent::SendClosingSigned { .. } => None, + _ => panic!() + } + }).unwrap() + } else { get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()) }; + assert_eq!(updates_1_0.update_fail_htlcs.len(), 1); + nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &updates_1_0.update_fail_htlcs[0]); + do_commitment_signed_dance(&nodes[0], &nodes[1], &updates_1_0.commitment_signed, false, false); expect_payment_failed_conditions(&nodes[0], payment_hash, false, PaymentFailedConditions::new().expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 32])); } + +#[test] +fn blinded_path_retries() { + let chanmon_cfgs = create_chanmon_cfgs(4); + // Make one blinded path's fees slightly higher so they are tried in a deterministic order. + let mut higher_fee_chan_cfg = test_default_channel_config(); + higher_fee_chan_cfg.channel_config.forwarding_fee_base_msat += 1; + let node_cfgs = create_node_cfgs(4, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(4, &node_cfgs, &[None, None, Some(higher_fee_chan_cfg), None]); + let mut nodes = create_network(4, &node_cfgs, &node_chanmgrs); + + // Create this network topology so nodes[0] has a blinded route hint to retry over. + // n1 + // / \ + // n0 n3 + // \ / + // n2 + create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); + create_announced_chan_between_nodes_with_value(&nodes, 0, 2, 1_000_000, 0); + let chan_1_3 = create_announced_chan_between_nodes_with_value(&nodes, 1, 3, 1_000_000, 0); + let chan_2_3 = create_announced_chan_between_nodes_with_value(&nodes, 2, 3, 1_000_000, 0); + + let amt_msat = 5000; + let (_, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[3], Some(amt_msat), None); + let route_params = { + let pay_params = PaymentParameters::blinded( + vec![ + blinded_payment_path(payment_secret, 1, 1_0000_0000, + vec![nodes[1].node.get_our_node_id(), nodes[3].node.get_our_node_id()], &[&chan_1_3.0.contents], + &chanmon_cfgs[3].keys_manager + ), + blinded_payment_path(payment_secret, 1, 1_0000_0000, + vec![nodes[2].node.get_our_node_id(), nodes[3].node.get_our_node_id()], &[&chan_2_3.0.contents], + &chanmon_cfgs[3].keys_manager + ), + ] + ) + .with_bolt12_features(channelmanager::provided_bolt12_invoice_features(&UserConfig::default())) + .unwrap(); + RouteParameters::from_payment_params_and_value(pay_params, amt_msat) + }; + + nodes[0].node.send_payment(payment_hash, RecipientOnionFields::spontaneous_empty(), PaymentId(payment_hash.0), route_params.clone(), Retry::Attempts(2)).unwrap(); + check_added_monitors(&nodes[0], 1); + pass_along_route(&nodes[0], &[&[&nodes[1], &nodes[3]]], amt_msat, payment_hash, payment_secret); + + macro_rules! fail_payment_back { + ($intro_node: expr) => { + nodes[3].node.fail_htlc_backwards(&payment_hash); + expect_pending_htlcs_forwardable_conditions( + nodes[3].node.get_and_clear_pending_events(), &[HTLCDestination::FailedPayment { payment_hash }] + ); + nodes[3].node.process_pending_htlc_forwards(); + check_added_monitors!(nodes[3], 1); + + let updates = get_htlc_update_msgs!(nodes[3], $intro_node.node.get_our_node_id()); + assert_eq!(updates.update_fail_malformed_htlcs.len(), 1); + let update_malformed = &updates.update_fail_malformed_htlcs[0]; + assert_eq!(update_malformed.sha256_of_onion, [0; 32]); + assert_eq!(update_malformed.failure_code, INVALID_ONION_BLINDING); + $intro_node.node.handle_update_fail_malformed_htlc(&nodes[3].node.get_our_node_id(), update_malformed); + do_commitment_signed_dance(&$intro_node, &nodes[3], &updates.commitment_signed, true, false); + + let updates = get_htlc_update_msgs!($intro_node, nodes[0].node.get_our_node_id()); + assert_eq!(updates.update_fail_htlcs.len(), 1); + nodes[0].node.handle_update_fail_htlc(&$intro_node.node.get_our_node_id(), &updates.update_fail_htlcs[0]); + do_commitment_signed_dance(&nodes[0], &$intro_node, &updates.commitment_signed, false, false); + + let mut events = nodes[0].node.get_and_clear_pending_events(); + assert_eq!(events.len(), 2); + match events[0] { + Event::PaymentPathFailed { payment_hash: ev_payment_hash, payment_failed_permanently, .. } => { + assert_eq!(payment_hash, ev_payment_hash); + assert_eq!(payment_failed_permanently, false); + }, + _ => panic!("Unexpected event"), + } + match events[1] { + Event::PendingHTLCsForwardable { .. } => {}, + _ => panic!("Unexpected event"), + } + nodes[0].node.process_pending_htlc_forwards(); + } + } + + fail_payment_back!(nodes[1]); + + // Pass the retry along. + check_added_monitors!(nodes[0], 1); + let mut msg_events = nodes[0].node.get_and_clear_pending_msg_events(); + assert_eq!(msg_events.len(), 1); + pass_along_path(&nodes[0], &[&nodes[2], &nodes[3]], amt_msat, payment_hash, Some(payment_secret), msg_events.pop().unwrap(), true, None); + + fail_payment_back!(nodes[2]); + let evs = nodes[0].node.get_and_clear_pending_events(); + assert_eq!(evs.len(), 1); + match evs[0] { + Event::PaymentFailed { payment_hash: ev_payment_hash, reason, .. } => { + assert_eq!(ev_payment_hash, payment_hash); + // We have 1 retry attempt remaining, but we're out of blinded paths to try. + assert_eq!(reason, Some(PaymentFailureReason::RouteNotFound)); + }, + _ => panic!() + } +} + +#[test] +fn min_htlc() { + // The min htlc of a blinded path is the max (htlc_min - following_fees) along the path. Make sure + // the payment succeeds when we calculate the min htlc this way. + let chanmon_cfgs = create_chanmon_cfgs(4); + let node_cfgs = create_node_cfgs(4, &chanmon_cfgs); + let mut node_1_cfg = test_default_channel_config(); + node_1_cfg.channel_handshake_config.our_htlc_minimum_msat = 2000; + node_1_cfg.channel_config.forwarding_fee_base_msat = 1000; + node_1_cfg.channel_config.forwarding_fee_proportional_millionths = 100_000; + let mut node_2_cfg = test_default_channel_config(); + node_2_cfg.channel_handshake_config.our_htlc_minimum_msat = 5000; + node_2_cfg.channel_config.forwarding_fee_base_msat = 200; + node_2_cfg.channel_config.forwarding_fee_proportional_millionths = 150_000; + let mut node_3_cfg = test_default_channel_config(); + node_3_cfg.channel_handshake_config.our_htlc_minimum_msat = 2000; + let node_chanmgrs = create_node_chanmgrs(4, &node_cfgs, &[None, Some(node_1_cfg), Some(node_2_cfg), Some(node_3_cfg)]); + let nodes = create_network(4, &node_cfgs, &node_chanmgrs); + create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); + let chan_1_2 = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0); + let chan_2_3 = create_announced_chan_between_nodes_with_value(&nodes, 2, 3, 1_000_000, 0); + + let min_htlc_msat = { + // The min htlc for this setup is nodes[2]'s htlc_minimum_msat minus the + // following fees. + let post_base_fee = chan_2_3.1.contents.htlc_minimum_msat - chan_2_3.0.contents.fee_base_msat as u64; + let prop_fee = chan_2_3.0.contents.fee_proportional_millionths as u64; + (post_base_fee * 1_000_000 + 1_000_000 + prop_fee - 1) / (prop_fee + 1_000_000) + }; + let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[3], Some(min_htlc_msat), None); + let mut route_params = get_blinded_route_parameters( + min_htlc_msat, payment_secret, chan_1_2.1.contents.htlc_minimum_msat, + chan_1_2.1.contents.htlc_maximum_msat, vec![nodes[1].node.get_our_node_id(), + nodes[2].node.get_our_node_id(), nodes[3].node.get_our_node_id()], + &[&chan_1_2.0.contents, &chan_2_3.0.contents], &chanmon_cfgs[3].keys_manager); + assert_eq!(min_htlc_msat, + route_params.payment_params.payee.blinded_route_hints()[0].0.htlc_minimum_msat); + + nodes[0].node.send_payment(payment_hash, RecipientOnionFields::spontaneous_empty(), PaymentId(payment_hash.0), route_params.clone(), Retry::Attempts(0)).unwrap(); + check_added_monitors(&nodes[0], 1); + pass_along_route(&nodes[0], &[&[&nodes[1], &nodes[2], &nodes[3]]], min_htlc_msat, payment_hash, payment_secret); + claim_payment(&nodes[0], &[&nodes[1], &nodes[2], &nodes[3]], payment_preimage); + + // Paying 1 less than the min fails. + for _ in 0..IDEMPOTENCY_TIMEOUT_TICKS + 1 { + nodes[0].node.timer_tick_occurred(); + } + if let Payee::Blinded { ref mut route_hints, .. } = route_params.payment_params.payee { + route_hints[0].0.htlc_minimum_msat -= 1; + } else { panic!() } + route_params.final_value_msat -= 1; + 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); + + let mut payment_event_0_1 = { + let mut events = nodes[0].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + let ev = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events); + SendEvent::from_event(ev) + }; + nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &payment_event_0_1.msgs[0]); + check_added_monitors!(nodes[1], 0); + do_commitment_signed_dance(&nodes[1], &nodes[0], &payment_event_0_1.commitment_msg, true, true); + let mut 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]); + do_commitment_signed_dance(&nodes[0], &nodes[1], &updates.commitment_signed, false, false); + expect_payment_failed_conditions(&nodes[0], payment_hash, false, + PaymentFailedConditions::new().expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 32])); +} + +#[test] +fn conditionally_round_fwd_amt() { + // Previously, the (rng-found) feerates below caught a bug where an intermediate node would + // calculate an amt_to_forward that underpaid them by 1 msat, caused by rounding up the outbound + // amount on top of an already rounded-up total routing fee. Ensure that we'll conditionally round + // down intermediate nodes' outbound amounts based on whether rounding up will result in + // undercharging for relay. + let chanmon_cfgs = create_chanmon_cfgs(5); + let node_cfgs = create_node_cfgs(5, &chanmon_cfgs); + + let mut node_1_cfg = test_default_channel_config(); + node_1_cfg.channel_config.forwarding_fee_base_msat = 247371; + node_1_cfg.channel_config.forwarding_fee_proportional_millionths = 86552; + + let mut node_2_cfg = test_default_channel_config(); + node_2_cfg.channel_config.forwarding_fee_base_msat = 198921; + node_2_cfg.channel_config.forwarding_fee_proportional_millionths = 681759; + + let mut node_3_cfg = test_default_channel_config(); + node_3_cfg.channel_config.forwarding_fee_base_msat = 132845; + node_3_cfg.channel_config.forwarding_fee_proportional_millionths = 552561; + + let node_chanmgrs = create_node_chanmgrs(5, &node_cfgs, &[None, Some(node_1_cfg), Some(node_2_cfg), Some(node_3_cfg), None]); + let nodes = create_network(5, &node_cfgs, &node_chanmgrs); + create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); + let chan_1_2 = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0); + let chan_2_3 = create_announced_chan_between_nodes_with_value(&nodes, 2, 3, 1_000_000, 0); + let chan_3_4 = create_announced_chan_between_nodes_with_value(&nodes, 3, 4, 1_000_000, 0); + + let amt_msat = 100_000; + let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[4], Some(amt_msat), None); + let mut route_params = get_blinded_route_parameters(amt_msat, payment_secret, + chan_1_2.1.contents.htlc_minimum_msat, chan_1_2.1.contents.htlc_maximum_msat, + vec![nodes[1].node.get_our_node_id(), nodes[2].node.get_our_node_id(), + nodes[3].node.get_our_node_id(), nodes[4].node.get_our_node_id()], + &[&chan_1_2.0.contents, &chan_2_3.0.contents, &chan_3_4.0.contents], + &chanmon_cfgs[4].keys_manager); + route_params.max_total_routing_fee_msat = None; + + 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], &nodes[2], &nodes[3], &nodes[4]]], amt_msat, payment_hash, payment_secret); + nodes[4].node.claim_funds(payment_preimage); + let expected_path = &[&nodes[1], &nodes[2], &nodes[3], &nodes[4]]; + let expected_route = &[&expected_path[..]]; + let mut args = ClaimAlongRouteArgs::new(&nodes[0], &expected_route[..], payment_preimage) + .allow_1_msat_fee_overpay(); + let expected_fee = pass_claimed_payment_along_route(args); + expect_payment_sent(&nodes[0], payment_preimage, Some(Some(expected_fee)), true, true); +} diff --git a/lightning/src/ln/chan_utils.rs b/lightning/src/ln/chan_utils.rs index 3552748b31c..18c4d83406c 100644 --- a/lightning/src/ln/chan_utils.rs +++ b/lightning/src/ln/chan_utils.rs @@ -45,7 +45,7 @@ use crate::ln::channel::{INITIAL_COMMITMENT_NUMBER, ANCHOR_OUTPUT_VALUE_SATOSHI} use core::ops::Deref; use crate::chain; use crate::ln::features::ChannelTypeFeatures; -use crate::util::crypto::{sign, sign_with_aux_rand}; +use crate::crypto::utils::{sign, sign_with_aux_rand}; use super::channel_keys::{DelayedPaymentBasepoint, DelayedPaymentKey, HtlcKey, HtlcBasepoint, RevocationKey, RevocationBasepoint}; /// Maximum number of one-way in-flight HTLC (protocol-level value). @@ -485,9 +485,11 @@ impl TxCreationKeys { } /// The maximum length of a script returned by get_revokeable_redeemscript. -// Calculated as 6 bytes of opcodes, 1 byte push plus 2 bytes for contest_delay, and two public -// keys of 33 bytes (+ 1 push). -pub const REVOKEABLE_REDEEMSCRIPT_MAX_LENGTH: usize = 6 + 3 + 34*2; +// Calculated as 6 bytes of opcodes, 1 byte push plus 3 bytes for contest_delay, and two public +// keys of 33 bytes (+ 1 push). Generally, pushes are only 2 bytes (for values below 0x7fff, i.e. +// around 7 months), however, a 7 month contest delay shouldn't result in being unable to reclaim +// on-chain funds. +pub const REVOKEABLE_REDEEMSCRIPT_MAX_LENGTH: usize = 6 + 4 + 34*2; /// A script either spendable by the revocation /// key or the broadcaster_delayed_payment_key and satisfying the relative-locktime OP_CSV constrain. diff --git a/lightning/src/ln/chanmon_update_fail_tests.rs b/lightning/src/ln/chanmon_update_fail_tests.rs index af827b8cebb..2e95f5c63ff 100644 --- a/lightning/src/ln/chanmon_update_fail_tests.rs +++ b/lightning/src/ln/chanmon_update_fail_tests.rs @@ -21,7 +21,7 @@ use crate::chain::{ChannelMonitorUpdateStatus, Listen, Watch}; use crate::events::{Event, MessageSendEvent, MessageSendEventsProvider, PaymentPurpose, ClosureReason, HTLCDestination}; use crate::ln::channelmanager::{RAACommitmentOrder, PaymentSendFailure, PaymentId, RecipientOnionFields}; use crate::ln::channel::{AnnouncementSigsState, ChannelPhase}; -use crate::ln::msgs; +use crate::ln::{msgs, ChannelId}; use crate::ln::msgs::{ChannelMessageHandler, RoutingMessageHandler}; use crate::util::test_channel_signer::TestChannelSigner; use crate::util::errors::APIError; @@ -1861,7 +1861,7 @@ fn do_during_funding_monitor_fail(confirm_a_first: bool, restore_b_before_conf: chanmon_cfgs[1].persister.set_update_ret(ChannelMonitorUpdateStatus::InProgress); let funding_created_msg = get_event_msg!(nodes[0], MessageSendEvent::SendFundingCreated, nodes[1].node.get_our_node_id()); - let channel_id = OutPoint { txid: funding_created_msg.funding_txid, index: funding_created_msg.funding_output_index }.to_channel_id(); + let channel_id = ChannelId::v1_from_funding_outpoint(OutPoint { txid: funding_created_msg.funding_txid, index: funding_created_msg.funding_output_index }); nodes[1].node.handle_funding_created(&nodes[0].node.get_our_node_id(), &funding_created_msg); check_added_monitors!(nodes[1], 1); @@ -1955,8 +1955,8 @@ fn do_during_funding_monitor_fail(confirm_a_first: bool, restore_b_before_conf: 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, [nodes[1].node.get_our_node_id()], 100000); - check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure, [nodes[0].node.get_our_node_id()], 100000); + check_closed_event!(nodes[0], 1, ClosureReason::CounterpartyInitiatedCooperativeClosure, [nodes[1].node.get_our_node_id()], 100000); + check_closed_event!(nodes[1], 1, ClosureReason::LocallyInitiatedCooperativeClosure, [nodes[0].node.get_our_node_id()], 100000); } #[test] @@ -2634,8 +2634,8 @@ fn test_temporary_error_during_shutdown() { 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, [nodes[0].node.get_our_node_id()], 100000); - check_closed_event!(nodes[0], 1, ClosureReason::CooperativeClosure, [nodes[1].node.get_our_node_id()], 100000); + check_closed_event!(nodes[1], 1, ClosureReason::CounterpartyInitiatedCooperativeClosure, [nodes[0].node.get_our_node_id()], 100000); + check_closed_event!(nodes[0], 1, ClosureReason::LocallyInitiatedCooperativeClosure, [nodes[1].node.get_our_node_id()], 100000); } #[test] @@ -3404,7 +3404,8 @@ fn do_test_reload_mon_update_completion_actions(close_during_reload: bool) { 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); + expect_payment_forwarded(events.pop().unwrap(), &nodes[1], &nodes[0], &nodes[2], Some(1000), + None, close_during_reload, false, false); if close_during_reload { match events[0] { Event::ChannelClosed { .. } => {}, diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 7500d88a10e..5fbd2e1d733 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -7,11 +7,15 @@ // You may not use this file except in accordance with one or both of these // licenses. -use bitcoin::blockdata::constants::{ChainHash, WITNESS_SCALE_FACTOR}; +use bitcoin::blockdata::constants::ChainHash; +#[cfg(dual_funding)] +use bitcoin::blockdata::constants::WITNESS_SCALE_FACTOR; use bitcoin::blockdata::script::{Script, ScriptBuf, Builder}; use bitcoin::blockdata::transaction::Transaction; use bitcoin::sighash::{self, EcdsaSighashType}; -use bitcoin::consensus::{encode, Encodable}; +use bitcoin::consensus::encode; +#[cfg(dual_funding)] +use bitcoin::consensus::Encodable as _; use bitcoin::hashes::Hash; use bitcoin::hashes::sha256::Hash as Sha256; @@ -21,16 +25,18 @@ use bitcoin::hash_types::{Txid, BlockHash}; use bitcoin::secp256k1::constants::PUBLIC_KEY_SIZE; use bitcoin::secp256k1::{PublicKey,SecretKey}; use bitcoin::secp256k1::{Secp256k1,ecdsa::Signature}; -use bitcoin::{secp256k1, TxIn, TxOut}; -// #[cfg(dual_funding)] // TODO splicing -use bitcoin::Witness; +use bitcoin::secp256k1; +#[cfg(dual_funding)] +use bitcoin::{TxIn, TxOut, Witness}; #[cfg(dual_funding)] use bitcoin::locktime::absolute::LockTime; +#[cfg(dual_funding)] use crate::events::bump_transaction::{BASE_INPUT_WEIGHT, EMPTY_SCRIPT_SIG_WEIGHT}; use crate::ln::{ChannelId, PaymentPreimage, PaymentHash}; use crate::ln::channel_splice::{PendingSpliceInfoPre, PendingSpliceInfoPost}; use crate::ln::features::{ChannelTypeFeatures, InitFeatures}; +#[cfg(dual_funding)] use crate::ln::interactivetxs::{AbortReason, InteractiveTxConstructor, InteractiveTxMessageSend, InteractiveTxSigningSession}; use crate::ln::msgs; use crate::ln::msgs::DecodeError; @@ -40,7 +46,9 @@ use crate::ln::chan_utils::{CounterpartyCommitmentSecrets, TxCreationKeys, HTLCO use crate::ln::chan_utils; use crate::ln::onion_utils::HTLCFailReason; use crate::chain::BestBlock; -use crate::chain::chaininterface::{FeeEstimator, ConfirmationTarget, LowerBoundedFeeEstimator, fee_for_weight}; +use crate::chain::chaininterface::{FeeEstimator, ConfirmationTarget, LowerBoundedFeeEstimator}; +#[cfg(dual_funding)] +use crate::chain::chaininterface::fee_for_weight; use crate::chain::channelmonitor::{ChannelMonitor, ChannelMonitorUpdate, ChannelMonitorUpdateStep, LATENCY_GRACE_PERIOD_BLOCKS, CLOSED_CHANNEL_UPDATE_ID}; use crate::chain::transaction::{OutPoint, TransactionData}; use crate::sign::ecdsa::{EcdsaChannelSigner, WriteableEcdsaChannelSigner}; @@ -50,12 +58,15 @@ use crate::events::ClosureReason; use crate::events::Event; use crate::routing::gossip::NodeId; use crate::util::ser::{Readable, ReadableArgs, Writeable, Writer}; +#[cfg(dual_funding)] +use crate::util::ser::TransactionU16LenLimited; use crate::util::logger::{Logger, Record, WithContext}; use crate::util::errors::APIError; use crate::util::config::{UserConfig, ChannelConfig, LegacyChannelConfig, ChannelHandshakeConfig, ChannelHandshakeLimits, MaxDustHTLCExposure}; use crate::util::scid_utils::scid_from_parts; use crate::io; +#[cfg(dual_funding)] use crate::io_extras::sink; use crate::prelude::*; use core::{cmp,mem,fmt}; @@ -167,6 +178,72 @@ enum InboundHTLCState { LocalRemoved(InboundHTLCRemovalReason), } +/// Exposes the state of pending inbound HTLCs. +/// +/// At a high level, an HTLC being forwarded from one Lightning node to another Lightning node goes +/// through the following states in the state machine: +/// - Announced for addition by the originating node through the update_add_htlc message. +/// - Added to the commitment transaction of the receiving node and originating node in turn +/// through the exchange of commitment_signed and revoke_and_ack messages. +/// - Announced for resolution (fulfillment or failure) by the receiving node through either one of +/// the update_fulfill_htlc, update_fail_htlc, and update_fail_malformed_htlc messages. +/// - Removed from the commitment transaction of the originating node and receiving node in turn +/// through the exchange of commitment_signed and revoke_and_ack messages. +/// +/// This can be used to inspect what next message an HTLC is waiting for to advance its state. +#[derive(Clone, Debug, PartialEq)] +pub enum InboundHTLCStateDetails { + /// We have added this HTLC in our commitment transaction by receiving commitment_signed and + /// returning revoke_and_ack. We are awaiting the appropriate revoke_and_ack's from the remote + /// before this HTLC is included on the remote commitment transaction. + AwaitingRemoteRevokeToAdd, + /// This HTLC has been included in the commitment_signed and revoke_and_ack messages on both sides + /// and is included in both commitment transactions. + /// + /// This HTLC is now safe to either forward or be claimed as a payment by us. The HTLC will + /// remain in this state until the forwarded upstream HTLC has been resolved and we resolve this + /// HTLC correspondingly, or until we claim it as a payment. If it is part of a multipart + /// payment, it will only be claimed together with other required parts. + Committed, + /// We have received the preimage for this HTLC and it is being removed by fulfilling it with + /// update_fulfill_htlc. This HTLC is still on both commitment transactions, but we are awaiting + /// the appropriate revoke_and_ack's from the remote before this HTLC is removed from the remote + /// commitment transaction after update_fulfill_htlc. + AwaitingRemoteRevokeToRemoveFulfill, + /// The HTLC is being removed by failing it with update_fail_htlc or update_fail_malformed_htlc. + /// This HTLC is still on both commitment transactions, but we are awaiting the appropriate + /// revoke_and_ack's from the remote before this HTLC is removed from the remote commitment + /// transaction. + AwaitingRemoteRevokeToRemoveFail, +} + +impl From<&InboundHTLCState> for Option { + fn from(state: &InboundHTLCState) -> Option { + match state { + InboundHTLCState::RemoteAnnounced(_) => None, + InboundHTLCState::AwaitingRemoteRevokeToAnnounce(_) => + Some(InboundHTLCStateDetails::AwaitingRemoteRevokeToAdd), + InboundHTLCState::AwaitingAnnouncedRemoteRevoke(_) => + Some(InboundHTLCStateDetails::AwaitingRemoteRevokeToAdd), + InboundHTLCState::Committed => + Some(InboundHTLCStateDetails::Committed), + InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::FailRelay(_)) => + Some(InboundHTLCStateDetails::AwaitingRemoteRevokeToRemoveFail), + InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::FailMalformed(_)) => + Some(InboundHTLCStateDetails::AwaitingRemoteRevokeToRemoveFail), + InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::Fulfill(_)) => + Some(InboundHTLCStateDetails::AwaitingRemoteRevokeToRemoveFulfill), + } + } +} + +impl_writeable_tlv_based_enum_upgradable!(InboundHTLCStateDetails, + (0, AwaitingRemoteRevokeToAdd) => {}, + (2, Committed) => {}, + (4, AwaitingRemoteRevokeToRemoveFulfill) => {}, + (6, AwaitingRemoteRevokeToRemoveFail) => {}; +); + struct InboundHTLCOutput { htlc_id: u64, amount_msat: u64, @@ -175,6 +252,53 @@ struct InboundHTLCOutput { state: InboundHTLCState, } +/// Exposes details around pending inbound HTLCs. +#[derive(Clone, Debug, PartialEq)] +pub struct InboundHTLCDetails { + /// The HTLC ID. + /// The IDs are incremented by 1 starting from 0 for each offered HTLC. + /// They are unique per channel and inbound/outbound direction, unless an HTLC was only announced + /// and not part of any commitment transaction. + pub htlc_id: u64, + /// The amount in msat. + pub amount_msat: u64, + /// The block height at which this HTLC expires. + pub cltv_expiry: u32, + /// The payment hash. + pub payment_hash: PaymentHash, + /// The state of the HTLC in the state machine. + /// + /// Determines on which commitment transactions the HTLC is included and what message the HTLC is + /// waiting for to advance to the next state. + /// + /// See [`InboundHTLCStateDetails`] for information on the specific states. + /// + /// LDK will always fill this field in, but when downgrading to prior versions of LDK, new + /// states may result in `None` here. + pub state: Option, + /// Whether the HTLC has an output below the local dust limit. If so, the output will be trimmed + /// from the local commitment transaction and added to the commitment transaction fee. + /// For non-anchor channels, this takes into account the cost of the second-stage HTLC + /// transactions as well. + /// + /// When the local commitment transaction is broadcasted as part of a unilateral closure, + /// the value of this HTLC will therefore not be claimable but instead burned as a transaction + /// fee. + /// + /// Note that dust limits are specific to each party. An HTLC can be dust for the local + /// commitment transaction but not for the counterparty's commitment transaction and vice versa. + pub is_dust: bool, +} + +impl_writeable_tlv_based!(InboundHTLCDetails, { + (0, htlc_id, required), + (2, amount_msat, required), + (4, cltv_expiry, required), + (6, payment_hash, required), + (7, state, upgradable_option), + (8, is_dust, required), +}); + #[cfg_attr(test, derive(Clone, Debug, PartialEq))] enum OutboundHTLCState { /// Added by us and included in a commitment_signed (if we were AwaitingRemoteRevoke when we @@ -208,6 +332,72 @@ enum OutboundHTLCState { AwaitingRemovedRemoteRevoke(OutboundHTLCOutcome), } +/// Exposes the state of pending outbound HTLCs. +/// +/// At a high level, an HTLC being forwarded from one Lightning node to another Lightning node goes +/// through the following states in the state machine: +/// - Announced for addition by the originating node through the update_add_htlc message. +/// - Added to the commitment transaction of the receiving node and originating node in turn +/// through the exchange of commitment_signed and revoke_and_ack messages. +/// - Announced for resolution (fulfillment or failure) by the receiving node through either one of +/// the update_fulfill_htlc, update_fail_htlc, and update_fail_malformed_htlc messages. +/// - Removed from the commitment transaction of the originating node and receiving node in turn +/// through the exchange of commitment_signed and revoke_and_ack messages. +/// +/// This can be used to inspect what next message an HTLC is waiting for to advance its state. +#[derive(Clone, Debug, PartialEq)] +pub enum OutboundHTLCStateDetails { + /// We are awaiting the appropriate revoke_and_ack's from the remote before the HTLC is added + /// on the remote's commitment transaction after update_add_htlc. + AwaitingRemoteRevokeToAdd, + /// The HTLC has been added to the remote's commitment transaction by sending commitment_signed + /// and receiving revoke_and_ack in return. + /// + /// The HTLC will remain in this state until the remote node resolves the HTLC, or until we + /// unilaterally close the channel due to a timeout with an uncooperative remote node. + Committed, + /// The HTLC has been fulfilled successfully by the remote with a preimage in update_fulfill_htlc, + /// and we removed the HTLC from our commitment transaction by receiving commitment_signed and + /// returning revoke_and_ack. We are awaiting the appropriate revoke_and_ack's from the remote + /// for the removal from its commitment transaction. + AwaitingRemoteRevokeToRemoveSuccess, + /// The HTLC has been failed by the remote with update_fail_htlc or update_fail_malformed_htlc, + /// and we removed the HTLC from our commitment transaction by receiving commitment_signed and + /// returning revoke_and_ack. We are awaiting the appropriate revoke_and_ack's from the remote + /// for the removal from its commitment transaction. + AwaitingRemoteRevokeToRemoveFailure, +} + +impl From<&OutboundHTLCState> for OutboundHTLCStateDetails { + fn from(state: &OutboundHTLCState) -> OutboundHTLCStateDetails { + match state { + OutboundHTLCState::LocalAnnounced(_) => + OutboundHTLCStateDetails::AwaitingRemoteRevokeToAdd, + OutboundHTLCState::Committed => + OutboundHTLCStateDetails::Committed, + // RemoteRemoved states are ignored as the state is transient and the remote has not committed to + // the state yet. + OutboundHTLCState::RemoteRemoved(_) => + OutboundHTLCStateDetails::Committed, + OutboundHTLCState::AwaitingRemoteRevokeToRemove(OutboundHTLCOutcome::Success(_)) => + OutboundHTLCStateDetails::AwaitingRemoteRevokeToRemoveSuccess, + OutboundHTLCState::AwaitingRemoteRevokeToRemove(OutboundHTLCOutcome::Failure(_)) => + OutboundHTLCStateDetails::AwaitingRemoteRevokeToRemoveFailure, + OutboundHTLCState::AwaitingRemovedRemoteRevoke(OutboundHTLCOutcome::Success(_)) => + OutboundHTLCStateDetails::AwaitingRemoteRevokeToRemoveSuccess, + OutboundHTLCState::AwaitingRemovedRemoteRevoke(OutboundHTLCOutcome::Failure(_)) => + OutboundHTLCStateDetails::AwaitingRemoteRevokeToRemoveFailure, + } + } +} + +impl_writeable_tlv_based_enum_upgradable!(OutboundHTLCStateDetails, + (0, AwaitingRemoteRevokeToAdd) => {}, + (2, Committed) => {}, + (4, AwaitingRemoteRevokeToRemoveSuccess) => {}, + (6, AwaitingRemoteRevokeToRemoveFailure) => {}; +); + #[derive(Clone)] #[cfg_attr(test, derive(Debug, PartialEq))] enum OutboundHTLCOutcome { @@ -246,6 +436,58 @@ struct OutboundHTLCOutput { skimmed_fee_msat: Option, } +/// Exposes details around pending outbound HTLCs. +#[derive(Clone, Debug, PartialEq)] +pub struct OutboundHTLCDetails { + /// The HTLC ID. + /// The IDs are incremented by 1 starting from 0 for each offered HTLC. + /// They are unique per channel and inbound/outbound direction, unless an HTLC was only announced + /// and not part of any commitment transaction. + /// + /// Not present when we are awaiting a remote revocation and the HTLC is not added yet. + pub htlc_id: Option, + /// The amount in msat. + pub amount_msat: u64, + /// The block height at which this HTLC expires. + pub cltv_expiry: u32, + /// The payment hash. + pub payment_hash: PaymentHash, + /// The state of the HTLC in the state machine. + /// + /// Determines on which commitment transactions the HTLC is included and what message the HTLC is + /// waiting for to advance to the next state. + /// + /// See [`OutboundHTLCStateDetails`] for information on the specific states. + /// + /// LDK will always fill this field in, but when downgrading to prior versions of LDK, new + /// states may result in `None` here. + pub state: Option, + /// The extra fee being skimmed off the top of this HTLC. + pub skimmed_fee_msat: Option, + /// Whether the HTLC has an output below the local dust limit. If so, the output will be trimmed + /// from the local commitment transaction and added to the commitment transaction fee. + /// For non-anchor channels, this takes into account the cost of the second-stage HTLC + /// transactions as well. + /// + /// When the local commitment transaction is broadcasted as part of a unilateral closure, + /// the value of this HTLC will therefore not be claimable but instead burned as a transaction + /// fee. + /// + /// Note that dust limits are specific to each party. An HTLC can be dust for the local + /// commitment transaction but not for the counterparty's commitment transaction and vice versa. + pub is_dust: bool, +} + +impl_writeable_tlv_based!(OutboundHTLCDetails, { + (0, htlc_id, required), + (2, amount_msat, required), + (4, cltv_expiry, required), + (6, payment_hash, required), + (7, state, upgradable_option), + (8, skimmed_fee_msat, required), + (10, is_dust, required), +}); + /// See AwaitingRemoteRevoke ChannelState for more info #[cfg_attr(test, derive(Clone, Debug, PartialEq))] enum HTLCUpdateAwaitingACK { @@ -268,10 +510,15 @@ enum HTLCUpdateAwaitingACK { htlc_id: u64, err_packet: msgs::OnionErrorPacket, }, + FailMalformedHTLC { + htlc_id: u64, + failure_code: u16, + sha256_of_onion: [u8; 32], + }, } macro_rules! define_state_flags { - ($flag_type_doc: expr, $flag_type: ident, [$(($flag_doc: expr, $flag: ident, $value: expr)),+], $extra_flags: expr) => { + ($flag_type_doc: expr, $flag_type: ident, [$(($flag_doc: expr, $flag: ident, $value: expr, $get: ident, $set: ident, $clear: ident)),+], $extra_flags: expr) => { #[doc = $flag_type_doc] #[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq)] struct $flag_type(u32); @@ -300,15 +547,18 @@ macro_rules! define_state_flags { #[allow(unused)] fn is_empty(&self) -> bool { self.0 == 0 } - #[allow(unused)] fn is_set(&self, flag: Self) -> bool { *self & flag == flag } + #[allow(unused)] + fn set(&mut self, flag: Self) { *self |= flag } + #[allow(unused)] + fn clear(&mut self, flag: Self) -> Self { self.0 &= !flag.0; *self } } - impl core::ops::Not for $flag_type { - type Output = Self; - fn not(self) -> Self::Output { Self(!self.0) } - } + $( + define_state_flags!($flag_type, Self::$flag, $get, $set, $clear); + )* + impl core::ops::BitOr for $flag_type { type Output = Self; fn bitor(self, rhs: Self) -> Self::Output { Self(self.0 | rhs.0) } @@ -327,8 +577,28 @@ macro_rules! define_state_flags { ($flag_type_doc: expr, $flag_type: ident, $flags: tt) => { define_state_flags!($flag_type_doc, $flag_type, $flags, 0); }; + ($flag_type: ident, $flag: expr, $get: ident, $set: ident, $clear: ident) => { + impl $flag_type { + #[allow(unused)] + fn $get(&self) -> bool { self.is_set($flag_type::new() | $flag) } + #[allow(unused)] + fn $set(&mut self) { self.set($flag_type::new() | $flag) } + #[allow(unused)] + fn $clear(&mut self) -> Self { self.clear($flag_type::new() | $flag) } + } + }; ($flag_type_doc: expr, FUNDED_STATE, $flag_type: ident, $flags: tt) => { define_state_flags!($flag_type_doc, $flag_type, $flags, FundedStateFlags::ALL.0); + + define_state_flags!($flag_type, FundedStateFlags::PEER_DISCONNECTED, + is_peer_disconnected, set_peer_disconnected, clear_peer_disconnected); + define_state_flags!($flag_type, FundedStateFlags::MONITOR_UPDATE_IN_PROGRESS, + is_monitor_update_in_progress, set_monitor_update_in_progress, clear_monitor_update_in_progress); + define_state_flags!($flag_type, FundedStateFlags::REMOTE_SHUTDOWN_SENT, + is_remote_shutdown_sent, set_remote_shutdown_sent, clear_remote_shutdown_sent); + define_state_flags!($flag_type, FundedStateFlags::LOCAL_SHUTDOWN_SENT, + is_local_shutdown_sent, set_local_shutdown_sent, clear_local_shutdown_sent); + impl core::ops::BitOr for $flag_type { type Output = Self; fn bitor(self, rhs: FundedStateFlags) -> Self::Output { Self(self.0 | rhs.0) } @@ -375,15 +645,19 @@ define_state_flags!( "Flags that apply to all [`ChannelState`] variants in which the channel is funded.", FundedStateFlags, [ ("Indicates the remote side is considered \"disconnected\" and no updates are allowed \ - until after we've done a `channel_reestablish` dance.", PEER_DISCONNECTED, state_flags::PEER_DISCONNECTED), + until after we've done a `channel_reestablish` dance.", PEER_DISCONNECTED, state_flags::PEER_DISCONNECTED, + is_peer_disconnected, set_peer_disconnected, clear_peer_disconnected), ("Indicates 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 \ - complete it.", MONITOR_UPDATE_IN_PROGRESS, state_flags::MONITOR_UPDATE_IN_PROGRESS), + complete it.", MONITOR_UPDATE_IN_PROGRESS, state_flags::MONITOR_UPDATE_IN_PROGRESS, + is_monitor_update_in_progress, set_monitor_update_in_progress, clear_monitor_update_in_progress), ("Indicates we received 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.", REMOTE_SHUTDOWN_SENT, state_flags::REMOTE_SHUTDOWN_SENT), + message when possible.", REMOTE_SHUTDOWN_SENT, state_flags::REMOTE_SHUTDOWN_SENT, + is_remote_shutdown_sent, set_remote_shutdown_sent, clear_remote_shutdown_sent), ("Indicates we sent a `shutdown` message. At this point, we may not add any new HTLCs to \ - the channel.", LOCAL_SHUTDOWN_SENT, state_flags::LOCAL_SHUTDOWN_SENT) + the channel.", LOCAL_SHUTDOWN_SENT, state_flags::LOCAL_SHUTDOWN_SENT, + is_local_shutdown_sent, set_local_shutdown_sent, clear_local_shutdown_sent) ] ); @@ -391,9 +665,9 @@ define_state_flags!( "Flags that only apply to [`ChannelState::NegotiatingFunding`].", NegotiatingFundingFlags, [ ("Indicates we have (or are prepared to) send our `open_channel`/`accept_channel` message.", - OUR_INIT_SENT, state_flags::OUR_INIT_SENT), + OUR_INIT_SENT, state_flags::OUR_INIT_SENT, is_our_init_sent, set_our_init_sent, clear_our_init_sent), ("Indicates we have received their `open_channel`/`accept_channel` message.", - THEIR_INIT_SENT, state_flags::THEIR_INIT_SENT) + THEIR_INIT_SENT, state_flags::THEIR_INIT_SENT, is_their_init_sent, set_their_init_sent, clear_their_init_sent) ] ); @@ -402,13 +676,16 @@ define_state_flags!( FUNDED_STATE, AwaitingChannelReadyFlags, [ ("Indicates they sent us a `channel_ready` message. Once both `THEIR_CHANNEL_READY` and \ `OUR_CHANNEL_READY` are set, our state moves on to `ChannelReady`.", - THEIR_CHANNEL_READY, state_flags::THEIR_CHANNEL_READY), + THEIR_CHANNEL_READY, state_flags::THEIR_CHANNEL_READY, + is_their_channel_ready, set_their_channel_ready, clear_their_channel_ready), ("Indicates we sent them a `channel_ready` message. Once both `THEIR_CHANNEL_READY` and \ `OUR_CHANNEL_READY` are set, our state moves on to `ChannelReady`.", - OUR_CHANNEL_READY, state_flags::OUR_CHANNEL_READY), + OUR_CHANNEL_READY, state_flags::OUR_CHANNEL_READY, + is_our_channel_ready, set_our_channel_ready, clear_our_channel_ready), ("Indicates the channel was funded in a batch and the broadcast of the funding transaction \ is being held until all channels in the batch have received `funding_signed` and have \ - their monitors persisted.", WAITING_FOR_BATCH, state_flags::WAITING_FOR_BATCH) + their monitors persisted.", WAITING_FOR_BATCH, state_flags::WAITING_FOR_BATCH, + is_waiting_for_batch, set_waiting_for_batch, clear_waiting_for_batch) ] ); @@ -419,10 +696,13 @@ define_state_flags!( `revoke_and_ack` message. During this period, we can't generate new `commitment_signed` \ messages as we'd 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.", - AWAITING_REMOTE_REVOKE, state_flags::AWAITING_REMOTE_REVOKE) + AWAITING_REMOTE_REVOKE, state_flags::AWAITING_REMOTE_REVOKE, + is_awaiting_remote_revoke, set_awaiting_remote_revoke, clear_awaiting_remote_revoke) ] ); +// Note that the order of this enum is implicitly defined by where each variant is placed. Take this +// into account when introducing new states and update `test_channel_state_order` accordingly. #[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq)] enum ChannelState { /// We are negotiating the parameters required for the channel prior to funding it. @@ -443,12 +723,12 @@ enum ChannelState { } macro_rules! impl_state_flag { - ($get: ident, $set: ident, $clear: ident, $state_flag: expr, [$($state: ident),+]) => { + ($get: ident, $set: ident, $clear: ident, [$($state: ident),+]) => { #[allow(unused)] fn $get(&self) -> bool { match self { $( - ChannelState::$state(flags) => flags.is_set($state_flag.into()), + ChannelState::$state(flags) => flags.$get(), )* _ => false, } @@ -457,7 +737,7 @@ macro_rules! impl_state_flag { fn $set(&mut self) { match self { $( - ChannelState::$state(flags) => *flags |= $state_flag, + ChannelState::$state(flags) => flags.$set(), )* _ => debug_assert!(false, "Attempted to set flag on unexpected ChannelState"), } @@ -466,17 +746,17 @@ macro_rules! impl_state_flag { fn $clear(&mut self) { match self { $( - ChannelState::$state(flags) => *flags &= !($state_flag), + ChannelState::$state(flags) => { let _ = flags.$clear(); }, )* _ => debug_assert!(false, "Attempted to clear flag on unexpected ChannelState"), } } }; - ($get: ident, $set: ident, $clear: ident, $state_flag: expr, FUNDED_STATES) => { - impl_state_flag!($get, $set, $clear, $state_flag, [AwaitingChannelReady, ChannelReady]); + ($get: ident, $set: ident, $clear: ident, FUNDED_STATES) => { + impl_state_flag!($get, $set, $clear, [AwaitingChannelReady, ChannelReady]); }; - ($get: ident, $set: ident, $clear: ident, $state_flag: expr, $state: ident) => { - impl_state_flag!($get, $set, $clear, $state_flag, [$state]); + ($get: ident, $set: ident, $clear: ident, $state: ident) => { + impl_state_flag!($get, $set, $clear, [$state]); }; } @@ -527,35 +807,27 @@ impl ChannelState { } } - fn should_force_holding_cell(&self) -> bool { + fn can_generate_new_commitment(&self) -> bool { match self { ChannelState::ChannelReady(flags) => - flags.is_set(ChannelReadyFlags::AWAITING_REMOTE_REVOKE) || - flags.is_set(FundedStateFlags::MONITOR_UPDATE_IN_PROGRESS.into()) || - flags.is_set(FundedStateFlags::PEER_DISCONNECTED.into()), + !flags.is_set(ChannelReadyFlags::AWAITING_REMOTE_REVOKE) && + !flags.is_set(FundedStateFlags::MONITOR_UPDATE_IN_PROGRESS.into()) && + !flags.is_set(FundedStateFlags::PEER_DISCONNECTED.into()), _ => { - debug_assert!(false, "The holding cell is only valid within ChannelReady"); + debug_assert!(false, "Can only generate new commitment within ChannelReady"); false }, } } - impl_state_flag!(is_peer_disconnected, set_peer_disconnected, clear_peer_disconnected, - FundedStateFlags::PEER_DISCONNECTED, FUNDED_STATES); - impl_state_flag!(is_monitor_update_in_progress, set_monitor_update_in_progress, clear_monitor_update_in_progress, - FundedStateFlags::MONITOR_UPDATE_IN_PROGRESS, FUNDED_STATES); - impl_state_flag!(is_local_shutdown_sent, set_local_shutdown_sent, clear_local_shutdown_sent, - FundedStateFlags::LOCAL_SHUTDOWN_SENT, FUNDED_STATES); - impl_state_flag!(is_remote_shutdown_sent, set_remote_shutdown_sent, clear_remote_shutdown_sent, - FundedStateFlags::REMOTE_SHUTDOWN_SENT, FUNDED_STATES); - impl_state_flag!(is_our_channel_ready, set_our_channel_ready, clear_our_channel_ready, - AwaitingChannelReadyFlags::OUR_CHANNEL_READY, AwaitingChannelReady); - impl_state_flag!(is_their_channel_ready, set_their_channel_ready, clear_their_channel_ready, - AwaitingChannelReadyFlags::THEIR_CHANNEL_READY, AwaitingChannelReady); - impl_state_flag!(is_waiting_for_batch, set_waiting_for_batch, clear_waiting_for_batch, - AwaitingChannelReadyFlags::WAITING_FOR_BATCH, AwaitingChannelReady); - impl_state_flag!(is_awaiting_remote_revoke, set_awaiting_remote_revoke, clear_awaiting_remote_revoke, - ChannelReadyFlags::AWAITING_REMOTE_REVOKE, ChannelReady); + impl_state_flag!(is_peer_disconnected, set_peer_disconnected, clear_peer_disconnected, FUNDED_STATES); + impl_state_flag!(is_monitor_update_in_progress, set_monitor_update_in_progress, clear_monitor_update_in_progress, FUNDED_STATES); + impl_state_flag!(is_local_shutdown_sent, set_local_shutdown_sent, clear_local_shutdown_sent, FUNDED_STATES); + impl_state_flag!(is_remote_shutdown_sent, set_remote_shutdown_sent, clear_remote_shutdown_sent, FUNDED_STATES); + impl_state_flag!(is_our_channel_ready, set_our_channel_ready, clear_our_channel_ready, AwaitingChannelReady); + impl_state_flag!(is_their_channel_ready, set_their_channel_ready, clear_their_channel_ready, AwaitingChannelReady); + impl_state_flag!(is_waiting_for_batch, set_waiting_for_batch, clear_waiting_for_batch, AwaitingChannelReady); + impl_state_flag!(is_awaiting_remote_revoke, set_awaiting_remote_revoke, clear_awaiting_remote_revoke, ChannelReady); } pub const INITIAL_COMMITMENT_NUMBER: u64 = (1 << 48) - 1; @@ -736,8 +1008,8 @@ struct CommitmentStats<'a> { total_fee_sat: u64, // the total fee included in the transaction num_nondust_htlcs: usize, // the number of HTLC outputs (dust HTLCs *non*-included) htlcs_included: Vec<(HTLCOutputInCommitment, Option<&'a HTLCSource>)>, // the list of HTLCs (dust HTLCs *included*) which were not ignored when building the transaction - local_balance_msat: u64, // local balance before fees but considering dust limits - remote_balance_msat: u64, // remote balance before fees but considering dust limits + local_balance_msat: u64, // local balance before fees *not* considering dust limits + remote_balance_msat: u64, // remote balance before fees *not* considering dust limits outbound_htlc_preimages: Vec, // preimages for successful offered HTLCs since last commitment inbound_htlc_preimages: Vec, // preimages for successful received HTLCs since last commitment } @@ -818,15 +1090,20 @@ pub(super) struct ReestablishResponses { /// The result of a shutdown that should be handled. #[must_use] pub(crate) struct ShutdownResult { + pub(crate) closure_reason: ClosureReason, /// A channel monitor update to apply. - pub(crate) monitor_update: Option<(PublicKey, OutPoint, ChannelMonitorUpdate)>, + pub(crate) monitor_update: Option<(PublicKey, OutPoint, ChannelId, ChannelMonitorUpdate)>, /// A list of dropped outbound HTLCs that can safely be failed backwards immediately. pub(crate) dropped_outbound_htlcs: Vec<(HTLCSource, PaymentHash, PublicKey, ChannelId)>, /// An unbroadcasted batch funding transaction id. The closure of this channel should be /// propagated to the remainder of the batch. pub(crate) unbroadcasted_batch_funding_txid: Option, pub(crate) channel_id: ChannelId, + pub(crate) user_channel_id: u128, + pub(crate) channel_capacity_satoshis: u64, pub(crate) counterparty_node_id: PublicKey, + pub(crate) unbroadcasted_funding_tx: Option, + pub(crate) channel_funding_txo: Option, } /// If the majority of the channels funds are to the fundee and the initiator holds only just @@ -1256,15 +1533,22 @@ pub(super) struct ChannelContext where SP::Target: SignerProvider { // We track whether we already emitted a `ChannelReady` event. channel_ready_event_emitted: bool, + /// Some if we initiated to shut down the channel. + local_initiated_shutdown: Option<()>, + /// The unique identifier used to re-derive the private key material for the channel through /// [`SignerProvider::derive_channel_signer`]. + #[cfg(not(test))] channel_keys_id: [u8; 32], + #[cfg(test)] + pub channel_keys_id: [u8; 32], /// If we can't release a [`ChannelMonitorUpdate`] until some external action completes, we /// store it here and only release it to the `ChannelManager` once it asks for it. blocked_monitor_updates: Vec, - /// The current interactive transaction construction session. + /// The current interactive transaction construction session under negotiation. + #[cfg(dual_funding)] interactive_tx_constructor: Option, } @@ -1274,32 +1558,19 @@ impl ChannelContext where SP::Target: SignerProvider { entropy_source: &'a ES, signer_provider: &'a SP, counterparty_node_id: PublicKey, - our_supported_features: &'a ChannelTypeFeatures, their_features: &'a InitFeatures, user_id: u128, config: &'a UserConfig, current_chain_height: u32, logger: &'a L, is_0conf: bool, - our_funding_satoshis: u64, - counterparty_pubkeys: ChannelPublicKeys, - msg_channel_flags: u8, - msg_channel_type: Option, - msg_funding_satoshis: u64, - msg_to_self_delay: u16, + channel_type: ChannelTypeFeatures, holder_selected_channel_reserve_satoshis: u64, msg_channel_reserve_satoshis: u64, msg_push_msat: u64, - msg_dust_limit_satoshis: u64, - msg_htlc_minimum_msat: u64, - msg_commitment_feerate_sat_per_1000_weight: u32, - msg_max_accepted_htlcs: u16, - msg_shutdown_scriptpubkey: Option, - msg_max_htlc_value_in_flight_msat: u64, - msg_temporary_channel_id: ChannelId, - msg_first_per_commitment_point: PublicKey, + open_channel_fields: msgs::CommonOpenChannelFields, ) -> Result, ChannelError> where ES::Target: EntropySource, @@ -1307,105 +1578,76 @@ impl ChannelContext where SP::Target: SignerProvider { L::Target: Logger, SP::Target: SignerProvider, { - let logger = WithContext::from(logger, Some(counterparty_node_id), Some(msg_temporary_channel_id)); - let announced_channel = if (msg_channel_flags & 1) == 1 { true } else { false }; - - // First check the channel type is known, failing before we do anything else if we don't - // support this channel type. - let channel_type = if let Some(channel_type) = &msg_channel_type { - if channel_type.supports_any_optional_bits() { - return Err(ChannelError::Close("Channel Type field contained optional bits - this is not allowed".to_owned())); - } + let logger = WithContext::from(logger, Some(counterparty_node_id), Some(open_channel_fields.temporary_channel_id)); + let announced_channel = if (open_channel_fields.channel_flags & 1) == 1 { true } else { false }; - // We only support the channel types defined by the `ChannelManager` in - // `provided_channel_type_features`. The channel type must always support - // `static_remote_key`. - if !channel_type.requires_static_remote_key() { - return Err(ChannelError::Close("Channel Type was not understood - we require static remote key".to_owned())); - } - // Make sure we support all of the features behind the channel type. - if !channel_type.is_subset(our_supported_features) { - return Err(ChannelError::Close("Channel Type contains unsupported features".to_owned())); - } - if channel_type.requires_scid_privacy() && announced_channel { - return Err(ChannelError::Close("SCID Alias/Privacy Channel Type cannot be set on a public channel".to_owned())); - } - channel_type.clone() - } else { - let channel_type = ChannelTypeFeatures::from_init(&their_features); - if channel_type != ChannelTypeFeatures::only_static_remote_key() { - return Err(ChannelError::Close("Only static_remote_key is supported for non-negotiated channel types".to_owned())); - } - channel_type - }; + let channel_value_satoshis = our_funding_satoshis.saturating_add(open_channel_fields.funding_satoshis); - let channel_keys_id = signer_provider.generate_channel_keys_id(true, msg_funding_satoshis, user_id); - let holder_signer = signer_provider.derive_channel_signer(msg_funding_satoshis, channel_keys_id); + let channel_keys_id = signer_provider.generate_channel_keys_id(true, channel_value_satoshis, user_id); + let holder_signer = signer_provider.derive_channel_signer(channel_value_satoshis, channel_keys_id); let pubkeys = holder_signer.pubkeys().clone(); if config.channel_handshake_config.our_to_self_delay < BREAKDOWN_TIMEOUT { return Err(ChannelError::Close(format!("Configured with an unreasonable our_to_self_delay ({}) putting user funds at risks. It must be greater than {}", config.channel_handshake_config.our_to_self_delay, BREAKDOWN_TIMEOUT))); } - let channel_value_satoshis = our_funding_satoshis.saturating_add(msg_funding_satoshis); - // Check sanity of message fields: if channel_value_satoshis > config.channel_handshake_limits.max_funding_satoshis { return Err(ChannelError::Close(format!( "Per our config, funding must be at most {}. It was {}. Peer contribution: {}. Our contribution: {}", config.channel_handshake_limits.max_funding_satoshis, channel_value_satoshis, - msg_funding_satoshis, our_funding_satoshis))); + open_channel_fields.funding_satoshis, our_funding_satoshis))); } - if msg_funding_satoshis >= TOTAL_BITCOIN_SUPPLY_SATOSHIS { - return Err(ChannelError::Close(format!("Funding must be smaller than the total bitcoin supply. It was {}", msg_funding_satoshis))); + if channel_value_satoshis >= TOTAL_BITCOIN_SUPPLY_SATOSHIS { + return Err(ChannelError::Close(format!("Funding must be smaller than the total bitcoin supply. It was {}", channel_value_satoshis))); } - if msg_channel_reserve_satoshis > msg_funding_satoshis { - return Err(ChannelError::Close(format!("Bogus channel_reserve_satoshis ({}). Must be not greater than funding_satoshis: {}", msg_channel_reserve_satoshis, msg_funding_satoshis))); + if msg_channel_reserve_satoshis > channel_value_satoshis { + return Err(ChannelError::Close(format!("Bogus channel_reserve_satoshis ({}). Must be no greater than channel_value_satoshis: {}", msg_channel_reserve_satoshis, channel_value_satoshis))); } - let full_channel_value_msat = (msg_funding_satoshis - msg_channel_reserve_satoshis) * 1000; + let full_channel_value_msat = (channel_value_satoshis - msg_channel_reserve_satoshis) * 1000; if msg_push_msat > full_channel_value_msat { return Err(ChannelError::Close(format!("push_msat {} was larger than channel amount minus reserve ({})", msg_push_msat, full_channel_value_msat))); } - if msg_dust_limit_satoshis > msg_funding_satoshis { - return Err(ChannelError::Close(format!("dust_limit_satoshis {} was larger than funding_satoshis {}. Peer never wants payout outputs?", msg_dust_limit_satoshis, msg_funding_satoshis))); + if open_channel_fields.dust_limit_satoshis > channel_value_satoshis { + return Err(ChannelError::Close(format!("dust_limit_satoshis {} was larger than channel_value_satoshis {}. Peer never wants payout outputs?", open_channel_fields.dust_limit_satoshis, channel_value_satoshis))); } - 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))); + if open_channel_fields.htlc_minimum_msat >= full_channel_value_msat { + return Err(ChannelError::Close(format!("Minimum htlc value ({}) was larger than full channel value ({})", open_channel_fields.htlc_minimum_msat, full_channel_value_msat))); } - Channel::::check_remote_fee(&channel_type, fee_estimator, msg_commitment_feerate_sat_per_1000_weight, None, &&logger)?; + Channel::::check_remote_fee(&channel_type, fee_estimator, open_channel_fields.commitment_feerate_sat_per_1000_weight, 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 { - return Err(ChannelError::Close(format!("They wanted our payments to be delayed by a needlessly long period. Upper limit: {}. Actual: {}", max_counterparty_selected_contest_delay, msg_to_self_delay))); + if open_channel_fields.to_self_delay > max_counterparty_selected_contest_delay { + return Err(ChannelError::Close(format!("They wanted our payments to be delayed by a needlessly long period. Upper limit: {}. Actual: {}", max_counterparty_selected_contest_delay, open_channel_fields.to_self_delay))); } - if msg_max_accepted_htlcs < 1 { + if open_channel_fields.max_accepted_htlcs < 1 { return Err(ChannelError::Close("0 max_accepted_htlcs makes for a useless channel".to_owned())); } - if msg_max_accepted_htlcs > MAX_HTLCS { - return Err(ChannelError::Close(format!("max_accepted_htlcs was {}. It must not be larger than {}", msg_max_accepted_htlcs, MAX_HTLCS))); + if open_channel_fields.max_accepted_htlcs > MAX_HTLCS { + return Err(ChannelError::Close(format!("max_accepted_htlcs was {}. It must not be larger than {}", open_channel_fields.max_accepted_htlcs, MAX_HTLCS))); } // Now check against optional parameters as set by config... - if msg_funding_satoshis < config.channel_handshake_limits.min_funding_satoshis { - return Err(ChannelError::Close(format!("Funding satoshis ({}) is less than the user specified limit ({})", msg_funding_satoshis, config.channel_handshake_limits.min_funding_satoshis))); + if channel_value_satoshis < config.channel_handshake_limits.min_funding_satoshis { + return Err(ChannelError::Close(format!("Funding satoshis ({}) is less than the user specified limit ({})", channel_value_satoshis, config.channel_handshake_limits.min_funding_satoshis))); } - if msg_htlc_minimum_msat > config.channel_handshake_limits.max_htlc_minimum_msat { - return Err(ChannelError::Close(format!("htlc_minimum_msat ({}) is higher than the user specified limit ({})", msg_htlc_minimum_msat, config.channel_handshake_limits.max_htlc_minimum_msat))); + if open_channel_fields.htlc_minimum_msat > config.channel_handshake_limits.max_htlc_minimum_msat { + return Err(ChannelError::Close(format!("htlc_minimum_msat ({}) is higher than the user specified limit ({})", open_channel_fields.htlc_minimum_msat, config.channel_handshake_limits.max_htlc_minimum_msat))); } - if msg_max_htlc_value_in_flight_msat < config.channel_handshake_limits.min_max_htlc_value_in_flight_msat { - return Err(ChannelError::Close(format!("max_htlc_value_in_flight_msat ({}) is less than the user specified limit ({})", msg_max_htlc_value_in_flight_msat, config.channel_handshake_limits.min_max_htlc_value_in_flight_msat))); + if open_channel_fields.max_htlc_value_in_flight_msat < config.channel_handshake_limits.min_max_htlc_value_in_flight_msat { + return Err(ChannelError::Close(format!("max_htlc_value_in_flight_msat ({}) is less than the user specified limit ({})", open_channel_fields.max_htlc_value_in_flight_msat, config.channel_handshake_limits.min_max_htlc_value_in_flight_msat))); } if msg_channel_reserve_satoshis > config.channel_handshake_limits.max_channel_reserve_satoshis { return Err(ChannelError::Close(format!("channel_reserve_satoshis ({}) is higher than the user specified limit ({})", msg_channel_reserve_satoshis, config.channel_handshake_limits.max_channel_reserve_satoshis))); } - if msg_max_accepted_htlcs < config.channel_handshake_limits.min_max_accepted_htlcs { - return Err(ChannelError::Close(format!("max_accepted_htlcs ({}) is less than the user specified limit ({})", msg_max_accepted_htlcs, config.channel_handshake_limits.min_max_accepted_htlcs))); + if open_channel_fields.max_accepted_htlcs < config.channel_handshake_limits.min_max_accepted_htlcs { + return Err(ChannelError::Close(format!("max_accepted_htlcs ({}) is less than the user specified limit ({})", open_channel_fields.max_accepted_htlcs, config.channel_handshake_limits.min_max_accepted_htlcs))); } - if msg_dust_limit_satoshis < MIN_CHAN_DUST_LIMIT_SATOSHIS { - return Err(ChannelError::Close(format!("dust_limit_satoshis ({}) is less than the implementation limit ({})", msg_dust_limit_satoshis, MIN_CHAN_DUST_LIMIT_SATOSHIS))); + if open_channel_fields.dust_limit_satoshis < MIN_CHAN_DUST_LIMIT_SATOSHIS { + return Err(ChannelError::Close(format!("dust_limit_satoshis ({}) is less than the implementation limit ({})", open_channel_fields.dust_limit_satoshis, MIN_CHAN_DUST_LIMIT_SATOSHIS))); } - if msg_dust_limit_satoshis > MAX_CHAN_DUST_LIMIT_SATOSHIS { - return Err(ChannelError::Close(format!("dust_limit_satoshis ({}) is greater than the implementation limit ({})", msg_dust_limit_satoshis, MAX_CHAN_DUST_LIMIT_SATOSHIS))); + if open_channel_fields.dust_limit_satoshis > MAX_CHAN_DUST_LIMIT_SATOSHIS { + return Err(ChannelError::Close(format!("dust_limit_satoshis ({}) is greater than the implementation limit ({})", open_channel_fields.dust_limit_satoshis, MAX_CHAN_DUST_LIMIT_SATOSHIS))); } // Convert things into internal flags and prep our state: @@ -1428,8 +1670,8 @@ impl ChannelContext where SP::Target: SignerProvider { log_debug!(logger, "channel_reserve_satoshis ({}) is smaller than our dust limit ({}). We can broadcast stale states without any risk, implying this channel is very insecure for our counterparty.", msg_channel_reserve_satoshis, MIN_CHAN_DUST_LIMIT_SATOSHIS); } - if holder_selected_channel_reserve_satoshis < msg_dust_limit_satoshis { - return Err(ChannelError::Close(format!("Dust limit ({}) too high for the channel reserve we require the remote to keep ({})", msg_dust_limit_satoshis, holder_selected_channel_reserve_satoshis))); + if holder_selected_channel_reserve_satoshis < open_channel_fields.dust_limit_satoshis { + return Err(ChannelError::Close(format!("Dust limit ({}) too high for the channel reserve we require the remote to keep ({})", open_channel_fields.dust_limit_satoshis, holder_selected_channel_reserve_satoshis))); } // check if the funder's amount for the initial commitment tx is sufficient @@ -1439,8 +1681,8 @@ impl ChannelContext where SP::Target: SignerProvider { } else { 0 }; - let funders_amount_msat = msg_funding_satoshis * 1000 - msg_push_msat; - let commitment_tx_fee = commit_tx_fee_msat(msg_commitment_feerate_sat_per_1000_weight, MIN_AFFORDABLE_HTLC_COUNT, &channel_type) / 1000; + let funders_amount_msat = open_channel_fields.funding_satoshis * 1000 - msg_push_msat; + let commitment_tx_fee = commit_tx_fee_msat(open_channel_fields.commitment_feerate_sat_per_1000_weight, MIN_AFFORDABLE_HTLC_COUNT, &channel_type) / 1000; if (funders_amount_msat / 1000).saturating_sub(anchor_outputs_value) < commitment_tx_fee { return Err(ChannelError::Close(format!("Funding amount ({} sats) can't even pay fee for initial commitment transaction fee of {} sats.", (funders_amount_msat / 1000).saturating_sub(anchor_outputs_value), commitment_tx_fee))); } @@ -1453,7 +1695,7 @@ impl ChannelContext where SP::Target: SignerProvider { } let counterparty_shutdown_scriptpubkey = if their_features.supports_upfront_shutdown_script() { - match &msg_shutdown_scriptpubkey { + match &open_channel_fields.shutdown_scriptpubkey { &Some(ref script) => { // Peer is signaling upfront_shutdown and has opt-out with a 0-length script. We don't enforce anything if script.len() == 0 { @@ -1499,7 +1741,7 @@ impl ChannelContext where SP::Target: SignerProvider { Some(cmp::max(config.channel_handshake_config.minimum_depth, 1)) }; - let value_to_self_msat = our_funding_satoshis * 1000 + msg_push_msat; + let value_to_self_msat = our_funding_satoshis * 1000 + msg_push_msat; // TODO(dual_funding): Checks for `funding_feerate_sat_per_1000_weight`? @@ -1516,8 +1758,8 @@ impl ChannelContext where SP::Target: SignerProvider { inbound_handshake_limits_override: None, - temporary_channel_id: Some(msg_temporary_channel_id), - channel_id: msg_temporary_channel_id, + temporary_channel_id: Some(open_channel_fields.temporary_channel_id), + channel_id: open_channel_fields.temporary_channel_id, channel_state: ChannelState::NegotiatingFunding( NegotiatingFundingFlags::OUR_INIT_SENT | NegotiatingFundingFlags::THEIR_INIT_SENT ), @@ -1557,9 +1799,9 @@ impl ChannelContext where SP::Target: SignerProvider { #[cfg(debug_assertions)] - holder_max_commitment_tx_output: Mutex::new((value_to_self_msat, (msg_funding_satoshis * 1000 - msg_push_msat).saturating_sub(value_to_self_msat))), + holder_max_commitment_tx_output: Mutex::new((value_to_self_msat, (channel_value_satoshis * 1000 - msg_push_msat).saturating_sub(value_to_self_msat))), #[cfg(debug_assertions)] - counterparty_max_commitment_tx_output: Mutex::new((value_to_self_msat, (msg_funding_satoshis * 1000 - msg_push_msat).saturating_sub(value_to_self_msat))), + counterparty_max_commitment_tx_output: Mutex::new((value_to_self_msat, (channel_value_satoshis * 1000 - msg_push_msat).saturating_sub(value_to_self_msat))), last_sent_closing_fee: None, pending_counterparty_closing_signed: None, @@ -1572,17 +1814,17 @@ impl ChannelContext where SP::Target: SignerProvider { short_channel_id: None, channel_creation_height: current_chain_height, - feerate_per_kw: msg_commitment_feerate_sat_per_1000_weight, + feerate_per_kw: open_channel_fields.commitment_feerate_sat_per_1000_weight, channel_value_satoshis, - counterparty_dust_limit_satoshis: msg_dust_limit_satoshis, + counterparty_dust_limit_satoshis: open_channel_fields.dust_limit_satoshis, holder_dust_limit_satoshis: MIN_CHAN_DUST_LIMIT_SATOSHIS, - counterparty_max_htlc_value_in_flight_msat: cmp::min(msg_max_htlc_value_in_flight_msat, channel_value_satoshis * 1000), + counterparty_max_htlc_value_in_flight_msat: cmp::min(open_channel_fields.max_htlc_value_in_flight_msat, channel_value_satoshis * 1000), holder_max_htlc_value_in_flight_msat: get_holder_max_htlc_value_in_flight_msat(channel_value_satoshis, &config.channel_handshake_config), counterparty_selected_channel_reserve_satoshis: Some(msg_channel_reserve_satoshis), holder_selected_channel_reserve_satoshis, - counterparty_htlc_minimum_msat: msg_htlc_minimum_msat, + counterparty_htlc_minimum_msat: open_channel_fields.htlc_minimum_msat, 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, + counterparty_max_accepted_htlcs: open_channel_fields.max_accepted_htlcs, holder_max_accepted_htlcs: cmp::min(config.channel_handshake_config.our_max_accepted_htlcs, MAX_HTLCS), minimum_depth, @@ -1593,7 +1835,7 @@ impl ChannelContext where SP::Target: SignerProvider { holder_selected_contest_delay: config.channel_handshake_config.our_to_self_delay, is_outbound_from_holder: false, counterparty_parameters: Some(CounterpartyChannelTransactionParameters { - selected_contest_delay: msg_to_self_delay, + selected_contest_delay: open_channel_fields.to_self_delay, pubkeys: counterparty_pubkeys, }), funding_outpoint: None, @@ -1603,7 +1845,7 @@ impl ChannelContext where SP::Target: SignerProvider { funding_transaction_saved: None, is_batch_funding: None, - counterparty_cur_commitment_point: Some(msg_first_per_commitment_point), + counterparty_cur_commitment_point: Some(open_channel_fields.first_per_commitment_point), counterparty_prev_commitment_point: None, counterparty_node_id, @@ -1631,13 +1873,16 @@ impl ChannelContext where SP::Target: SignerProvider { channel_ready_event_emitted: false, #[cfg(any(test, fuzzing))] - historical_inbound_htlc_fulfills: HashSet::new(), + historical_inbound_htlc_fulfills: new_hash_set(), channel_type, channel_keys_id, + local_initiated_shutdown: None, + blocked_monitor_updates: Vec::new(), + #[cfg(dual_funding)] interactive_tx_constructor: None, pending_splice_pre: None, pending_splice_post: None, @@ -1658,7 +1903,7 @@ impl ChannelContext where SP::Target: SignerProvider { config: &'a UserConfig, current_chain_height: u32, outbound_scid_alias: u64, - temporary_channel_id: ChannelId, + temporary_channel_id: Option, holder_selected_channel_reserve_satoshis: u64, channel_keys_id: [u8; 32], holder_signer: ::EcdsaSigner, @@ -1725,6 +1970,7 @@ impl ChannelContext where SP::Target: SignerProvider { Err(_) => return Err(APIError::ChannelUnavailable { err: "Failed to get destination script".to_owned()}), }; + let temporary_channel_id = temporary_channel_id.unwrap_or_else(|| ChannelId::temporary_from_entropy_source(entropy_source)); Ok(Self { user_id, @@ -1853,13 +2099,15 @@ impl ChannelContext where SP::Target: SignerProvider { channel_ready_event_emitted: false, #[cfg(any(test, fuzzing))] - historical_inbound_htlc_fulfills: HashSet::new(), + historical_inbound_htlc_fulfills: new_hash_set(), channel_type, channel_keys_id, blocked_monitor_updates: Vec::new(), + local_initiated_shutdown: None, + #[cfg(dual_funding)] interactive_tx_constructor: None, pending_splice_pre: None, pending_splice_post: None, @@ -2018,12 +2266,7 @@ impl ChannelContext where SP::Target: SignerProvider { } fn do_accept_channel_checks(&mut self, default_limits: &ChannelHandshakeLimits, - their_features: &InitFeatures, msg_dust_limit_satoshis: u64, msg_channel_reserve_satoshis: u64, - msg_to_self_delay: u16, msg_max_accepted_htlcs: u16, msg_htlc_minimum_msat: u64, - msg_max_htlc_value_in_flight_msat: u64, msg_minimum_depth: u32, msg_channel_type: &Option, - msg_shutdown_scriptpubkey: &Option, msg_funding_pubkey: PublicKey, msg_revocation_basepoint: PublicKey, - msg_payment_point: PublicKey, msg_delayed_payment_basepoint: PublicKey, msg_htlc_basepoint: PublicKey, - msg_first_per_commitment_point: PublicKey, + their_features: &InitFeatures, common_fields: &msgs::CommonAcceptChannelFields, channel_reserve_satoshis: u64, ) -> Result<(), ChannelError> { let peer_limits = if let Some(ref limits) = self.inbound_handshake_limits_override { limits } else { default_limits }; @@ -2034,58 +2277,58 @@ impl ChannelContext where SP::Target: SignerProvider { if !matches!(self.channel_state, ChannelState::NegotiatingFunding(flags) if flags == NegotiatingFundingFlags::OUR_INIT_SENT) { return Err(ChannelError::Close("Got an accept_channel message at a strange time".to_owned())); } - if msg_dust_limit_satoshis > 21000000 * 100000000 { - return Err(ChannelError::Close(format!("Peer never wants payout outputs? dust_limit_satoshis was {}", msg_dust_limit_satoshis))); + if common_fields.dust_limit_satoshis > 21000000 * 100000000 { + return Err(ChannelError::Close(format!("Peer never wants payout outputs? dust_limit_satoshis was {}", common_fields.dust_limit_satoshis))); } - if msg_channel_reserve_satoshis > self.channel_value_satoshis { - return Err(ChannelError::Close(format!("Bogus channel_reserve_satoshis ({}). Must not be greater than ({})", msg_channel_reserve_satoshis, self.channel_value_satoshis))); + if channel_reserve_satoshis > self.channel_value_satoshis { + return Err(ChannelError::Close(format!("Bogus channel_reserve_satoshis ({}). Must not be greater than ({})", channel_reserve_satoshis, self.channel_value_satoshis))); } - if msg_dust_limit_satoshis > self.holder_selected_channel_reserve_satoshis { - return Err(ChannelError::Close(format!("Dust limit ({}) is bigger than our channel reserve ({})", msg_dust_limit_satoshis, self.holder_selected_channel_reserve_satoshis))); + if common_fields.dust_limit_satoshis > self.holder_selected_channel_reserve_satoshis { + return Err(ChannelError::Close(format!("Dust limit ({}) is bigger than our channel reserve ({})", common_fields.dust_limit_satoshis, self.holder_selected_channel_reserve_satoshis))); } - if msg_channel_reserve_satoshis > self.channel_value_satoshis - self.holder_selected_channel_reserve_satoshis { + if channel_reserve_satoshis > self.channel_value_satoshis - self.holder_selected_channel_reserve_satoshis { return Err(ChannelError::Close(format!("Bogus channel_reserve_satoshis ({}). Must not be greater than channel value minus our reserve ({})", - msg_channel_reserve_satoshis, self.channel_value_satoshis - self.holder_selected_channel_reserve_satoshis))); + channel_reserve_satoshis, self.channel_value_satoshis - self.holder_selected_channel_reserve_satoshis))); } - let full_channel_value_msat = (self.channel_value_satoshis - msg_channel_reserve_satoshis) * 1000; - if msg_htlc_minimum_msat >= full_channel_value_msat { - return Err(ChannelError::Close(format!("Minimum htlc value ({}) is full channel value ({})", msg_htlc_minimum_msat, full_channel_value_msat))); + let full_channel_value_msat = (self.channel_value_satoshis - channel_reserve_satoshis) * 1000; + if common_fields.htlc_minimum_msat >= full_channel_value_msat { + return Err(ChannelError::Close(format!("Minimum htlc value ({}) is full channel value ({})", common_fields.htlc_minimum_msat, full_channel_value_msat))); } let max_delay_acceptable = u16::min(peer_limits.their_to_self_delay, MAX_LOCAL_BREAKDOWN_TIMEOUT); - if msg_to_self_delay > max_delay_acceptable { - return Err(ChannelError::Close(format!("They wanted our payments to be delayed by a needlessly long period. Upper limit: {}. Actual: {}", max_delay_acceptable, msg_to_self_delay))); + if common_fields.to_self_delay > max_delay_acceptable { + return Err(ChannelError::Close(format!("They wanted our payments to be delayed by a needlessly long period. Upper limit: {}. Actual: {}", max_delay_acceptable, common_fields.to_self_delay))); } - if msg_max_accepted_htlcs < 1 { + if common_fields.max_accepted_htlcs < 1 { return Err(ChannelError::Close("0 max_accepted_htlcs makes for a useless channel".to_owned())); } - if msg_max_accepted_htlcs > MAX_HTLCS { - return Err(ChannelError::Close(format!("max_accepted_htlcs was {}. It must not be larger than {}", msg_max_accepted_htlcs, MAX_HTLCS))); + if common_fields.max_accepted_htlcs > MAX_HTLCS { + return Err(ChannelError::Close(format!("max_accepted_htlcs was {}. It must not be larger than {}", common_fields.max_accepted_htlcs, MAX_HTLCS))); } // Now check against optional parameters as set by config... - if msg_htlc_minimum_msat > peer_limits.max_htlc_minimum_msat { - return Err(ChannelError::Close(format!("htlc_minimum_msat ({}) is higher than the user specified limit ({})", msg_htlc_minimum_msat, peer_limits.max_htlc_minimum_msat))); + if common_fields.htlc_minimum_msat > peer_limits.max_htlc_minimum_msat { + return Err(ChannelError::Close(format!("htlc_minimum_msat ({}) is higher than the user specified limit ({})", common_fields.htlc_minimum_msat, peer_limits.max_htlc_minimum_msat))); } - if msg_max_htlc_value_in_flight_msat < peer_limits.min_max_htlc_value_in_flight_msat { - return Err(ChannelError::Close(format!("max_htlc_value_in_flight_msat ({}) is less than the user specified limit ({})", msg_max_htlc_value_in_flight_msat, peer_limits.min_max_htlc_value_in_flight_msat))); + if common_fields.max_htlc_value_in_flight_msat < peer_limits.min_max_htlc_value_in_flight_msat { + return Err(ChannelError::Close(format!("max_htlc_value_in_flight_msat ({}) is less than the user specified limit ({})", common_fields.max_htlc_value_in_flight_msat, peer_limits.min_max_htlc_value_in_flight_msat))); } - if msg_channel_reserve_satoshis > peer_limits.max_channel_reserve_satoshis { - return Err(ChannelError::Close(format!("channel_reserve_satoshis ({}) is higher than the user specified limit ({})", msg_channel_reserve_satoshis, peer_limits.max_channel_reserve_satoshis))); + if channel_reserve_satoshis > peer_limits.max_channel_reserve_satoshis { + return Err(ChannelError::Close(format!("channel_reserve_satoshis ({}) is higher than the user specified limit ({})", channel_reserve_satoshis, peer_limits.max_channel_reserve_satoshis))); } - if msg_max_accepted_htlcs < peer_limits.min_max_accepted_htlcs { - return Err(ChannelError::Close(format!("max_accepted_htlcs ({}) is less than the user specified limit ({})", msg_max_accepted_htlcs, peer_limits.min_max_accepted_htlcs))); + if common_fields.max_accepted_htlcs < peer_limits.min_max_accepted_htlcs { + return Err(ChannelError::Close(format!("max_accepted_htlcs ({}) is less than the user specified limit ({})", common_fields.max_accepted_htlcs, peer_limits.min_max_accepted_htlcs))); } - if msg_dust_limit_satoshis < MIN_CHAN_DUST_LIMIT_SATOSHIS { - return Err(ChannelError::Close(format!("dust_limit_satoshis ({}) is less than the implementation limit ({})", msg_dust_limit_satoshis, MIN_CHAN_DUST_LIMIT_SATOSHIS))); + if common_fields.dust_limit_satoshis < MIN_CHAN_DUST_LIMIT_SATOSHIS { + return Err(ChannelError::Close(format!("dust_limit_satoshis ({}) is less than the implementation limit ({})", common_fields.dust_limit_satoshis, MIN_CHAN_DUST_LIMIT_SATOSHIS))); } - if msg_dust_limit_satoshis > MAX_CHAN_DUST_LIMIT_SATOSHIS { - return Err(ChannelError::Close(format!("dust_limit_satoshis ({}) is greater than the implementation limit ({})", msg_dust_limit_satoshis, MAX_CHAN_DUST_LIMIT_SATOSHIS))); + if common_fields.dust_limit_satoshis > MAX_CHAN_DUST_LIMIT_SATOSHIS { + return Err(ChannelError::Close(format!("dust_limit_satoshis ({}) is greater than the implementation limit ({})", common_fields.dust_limit_satoshis, MAX_CHAN_DUST_LIMIT_SATOSHIS))); } - if msg_minimum_depth > peer_limits.max_minimum_depth { - return Err(ChannelError::Close(format!("We consider the minimum depth to be unreasonably large. Expected minimum: ({}). Actual: ({})", peer_limits.max_minimum_depth, msg_minimum_depth))); + if common_fields.minimum_depth > peer_limits.max_minimum_depth { + return Err(ChannelError::Close(format!("We consider the minimum depth to be unreasonably large. Expected minimum: ({}). Actual: ({})", peer_limits.max_minimum_depth, common_fields.minimum_depth))); } - if let Some(ty) = &msg_channel_type { + if let Some(ty) = &common_fields.channel_type { if *ty != self.channel_type { return Err(ChannelError::Close("Channel Type in accept_channel didn't match the one sent in open_channel.".to_owned())); } @@ -2101,7 +2344,7 @@ impl ChannelContext where SP::Target: SignerProvider { } let counterparty_shutdown_scriptpubkey = if their_features.supports_upfront_shutdown_script() { - match &msg_shutdown_scriptpubkey { + match &common_fields.shutdown_scriptpubkey { &Some(ref script) => { // Peer is signaling upfront_shutdown and has opt-out with a 0-length script. We don't enforce anything if script.len() == 0 { @@ -2120,32 +2363,32 @@ impl ChannelContext where SP::Target: SignerProvider { } } else { None }; - self.counterparty_dust_limit_satoshis = msg_dust_limit_satoshis; - self.counterparty_max_htlc_value_in_flight_msat = cmp::min(msg_max_htlc_value_in_flight_msat, self.channel_value_satoshis * 1000); - self.counterparty_selected_channel_reserve_satoshis = Some(msg_channel_reserve_satoshis); - self.counterparty_htlc_minimum_msat = msg_htlc_minimum_msat; - self.counterparty_max_accepted_htlcs = msg_max_accepted_htlcs; + self.counterparty_dust_limit_satoshis = common_fields.dust_limit_satoshis; + self.counterparty_max_htlc_value_in_flight_msat = cmp::min(common_fields.max_htlc_value_in_flight_msat, self.channel_value_satoshis * 1000); + self.counterparty_selected_channel_reserve_satoshis = Some(channel_reserve_satoshis); + self.counterparty_htlc_minimum_msat = common_fields.htlc_minimum_msat; + self.counterparty_max_accepted_htlcs = common_fields.max_accepted_htlcs; if peer_limits.trust_own_funding_0conf { - self.minimum_depth = Some(msg_minimum_depth); + self.minimum_depth = Some(common_fields.minimum_depth); } else { - self.minimum_depth = Some(cmp::max(1, msg_minimum_depth)); + self.minimum_depth = Some(cmp::max(1, common_fields.minimum_depth)); } let counterparty_pubkeys = ChannelPublicKeys { - funding_pubkey: msg_funding_pubkey, - revocation_basepoint: RevocationBasepoint::from(msg_revocation_basepoint), - payment_point: msg_payment_point, - delayed_payment_basepoint: DelayedPaymentBasepoint::from(msg_delayed_payment_basepoint), - htlc_basepoint: HtlcBasepoint(msg_htlc_basepoint) + funding_pubkey: common_fields.funding_pubkey, + revocation_basepoint: RevocationBasepoint::from(common_fields.revocation_basepoint), + payment_point: common_fields.payment_basepoint, + delayed_payment_basepoint: DelayedPaymentBasepoint::from(common_fields.delayed_payment_basepoint), + htlc_basepoint: HtlcBasepoint::from(common_fields.htlc_basepoint) }; self.channel_transaction_parameters.counterparty_parameters = Some(CounterpartyChannelTransactionParameters { - selected_contest_delay: msg_to_self_delay, + selected_contest_delay: common_fields.to_self_delay, pubkeys: counterparty_pubkeys, }); - self.counterparty_cur_commitment_point = Some(msg_first_per_commitment_point); + self.counterparty_cur_commitment_point = Some(common_fields.first_per_commitment_point); self.counterparty_shutdown_scriptpubkey = counterparty_shutdown_scriptpubkey; self.channel_state = ChannelState::NegotiatingFunding( @@ -2497,13 +2740,13 @@ impl ChannelContext where SP::Target: SignerProvider { } } - let mut value_to_self_msat: i64 = (self.value_to_self_msat - local_htlc_total_msat) as i64 + value_to_self_msat_offset; + let value_to_self_msat: i64 = (self.value_to_self_msat - local_htlc_total_msat) as i64 + value_to_self_msat_offset; assert!(value_to_self_msat >= 0); // Note that in case they have several just-awaiting-last-RAA fulfills in-progress (ie // AwaitingRemoteRevokeToRemove or AwaitingRemovedRemoteRevoke) we may have allowed them to // "violate" their reserve value by couting those against it. Thus, we have to convert // everything to i64 before subtracting as otherwise we can overflow. - let mut value_to_remote_msat: i64 = (self.channel_value_satoshis * 1000) as i64 - (self.value_to_self_msat as i64) - (remote_htlc_total_msat as i64) - value_to_self_msat_offset; + let value_to_remote_msat: i64 = (self.channel_value_satoshis * 1000) as i64 - (self.value_to_self_msat as i64) - (remote_htlc_total_msat as i64) - value_to_self_msat_offset; assert!(value_to_remote_msat >= 0); #[cfg(debug_assertions)] @@ -2569,10 +2812,6 @@ impl ChannelContext where SP::Target: SignerProvider { htlcs_included.sort_unstable_by_key(|h| h.0.transaction_output_index.unwrap()); htlcs_included.append(&mut included_dust_htlcs); - // For the stats, trimmed-to-0 the value in msats accordingly - value_to_self_msat = if (value_to_self_msat * 1000) < broadcaster_dust_limit_satoshis as i64 { 0 } else { value_to_self_msat }; - value_to_remote_msat = if (value_to_remote_msat * 1000) < broadcaster_dust_limit_satoshis as i64 { 0 } else { value_to_remote_msat }; - CommitmentStats { tx, feerate_per_kw, @@ -2606,8 +2845,6 @@ impl ChannelContext where SP::Target: SignerProvider { /// will sign and send to our counterparty. /// 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! let revocation_basepoint = &self.get_holder_pubkeys().revocation_basepoint; let htlc_basepoint = &self.get_holder_pubkeys().htlc_basepoint; let counterparty_pubkeys = self.get_counterparty_pubkeys(); @@ -2645,7 +2882,8 @@ impl ChannelContext where SP::Target: SignerProvider { if let Some(feerate) = outbound_feerate_update { feerate_per_kw = cmp::max(feerate_per_kw, feerate); } - cmp::max(2530, feerate_per_kw * 1250 / 1000) + let feerate_plus_quarter = feerate_per_kw.checked_mul(1250).map(|v| v / 1000); + cmp::max(2530, feerate_plus_quarter.unwrap_or(u32::max_value())) } /// Get forwarding information for the counterparty. @@ -2735,6 +2973,99 @@ impl ChannelContext where SP::Target: SignerProvider { stats } + /// Returns information on all pending inbound HTLCs. + pub fn get_pending_inbound_htlc_details(&self) -> Vec { + let mut holding_cell_states = new_hash_map(); + for holding_cell_update in self.holding_cell_htlc_updates.iter() { + match holding_cell_update { + HTLCUpdateAwaitingACK::ClaimHTLC { htlc_id, .. } => { + holding_cell_states.insert( + htlc_id, + InboundHTLCStateDetails::AwaitingRemoteRevokeToRemoveFulfill, + ); + }, + HTLCUpdateAwaitingACK::FailHTLC { htlc_id, .. } => { + holding_cell_states.insert( + htlc_id, + InboundHTLCStateDetails::AwaitingRemoteRevokeToRemoveFail, + ); + }, + HTLCUpdateAwaitingACK::FailMalformedHTLC { htlc_id, .. } => { + holding_cell_states.insert( + htlc_id, + InboundHTLCStateDetails::AwaitingRemoteRevokeToRemoveFail, + ); + }, + // Outbound HTLC. + HTLCUpdateAwaitingACK::AddHTLC { .. } => {}, + } + } + let mut inbound_details = Vec::new(); + let htlc_success_dust_limit = if self.get_channel_type().supports_anchors_zero_fee_htlc_tx() { + 0 + } else { + let dust_buffer_feerate = self.get_dust_buffer_feerate(None) as u64; + dust_buffer_feerate * htlc_success_tx_weight(self.get_channel_type()) / 1000 + }; + let holder_dust_limit_success_sat = htlc_success_dust_limit + self.holder_dust_limit_satoshis; + for htlc in self.pending_inbound_htlcs.iter() { + if let Some(state_details) = (&htlc.state).into() { + inbound_details.push(InboundHTLCDetails{ + htlc_id: htlc.htlc_id, + amount_msat: htlc.amount_msat, + cltv_expiry: htlc.cltv_expiry, + payment_hash: htlc.payment_hash, + state: Some(holding_cell_states.remove(&htlc.htlc_id).unwrap_or(state_details)), + is_dust: htlc.amount_msat / 1000 < holder_dust_limit_success_sat, + }); + } + } + inbound_details + } + + /// Returns information on all pending outbound HTLCs. + pub fn get_pending_outbound_htlc_details(&self) -> Vec { + let mut outbound_details = Vec::new(); + let htlc_timeout_dust_limit = if self.get_channel_type().supports_anchors_zero_fee_htlc_tx() { + 0 + } else { + let dust_buffer_feerate = self.get_dust_buffer_feerate(None) as u64; + dust_buffer_feerate * htlc_success_tx_weight(self.get_channel_type()) / 1000 + }; + let holder_dust_limit_timeout_sat = htlc_timeout_dust_limit + self.holder_dust_limit_satoshis; + for htlc in self.pending_outbound_htlcs.iter() { + outbound_details.push(OutboundHTLCDetails{ + htlc_id: Some(htlc.htlc_id), + amount_msat: htlc.amount_msat, + cltv_expiry: htlc.cltv_expiry, + payment_hash: htlc.payment_hash, + skimmed_fee_msat: htlc.skimmed_fee_msat, + state: Some((&htlc.state).into()), + is_dust: htlc.amount_msat / 1000 < holder_dust_limit_timeout_sat, + }); + } + for holding_cell_update in self.holding_cell_htlc_updates.iter() { + if let HTLCUpdateAwaitingACK::AddHTLC { + amount_msat, + cltv_expiry, + payment_hash, + skimmed_fee_msat, + .. + } = *holding_cell_update { + outbound_details.push(OutboundHTLCDetails{ + htlc_id: None, + amount_msat: amount_msat, + cltv_expiry: cltv_expiry, + payment_hash: payment_hash, + skimmed_fee_msat: skimmed_fee_msat, + state: Some(OutboundHTLCStateDetails::AwaitingRemoteRevokeToAdd), + is_dust: amount_msat / 1000 < holder_dust_limit_timeout_sat, + }); + } + } + outbound_details + } + /// Get the available balances, see [`AvailableBalances`]'s fields for more info. /// Doesn't bother handling the /// if-we-removed-it-already-but-haven't-fully-resolved-they-can-still-send-an-inbound-HTLC @@ -2891,9 +3222,6 @@ impl ChannelContext where SP::Target: SignerProvider { (context.holder_selected_channel_reserve_satoshis, context.counterparty_selected_channel_reserve_satoshis) } - /// #SPLICING - pub(crate) fn funding_transaction_saved(&self) -> Option<&Transaction> { self.funding_transaction_saved.as_ref() } - /// Get the commitment tx fee for the local's (i.e. our) next commitment transaction based on the /// number of pending HTLCs that are on track to be in our next commitment tx. /// @@ -3088,15 +3416,17 @@ impl ChannelContext where SP::Target: SignerProvider { res } - fn if_unbroadcasted_funding(&self, f: F) -> Option - where F: Fn() -> Option { + fn if_unbroadcasted_funding(&self, f: F) -> Option where F: Fn() -> Option { match self.channel_state { ChannelState::FundingNegotiated => f(), - ChannelState::AwaitingChannelReady(flags) => if flags.is_set(AwaitingChannelReadyFlags::WAITING_FOR_BATCH) { - f() - } else { - None - }, + ChannelState::AwaitingChannelReady(flags) => + if flags.is_set(AwaitingChannelReadyFlags::WAITING_FOR_BATCH) || + flags.is_set(FundedStateFlags::MONITOR_UPDATE_IN_PROGRESS.into()) + { + f() + } else { + None + }, _ => None, } } @@ -3131,7 +3461,7 @@ impl ChannelContext where SP::Target: SignerProvider { /// those explicitly stated to be allowed after shutdown completes, eg some simple getters). /// Also returns the list of payment_hashes for channels which we can safely fail backwards /// immediately (others we will have to allow to time out). - pub fn force_shutdown(&mut self, should_broadcast: bool) -> ShutdownResult { + pub fn force_shutdown(&mut self, should_broadcast: bool, closure_reason: ClosureReason) -> ShutdownResult { // Note that we MUST only generate a monitor update that indicates force-closure - we're // called during initialization prior to the chain_monitor in the encompassing ChannelManager // being fully configured in some cases. Thus, its likely any monitor events we generate will @@ -3158,28 +3488,32 @@ impl ChannelContext where SP::Target: SignerProvider { // funding transaction, don't return a funding txo (which prevents providing the // monitor update to the user, even if we return one). // See test_duplicate_chan_id and test_pre_lockin_no_chan_closed_update for more. - let generate_monitor_update = match self.channel_state { - ChannelState::AwaitingChannelReady(_)|ChannelState::ChannelReady(_)|ChannelState::ShutdownComplete => true, - _ => false, - }; - if generate_monitor_update { + if !self.channel_state.is_pre_funded_state() { self.latest_monitor_update_id = CLOSED_CHANNEL_UPDATE_ID; - Some((self.get_counterparty_node_id(), funding_txo, ChannelMonitorUpdate { + Some((self.get_counterparty_node_id(), funding_txo, self.channel_id(), ChannelMonitorUpdate { update_id: self.latest_monitor_update_id, + counterparty_node_id: Some(self.counterparty_node_id), updates: vec![ChannelMonitorUpdateStep::ChannelForceClosed { should_broadcast }], + channel_id: Some(self.channel_id()), })) } else { None } } else { None }; let unbroadcasted_batch_funding_txid = self.unbroadcasted_batch_funding_txid(); + let unbroadcasted_funding_tx = self.unbroadcasted_funding(); self.channel_state = ChannelState::ShutdownComplete; self.update_time_counter += 1; ShutdownResult { + closure_reason, monitor_update, dropped_outbound_htlcs, unbroadcasted_batch_funding_txid, channel_id: self.channel_id, + user_channel_id: self.user_id, + channel_capacity_satoshis: self.channel_value_satoshis, counterparty_node_id: self.counterparty_node_id, + unbroadcasted_funding_tx, + channel_funding_txo: self.get_funding_txo(), } } @@ -3206,8 +3540,13 @@ impl ChannelContext where SP::Target: SignerProvider { .ok(); if funding_signed.is_none() { - log_trace!(logger, "Counterparty commitment signature not available for funding_signed message; setting signer_pending_funding"); - self.signer_pending_funding = true; + #[cfg(not(async_signing))] { + panic!("Failed to get signature for funding_signed"); + } + #[cfg(async_signing)] { + log_trace!(logger, "Counterparty commitment signature not available for funding_signed message; setting signer_pending_funding"); + self.signer_pending_funding = true; + } } else if self.signer_pending_funding { log_trace!(logger, "Counterparty commitment signature available for funding_signed message; clearing signer_pending_funding"); self.signer_pending_funding = false; @@ -3222,22 +3561,65 @@ impl ChannelContext where SP::Target: SignerProvider { } } + /// If we receive an error message when attempting to open a channel, it may only be a rejection + /// of the channel type we tried, not of our ability to open any channel at all. We can see if a + /// downgrade of channel features would be possible so that we can still open the channel. + pub(crate) fn maybe_downgrade_channel_features( + &mut self, fee_estimator: &LowerBoundedFeeEstimator + ) -> Result<(), ()> + where + F::Target: FeeEstimator + { + if !self.is_outbound() || + !matches!( + self.channel_state, ChannelState::NegotiatingFunding(flags) + if flags == NegotiatingFundingFlags::OUR_INIT_SENT + ) + { + return Err(()); + } + if self.channel_type == ChannelTypeFeatures::only_static_remote_key() { + // We've exhausted our options + return Err(()); + } + // We support opening a few different types of channels. Try removing our additional + // features one by one until we've either arrived at our default or the counterparty has + // accepted one. + // + // Due to the order below, we may not negotiate `option_anchors_zero_fee_htlc_tx` if the + // counterparty doesn't support `option_scid_privacy`. Since `get_initial_channel_type` + // checks whether the counterparty supports every feature, this would only happen if the + // counterparty is advertising the feature, but rejecting channels proposing the feature for + // whatever reason. + if self.channel_type.supports_anchors_zero_fee_htlc_tx() { + self.channel_type.clear_anchors_zero_fee_htlc_tx(); + self.feerate_per_kw = fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::NonAnchorChannelFee); + assert!(!self.channel_transaction_parameters.channel_type_features.supports_anchors_nonzero_fee_htlc_tx()); + } else if self.channel_type.supports_scid_privacy() { + self.channel_type.clear_scid_privacy(); + } else { + self.channel_type = ChannelTypeFeatures::only_static_remote_key(); + } + self.channel_transaction_parameters.channel_type_features = self.channel_type.clone(); + Ok(()) + } + // Interactive transaction construction #[cfg(dual_funding)] pub fn begin_interactive_funding_tx_construction( &mut self, dual_funding_context: &DualFundingChannelContext, signer_provider: &SP, entropy_source: &ES, holder_node_id: PublicKey, is_initiator: bool, - funding_inputs: Vec<(TxIn, Transaction)>, + funding_inputs: Vec<(TxIn, TransactionU16LenLimited)>, ) -> Result, APIError> where ES::Target: EntropySource { // Check that vouts exist for each TxIn in provided transactions. for (idx, input) in funding_inputs.iter().enumerate() { - if input.1.output.get(input.0.previous_output.vout as usize).is_none() { + if input.1.as_transaction().output.get(input.0.previous_output.vout as usize).is_none() { return Err(APIError::APIMisuseError { err: format!("Transaction with txid {} does not have an output with vout of {} corresponding to TxIn at funding_inputs[{}]", - input.1.txid(), input.0.previous_output.vout, idx) }); + input.1.as_transaction().txid(), input.0.previous_output.vout, idx) }); } } @@ -3254,18 +3636,18 @@ impl ChannelContext where SP::Target: SignerProvider { } } - let total_input_satoshis: u64 = funding_inputs_with_extra.iter().map(|input| input.1.output[input.0.previous_output.vout as usize].value).sum(); + let total_input_satoshis: u64 = funding_inputs_with_extra.iter().map(|input| input.1.as_transaction().output[input.0.previous_output.vout as usize].value).sum(); if total_input_satoshis < dual_funding_context.our_funding_satoshis { return Err(APIError::APIMisuseError { - err: format!("Total value of funding inputs must be at least funding amount. It was {}; {}", - total_input_satoshis, dual_funding_context.our_funding_satoshis) }); + err: format!("Total value of funding inputs must be at least funding amount. It was {} sats", + total_input_satoshis) }); } // Add output for funding tx let mut funding_outputs = Vec::new(); if is_initiator { // add output - // #SPLICING Note on splicing: channel value at this point is changed to the post-splice value, so no sepcial action needed + // #SPLICING Note on splicing: channel value at this point is changed to the post-splice value, so no special action needed funding_outputs.push(TxOut { value: self.get_value_satoshis(), script_pubkey: self.get_funding_redeemscript().to_v0_p2wsh(), @@ -3280,13 +3662,14 @@ impl ChannelContext where SP::Target: SignerProvider { let (tx_constructor, msg) = InteractiveTxConstructor::new( entropy_source, self.channel_id(), dual_funding_context.funding_feerate_sat_per_1000_weight, holder_node_id, self.counterparty_node_id, is_initiator, dual_funding_context.funding_tx_locktime, - funding_inputs_with_extra, funding_outputs, + funding_inputs_with_extra, funding_outputs, 0 ); self.interactive_tx_constructor = Some(tx_constructor); Ok(msg) } + #[cfg(dual_funding)] fn get_tx_abort_msg_from_abort_reason(&self, reason: AbortReason) -> msgs::TxAbort { let msg = match reason { AbortReason::InvalidStateTransition => "State transition was invalid", @@ -3304,6 +3687,8 @@ impl ChannelContext where SP::Target: SignerProvider { AbortReason::BelowDustLimit => "Output amount is below the dust limit", AbortReason::InvalidOutputScript => "The output script is non-standard", AbortReason::InsufficientFees => "Insufficient fees paid", + AbortReason::OutputsValueExceedsInputsValue => "Total value of outputs exceeds total value of inputs", + AbortReason::InvalidTx => "The transaction is invalid", }.to_string(); msgs::TxAbort { @@ -3312,6 +3697,7 @@ impl ChannelContext where SP::Target: SignerProvider { } } + #[cfg(dual_funding)] pub fn tx_add_input(&mut self, msg: &msgs::TxAddInput) -> Result { match self.interactive_tx_constructor { Some(ref mut tx_constructor) => tx_constructor.handle_tx_add_input(msg).map_err( @@ -3323,6 +3709,7 @@ impl ChannelContext where SP::Target: SignerProvider { } } + #[cfg(dual_funding)] pub fn tx_add_output(&mut self, msg: &msgs::TxAddOutput)-> Result { match self.interactive_tx_constructor { Some(ref mut tx_constructor) => tx_constructor.handle_tx_add_output(msg).map_err( @@ -3334,6 +3721,7 @@ impl ChannelContext where SP::Target: SignerProvider { } } + #[cfg(dual_funding)] pub fn tx_remove_input(&mut self, msg: &msgs::TxRemoveInput)-> Result { match self.interactive_tx_constructor { Some(ref mut tx_constructor) => tx_constructor.handle_tx_remove_input(msg).map_err( @@ -3345,6 +3733,7 @@ impl ChannelContext where SP::Target: SignerProvider { } } + #[cfg(dual_funding)] pub fn tx_remove_output(&mut self, msg: &msgs::TxRemoveOutput)-> Result { match self.interactive_tx_constructor { Some(ref mut tx_constructor) => tx_constructor.handle_tx_remove_output(msg).map_err( @@ -3356,6 +3745,7 @@ impl ChannelContext where SP::Target: SignerProvider { } } + #[cfg(dual_funding)] pub fn tx_complete(&mut self, msg: &msgs::TxComplete) -> Result<(Option, Option), msgs::TxAbort> { match self.interactive_tx_constructor { @@ -3368,6 +3758,7 @@ impl ChannelContext where SP::Target: SignerProvider { } } + #[cfg(dual_funding)] pub fn tx_abort(&self, _msg: &msgs::TxAbort)-> Result { todo!(); } @@ -3587,11 +3978,11 @@ pub(crate) fn get_legacy_default_holder_selected_channel_reserve_satoshis(channe /// /// This is used both for outbound and inbound channels and has lower bound /// of `dust_limit_satoshis`. +#[cfg(dual_funding)] fn get_v2_channel_reserve_satoshis(channel_value_satoshis: u64, dust_limit_satoshis: u64) -> u64 { - let channel_reserve_proportional_millionths = 10_000; // Fixed at 1% in spec. - let calculated_reserve = - channel_value_satoshis.saturating_mul(channel_reserve_proportional_millionths) / 1_000_000; - cmp::min(channel_value_satoshis, cmp::max(calculated_reserve, dust_limit_satoshis)) + // Fixed at 1% of channel value by spec. + let (q, _) = channel_value_satoshis.overflowing_div(100); + cmp::min(channel_value_satoshis, cmp::max(q, dust_limit_satoshis)) } // Get the fee cost in SATS of a commitment tx with a given number of HTLC outputs. @@ -3609,8 +4000,9 @@ pub(crate) fn commit_tx_fee_msat(feerate_per_kw: u32, num_htlcs: usize, channel_ (commitment_tx_base_weight(channel_type_features) + num_htlcs as u64 * COMMITMENT_TX_WEIGHT_PER_HTLC) * feerate_per_kw as u64 / 1000 * 1000 } +#[cfg(dual_funding)] pub(super) fn maybe_add_funding_change_output(signer_provider: &SP, is_initiator: bool, - our_funding_satoshis: u64, funding_inputs: &Vec<(TxIn, Transaction)>, + our_funding_satoshis: u64, funding_inputs: &Vec<(TxIn, TransactionU16LenLimited)>, funding_outputs: &mut Vec, funding_feerate_sat_per_1000_weight: u32, total_input_satoshis: u64, holder_dust_limit_satoshis: u64, channel_keys_id: [u8; 32], ) -> Result, ChannelError> where @@ -3667,7 +4059,6 @@ pub(super) fn maybe_add_funding_change_output(signer_provider: &SP, i /// Context for dual-funded channels. #[cfg(dual_funding)] -#[derive(Clone)] pub(super) struct DualFundingChannelContext { /// The amount in satoshis we will be contributing to the channel. pub our_funding_satoshis: u64, @@ -3699,6 +4090,62 @@ struct CommitmentTxInfoCached { feerate: u32, } +/// Contents of a wire message that fails an HTLC backwards. Useful for [`Channel::fail_htlc`] to +/// fail with either [`msgs::UpdateFailMalformedHTLC`] or [`msgs::UpdateFailHTLC`] as needed. +trait FailHTLCContents { + type Message: FailHTLCMessageName; + fn to_message(self, htlc_id: u64, channel_id: ChannelId) -> Self::Message; + fn to_inbound_htlc_state(self) -> InboundHTLCState; + fn to_htlc_update_awaiting_ack(self, htlc_id: u64) -> HTLCUpdateAwaitingACK; +} +impl FailHTLCContents for msgs::OnionErrorPacket { + type Message = msgs::UpdateFailHTLC; + fn to_message(self, htlc_id: u64, channel_id: ChannelId) -> Self::Message { + msgs::UpdateFailHTLC { htlc_id, channel_id, reason: self } + } + fn to_inbound_htlc_state(self) -> InboundHTLCState { + InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::FailRelay(self)) + } + fn to_htlc_update_awaiting_ack(self, htlc_id: u64) -> HTLCUpdateAwaitingACK { + HTLCUpdateAwaitingACK::FailHTLC { htlc_id, err_packet: self } + } +} +impl FailHTLCContents for ([u8; 32], u16) { + type Message = msgs::UpdateFailMalformedHTLC; + fn to_message(self, htlc_id: u64, channel_id: ChannelId) -> Self::Message { + msgs::UpdateFailMalformedHTLC { + htlc_id, + channel_id, + sha256_of_onion: self.0, + failure_code: self.1 + } + } + fn to_inbound_htlc_state(self) -> InboundHTLCState { + InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::FailMalformed(self)) + } + fn to_htlc_update_awaiting_ack(self, htlc_id: u64) -> HTLCUpdateAwaitingACK { + HTLCUpdateAwaitingACK::FailMalformedHTLC { + htlc_id, + sha256_of_onion: self.0, + failure_code: self.1 + } + } +} + +trait FailHTLCMessageName { + fn name() -> &'static str; +} +impl FailHTLCMessageName for msgs::UpdateFailHTLC { + fn name() -> &'static str { + "update_fail_htlc" + } +} +impl FailHTLCMessageName for msgs::UpdateFailMalformedHTLC { + fn name() -> &'static str { + "update_fail_malformed_htlc" + } +} + impl Channel where SP::Target: SignerProvider, ::EcdsaSigner: WriteableEcdsaChannelSigner @@ -3815,7 +4262,7 @@ impl Channel where where L::Target: Logger { // Assert that we'll add the HTLC claim to the holding cell in `get_update_fulfill_htlc` // (see equivalent if condition there). - assert!(self.context.channel_state.should_force_holding_cell()); + assert!(!self.context.channel_state.can_generate_new_commitment()); let mon_update_id = self.context.latest_monitor_update_id; // Forget the ChannelMonitor update let fulfill_resp = self.get_update_fulfill_htlc(htlc_id_arg, payment_preimage_arg, logger); self.context.latest_monitor_update_id = mon_update_id; @@ -3879,12 +4326,14 @@ impl Channel where self.context.latest_monitor_update_id += 1; let monitor_update = ChannelMonitorUpdate { update_id: self.context.latest_monitor_update_id, + counterparty_node_id: Some(self.context.counterparty_node_id), updates: vec![ChannelMonitorUpdateStep::PaymentPreimage { payment_preimage: payment_preimage_arg.clone(), }], + channel_id: Some(self.context.channel_id()), }; - if self.context.channel_state.should_force_holding_cell() { + if !self.context.channel_state.can_generate_new_commitment() { // Note that this condition is the same as the assertion in // `claim_htlc_while_disconnected_dropping_mon_update` and must match exactly - // `claim_htlc_while_disconnected_dropping_mon_update` would not work correctly if we @@ -3900,7 +4349,9 @@ impl Channel where return UpdateFulfillFetch::DuplicateClaim {}; } }, - &HTLCUpdateAwaitingACK::FailHTLC { htlc_id, .. } => { + &HTLCUpdateAwaitingACK::FailHTLC { htlc_id, .. } | + &HTLCUpdateAwaitingACK::FailMalformedHTLC { htlc_id, .. } => + { if htlc_id_arg == htlc_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 @@ -3997,6 +4448,17 @@ impl Channel where .map(|msg_opt| assert!(msg_opt.is_none(), "We forced holding cell?")) } + /// Used for failing back with [`msgs::UpdateFailMalformedHTLC`]. For now, this is used when we + /// want to fail blinded HTLCs where we are not the intro node. + /// + /// See [`Self::queue_fail_htlc`] for more info. + pub fn queue_fail_malformed_htlc( + &mut self, htlc_id_arg: u64, failure_code: u16, sha256_of_onion: [u8; 32], logger: &L + ) -> Result<(), ChannelError> where L::Target: Logger { + self.fail_htlc(htlc_id_arg, (sha256_of_onion, failure_code), true, logger) + .map(|msg_opt| assert!(msg_opt.is_none(), "We forced holding cell?")) + } + /// We can only have one resolution per HTLC. In some cases around reconnect, we may fulfill /// an HTLC more than once or fulfill once and then attempt to fail after reconnect. We cannot, /// however, fail more than once as we wait for an upstream failure to be irrevocably committed @@ -4005,8 +4467,10 @@ impl Channel where /// If we do fail twice, we `debug_assert!(false)` and return `Ok(None)`. Thus, this will always /// return `Ok(_)` if preconditions are met. In any case, `Err`s will only be /// [`ChannelError::Ignore`]. - fn fail_htlc(&mut self, htlc_id_arg: u64, err_packet: msgs::OnionErrorPacket, mut force_holding_cell: bool, logger: &L) - -> Result, ChannelError> where L::Target: Logger { + fn fail_htlc( + &mut self, htlc_id_arg: u64, err_contents: E, mut force_holding_cell: bool, + logger: &L + ) -> Result, ChannelError> where L::Target: Logger { if !matches!(self.context.channel_state, ChannelState::ChannelReady(_)) { panic!("Was asked to fail an HTLC when channel was not in an operational state"); } @@ -4043,7 +4507,7 @@ impl Channel where return Ok(None); } - if self.context.channel_state.should_force_holding_cell() { + if !self.context.channel_state.can_generate_new_commitment() { debug_assert!(force_holding_cell, "!force_holding_cell is only called when emptying the holding cell, so we shouldn't end up back in it!"); force_holding_cell = true; } @@ -4059,7 +4523,9 @@ impl Channel where return Ok(None); } }, - &HTLCUpdateAwaitingACK::FailHTLC { htlc_id, .. } => { + &HTLCUpdateAwaitingACK::FailHTLC { htlc_id, .. } | + &HTLCUpdateAwaitingACK::FailMalformedHTLC { htlc_id, .. } => + { if htlc_id_arg == htlc_id { debug_assert!(false, "Tried to fail an HTLC that was already failed"); return Err(ChannelError::Ignore("Unable to find a pending HTLC which matched the given HTLC ID".to_owned())); @@ -4069,24 +4535,18 @@ impl Channel where } } 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, - }); + self.context.holding_cell_htlc_updates.push(err_contents.to_htlc_update_awaiting_ack(htlc_id_arg)); return Ok(None); } - log_trace!(logger, "Failing HTLC ID {} back with a update_fail_htlc message in channel {}.", htlc_id_arg, &self.context.channel_id()); + log_trace!(logger, "Failing HTLC ID {} back with {} message in channel {}.", htlc_id_arg, + E::Message::name(), &self.context.channel_id()); { let htlc = &mut self.context.pending_inbound_htlcs[pending_idx]; - htlc.state = InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::FailRelay(err_packet.clone())); + htlc.state = err_contents.clone().to_inbound_htlc_state(); } - Ok(Some(msgs::UpdateFailHTLC { - channel_id: self.context.channel_id(), - htlc_id: htlc_id_arg, - reason: err_packet - })) + Ok(Some(err_contents.to_message(htlc_id_arg, self.context.channel_id()))) } // Message handlers: @@ -4099,6 +4559,20 @@ impl Channel where self.context.channel_state.clear_waiting_for_batch(); } + /// Unsets the existing funding information. + /// + /// This must only be used if the channel has not yet completed funding and has not been used. + /// + /// Further, the channel must be immediately shut down after this with a call to + /// [`ChannelContext::force_shutdown`]. + pub fn unset_funding_info(&mut self, temporary_channel_id: ChannelId) { + debug_assert!(matches!( + self.context.channel_state, ChannelState::AwaitingChannelReady(_) + )); + self.context.channel_transaction_parameters.funding_outpoint = None; + self.context.channel_id = temporary_channel_id; + } + /// 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. @@ -4129,12 +4603,12 @@ impl Channel where let mut check_reconnection = false; match &self.context.channel_state { ChannelState::AwaitingChannelReady(flags) => { - let flags = *flags & !FundedStateFlags::ALL; + let flags = flags.clone().clear(FundedStateFlags::ALL.into()); debug_assert!(!flags.is_set(AwaitingChannelReadyFlags::OUR_CHANNEL_READY) || !flags.is_set(AwaitingChannelReadyFlags::WAITING_FOR_BATCH)); - if flags & !AwaitingChannelReadyFlags::WAITING_FOR_BATCH == AwaitingChannelReadyFlags::THEIR_CHANNEL_READY { + if flags.clone().clear(AwaitingChannelReadyFlags::WAITING_FOR_BATCH) == AwaitingChannelReadyFlags::THEIR_CHANNEL_READY { // If we reconnected before sending our `channel_ready` they may still resend theirs. check_reconnection = true; - } else if (flags & !AwaitingChannelReadyFlags::WAITING_FOR_BATCH).is_empty() { + } else if flags.clone().clear(AwaitingChannelReadyFlags::WAITING_FOR_BATCH).is_empty() { self.context.channel_state.set_their_channel_ready(); } else if flags == AwaitingChannelReadyFlags::OUR_CHANNEL_READY { self.context.channel_state = ChannelState::ChannelReady(self.context.channel_state.with_funded_state_flags_mask().into()); @@ -4179,7 +4653,7 @@ impl Channel where log_info!(logger, "Received channel_ready from peer for channel {}", &self.context.channel_id()); - Ok(self.get_announcement_sigs(node_signer, chain_hash, user_config, best_block.height(), logger)) + Ok(self.get_announcement_sigs(node_signer, chain_hash, user_config, best_block.height, logger)) } pub fn update_add_htlc( @@ -4384,7 +4858,7 @@ impl Channel where Err(ChannelError::Close("Remote tried to fulfill/fail an HTLC we couldn't find".to_owned())) } - pub fn update_fulfill_htlc(&mut self, msg: &msgs::UpdateFulfillHTLC) -> Result<(HTLCSource, u64), ChannelError> { + pub fn update_fulfill_htlc(&mut self, msg: &msgs::UpdateFulfillHTLC) -> Result<(HTLCSource, u64, Option), ChannelError> { if !matches!(self.context.channel_state, ChannelState::ChannelReady(_)) { return Err(ChannelError::Close("Got fulfill HTLC message when channel was not in an operational state".to_owned())); } @@ -4392,7 +4866,7 @@ impl Channel where return Err(ChannelError::Close("Peer sent update_fulfill_htlc when we needed a channel_reestablish".to_owned())); } - self.mark_outbound_htlc_removed(msg.htlc_id, Some(msg.payment_preimage), None).map(|htlc| (htlc.source.clone(), htlc.amount_msat)) + self.mark_outbound_htlc_removed(msg.htlc_id, Some(msg.payment_preimage), None).map(|htlc| (htlc.source.clone(), htlc.amount_msat, htlc.skimmed_fee_msat)) } pub fn update_fail_htlc(&mut self, msg: &msgs::UpdateFailHTLC, fail_reason: HTLCFailReason) -> Result<(), ChannelError> { @@ -4426,13 +4900,13 @@ impl Channel where where L::Target: Logger { if !matches!(self.context.channel_state, ChannelState::FundingNegotiated) { - return Err(ChannelError::Close(format!("Received initial commitment_signed before funding transaction constructed! {:?}", self.context.channel_state))); + return Err(ChannelError::Close("Received initial commitment_signed before funding transaction constructed!".to_owned())); } let is_splice = self.context.pending_splice_post.is_some(); if !is_splice { 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.cur_counterparty_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER || + self.context.cur_holder_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER { panic!("Should not have advanced channel commitment tx numbers prior to funding_created"); } } @@ -4486,7 +4960,7 @@ impl Channel where &self.context.channel_transaction_parameters, funding_redeemscript.clone(), self.context.channel_value_satoshis, obscure_factor, - holder_commitment_tx, best_block, self.context.counterparty_node_id); + holder_commitment_tx, best_block, self.context.counterparty_node_id, self.context.channel_id()); channel_monitor.provide_initial_counterparty_commitment_tx( counterparty_initial_bitcoin_tx.txid, Vec::new(), @@ -4497,13 +4971,13 @@ impl Channel where counterparty_initial_commitment_tx.to_countersignatory_value_sat(), logger); assert!(!self.context.channel_state.is_monitor_update_in_progress()); // We have no had any monitor(s) yet to fail update! - self.context.channel_state = ChannelState::AwaitingChannelReady(AwaitingChannelReadyFlags::new()); self.context.cur_holder_commitment_transaction_number -= 1; self.context.cur_counterparty_commitment_transaction_number -= 1; log_info!(logger, "Received initial commitment_signed from peer for channel {}", &self.context.channel_id()); let need_channel_ready = self.check_get_channel_ready(0).is_some(); + self.context.channel_state = ChannelState::AwaitingChannelReady(AwaitingChannelReadyFlags::new()); self.monitor_updating_paused(false, false, need_channel_ready, Vec::new(), Vec::new(), Vec::new()); Ok(channel_monitor) @@ -4679,12 +5153,14 @@ impl Channel where self.context.latest_monitor_update_id += 1; let mut monitor_update = ChannelMonitorUpdate { update_id: self.context.latest_monitor_update_id, + counterparty_node_id: Some(self.context.counterparty_node_id), updates: vec![ChannelMonitorUpdateStep::LatestHolderCommitmentTXInfo { commitment_tx: holder_commitment_tx, htlc_outputs: htlcs_and_sigs, claimed_htlcs, nondust_htlc_sources, - }] + }], + channel_id: Some(self.context.channel_id()), }; self.context.cur_holder_commitment_transaction_number -= 1; @@ -4739,7 +5215,7 @@ impl Channel where ) -> (Option, Vec<(HTLCSource, PaymentHash)>) where F::Target: FeeEstimator, L::Target: Logger { - if matches!(self.context.channel_state, ChannelState::ChannelReady(_)) && !self.context.channel_state.should_force_holding_cell() { + if matches!(self.context.channel_state, ChannelState::ChannelReady(_)) && self.context.channel_state.can_generate_new_commitment() { self.free_holding_cell_htlcs(fee_estimator, logger) } else { (None, Vec::new()) } } @@ -4758,7 +5234,9 @@ impl Channel where let mut monitor_update = ChannelMonitorUpdate { update_id: self.context.latest_monitor_update_id + 1, // We don't increment this yet! + counterparty_node_id: Some(self.context.counterparty_node_id), updates: Vec::new(), + channel_id: Some(self.context.channel_id()), }; let mut htlc_updates = Vec::new(); @@ -4773,7 +5251,7 @@ impl Channel where // the limit. In case it's less rare than I anticipate, we may want to revisit // handling this case better and maybe fulfilling some of the HTLCs while attempting // to rebalance channels. - match &htlc_update { + let fail_htlc_res = match &htlc_update { &HTLCUpdateAwaitingACK::AddHTLC { amount_msat, cltv_expiry, ref payment_hash, ref source, ref onion_routing_packet, skimmed_fee_msat, blinding_point, .. @@ -4801,6 +5279,7 @@ impl Channel where } } } + None }, &HTLCUpdateAwaitingACK::ClaimHTLC { ref payment_preimage, htlc_id, .. } => { // If an HTLC claim was previously added to the holding cell (via @@ -4814,26 +5293,33 @@ impl Channel where { monitor_update } else { unreachable!() }; update_fulfill_count += 1; monitor_update.updates.append(&mut additional_monitor_update.updates); + None }, &HTLCUpdateAwaitingACK::FailHTLC { htlc_id, ref err_packet } => { - match self.fail_htlc(htlc_id, err_packet.clone(), false, logger) { - Ok(update_fail_msg_option) => { - // If an HTLC failure was previously added to the holding cell (via - // `queue_fail_htlc`) then generating the fail message itself must - // 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. - debug_assert!(update_fail_msg_option.is_some()); - update_fail_count += 1; - }, - Err(e) => { - if let ChannelError::Ignore(_) = e {} - else { - panic!("Got a non-IgnoreError action trying to fail holding cell HTLC"); - } - } - } + Some(self.fail_htlc(htlc_id, err_packet.clone(), false, logger) + .map(|fail_msg_opt| fail_msg_opt.map(|_| ()))) }, + &HTLCUpdateAwaitingACK::FailMalformedHTLC { htlc_id, failure_code, sha256_of_onion } => { + Some(self.fail_htlc(htlc_id, (sha256_of_onion, failure_code), false, logger) + .map(|fail_msg_opt| fail_msg_opt.map(|_| ()))) + } + }; + if let Some(res) = fail_htlc_res { + match res { + Ok(fail_msg_opt) => { + // If an HTLC failure was previously added to the holding cell (via + // `queue_fail_{malformed_}htlc`) then generating the fail message itself must + // 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. + debug_assert!(fail_msg_opt.is_some()); + update_fail_count += 1; + }, + Err(ChannelError::Ignore(_)) => {}, + Err(_) => { + panic!("Got a non-IgnoreError action trying to fail holding cell HTLC"); + }, + } } } if update_add_count == 0 && update_fulfill_count == 0 && update_fail_count == 0 && self.context.holding_cell_update_fee.is_none() { @@ -4924,10 +5410,12 @@ impl Channel where self.context.latest_monitor_update_id += 1; let mut monitor_update = ChannelMonitorUpdate { update_id: self.context.latest_monitor_update_id, + counterparty_node_id: Some(self.context.counterparty_node_id), updates: vec![ChannelMonitorUpdateStep::CommitmentSecret { idx: self.context.cur_counterparty_commitment_transaction_number + 1, secret: msg.per_commitment_secret, }], + channel_id: Some(self.context.channel_id()), }; // Update state now that we've passed all the can-fail calls... @@ -5147,8 +5635,7 @@ impl Channel where } #[cfg(dual_funding)] - pub fn tx_signatures(&mut self, msg: &msgs::TxSignatures, logger: &L) - -> Result<(Option, Option), ChannelError> + pub fn tx_signatures(&mut self, msg: &msgs::TxSignatures, logger: &L) -> Result<(Option, Option), ChannelError> where L::Target: Logger { if let Some(ref mut signing_session) = self.interactive_tx_signing_session { let expected_witness_count = signing_session.counterparty_inputs_count(); @@ -5203,10 +5690,12 @@ impl Channel where } } + #[cfg(dual_funding)] pub fn tx_init_rbf(&self, _msg: &msgs::TxInitRbf)-> Result { todo!(); } + #[cfg(dual_funding)] pub fn tx_ack_rbf(&self, _msg: &msgs::TxAckRbf)-> Result { todo!(); } @@ -5412,8 +5901,8 @@ impl Channel where // first received the funding_signed. let mut funding_broadcastable = if self.context.is_outbound() && - matches!(self.context.channel_state, ChannelState::AwaitingChannelReady(flags) if !flags.is_set(AwaitingChannelReadyFlags::WAITING_FOR_BATCH)) || - matches!(self.context.channel_state, ChannelState::ChannelReady(_)) + (matches!(self.context.channel_state, ChannelState::AwaitingChannelReady(flags) if !flags.is_set(AwaitingChannelReadyFlags::WAITING_FOR_BATCH)) || + matches!(self.context.channel_state, ChannelState::ChannelReady(_))) { self.context.funding_transaction.take() } else { None }; @@ -5515,7 +6004,7 @@ impl Channel where /// Indicates that the signer may have some signatures for us, so we should retry if we're /// blocked. - #[allow(unused)] + #[cfg(async_signing)] pub fn signer_maybe_unblocked(&mut self, logger: &L) -> SignerResumeUpdates where L::Target: Logger { let commitment_update = if self.context.signer_pending_commitment_update { self.get_last_commitment_update_for_send(logger).ok() @@ -5619,11 +6108,16 @@ impl Channel where } update } else { - if !self.context.signer_pending_commitment_update { - log_trace!(logger, "Commitment update awaiting signer: setting signer_pending_commitment_update"); - self.context.signer_pending_commitment_update = true; + #[cfg(not(async_signing))] { + panic!("Failed to get signature for new commitment state"); + } + #[cfg(async_signing)] { + if !self.context.signer_pending_commitment_update { + log_trace!(logger, "Commitment update awaiting signer: setting signer_pending_commitment_update"); + self.context.signer_pending_commitment_update = true; + } + return Err(()); } - return Err(()); }; Ok(msgs::CommitmentUpdate { update_add_htlcs, update_fulfill_htlcs, update_fail_htlcs, update_fail_malformed_htlcs, update_fee, @@ -5712,7 +6206,7 @@ impl Channel where let shutdown_msg = self.get_outbound_shutdown(); - let announcement_sigs = self.get_announcement_sigs(node_signer, chain_hash, user_config, best_block.height(), logger); + let announcement_sigs = self.get_announcement_sigs(node_signer, chain_hash, user_config, best_block.height, logger); if matches!(self.context.channel_state, ChannelState::AwaitingChannelReady(_)) { // If we're waiting on a monitor update, we shouldn't re-send any channel_ready's. @@ -6049,9 +6543,11 @@ impl Channel where self.context.latest_monitor_update_id += 1; let monitor_update = ChannelMonitorUpdate { update_id: self.context.latest_monitor_update_id, + counterparty_node_id: Some(self.context.counterparty_node_id), updates: vec![ChannelMonitorUpdateStep::ShutdownScript { scriptpubkey: self.get_closing_scriptpubkey(), }], + channel_id: Some(self.context.channel_id()), }; self.monitor_updating_paused(false, false, false, Vec::new(), Vec::new(), Vec::new()); self.push_ret_blockable_mon_update(monitor_update) @@ -6158,15 +6654,26 @@ impl Channel where } } + let closure_reason = if self.initiated_shutdown() { + ClosureReason::LocallyInitiatedCooperativeClosure + } else { + ClosureReason::CounterpartyInitiatedCooperativeClosure + }; + assert!(self.context.shutdown_scriptpubkey.is_some()); if let Some((last_fee, sig)) = self.context.last_sent_closing_fee { if last_fee == msg.fee_satoshis { let shutdown_result = ShutdownResult { + closure_reason, monitor_update: None, dropped_outbound_htlcs: Vec::new(), unbroadcasted_batch_funding_txid: self.context.unbroadcasted_batch_funding_txid(), channel_id: self.context.channel_id, + user_channel_id: self.context.user_id, + channel_capacity_satoshis: self.context.channel_value_satoshis, counterparty_node_id: self.context.counterparty_node_id, + unbroadcasted_funding_tx: self.context.unbroadcasted_funding(), + channel_funding_txo: self.context.get_funding_txo(), }; let tx = self.build_signed_closing_transaction(&mut closing_tx, &msg.signature, &sig); self.context.channel_state = ChannelState::ShutdownComplete; @@ -6192,11 +6699,16 @@ impl Channel where .map_err(|_| ChannelError::Close("External signer refused to sign closing transaction".to_owned()))?; let (signed_tx, shutdown_result) = if $new_fee == msg.fee_satoshis { let shutdown_result = ShutdownResult { + closure_reason, monitor_update: None, dropped_outbound_htlcs: Vec::new(), unbroadcasted_batch_funding_txid: self.context.unbroadcasted_batch_funding_txid(), channel_id: self.context.channel_id, + user_channel_id: self.context.user_id, + channel_capacity_satoshis: self.context.channel_value_satoshis, counterparty_node_id: self.context.counterparty_node_id, + unbroadcasted_funding_tx: self.context.unbroadcasted_funding(), + channel_funding_txo: self.context.get_funding_txo(), }; self.context.channel_state = ChannelState::ShutdownComplete; self.context.update_time_counter += 1; @@ -6407,7 +6919,7 @@ impl Channel where if !self.is_awaiting_monitor_update() { return false; } if matches!( self.context.channel_state, ChannelState::AwaitingChannelReady(flags) - if (flags & !(AwaitingChannelReadyFlags::THEIR_CHANNEL_READY | FundedStateFlags::PEER_DISCONNECTED | FundedStateFlags::MONITOR_UPDATE_IN_PROGRESS | AwaitingChannelReadyFlags::WAITING_FOR_BATCH)).is_empty() + if flags.clone().clear(AwaitingChannelReadyFlags::THEIR_CHANNEL_READY | FundedStateFlags::PEER_DISCONNECTED | FundedStateFlags::MONITOR_UPDATE_IN_PROGRESS | AwaitingChannelReadyFlags::WAITING_FOR_BATCH).is_empty() ) { // If we're not a 0conf channel, we'll be waiting on a monitor update with only // AwaitingChannelReady set, though our peer could have sent their channel_ready. @@ -6452,6 +6964,11 @@ impl Channel where self.context.channel_state.is_local_shutdown_sent() } + /// Returns true if we initiated to shut down the channel. + pub fn initiated_shutdown(&self) -> bool { + self.context.local_initiated_shutdown.is_some() + } + /// Returns true if this channel is fully shut down. True here implies that no further actions /// may/will be taken on this channel, and thus this object should be freed. Any future changes /// will be handled appropriately by the chain monitor. @@ -6493,14 +7010,14 @@ impl Channel where // Note that we don't include ChannelState::WaitingForBatch as we don't want to send // channel_ready until the entire batch is ready. - let need_commitment_update = if matches!(self.context.channel_state, ChannelState::AwaitingChannelReady(f) if (f & !FundedStateFlags::ALL).is_empty()) { + let need_commitment_update = if matches!(self.context.channel_state, ChannelState::AwaitingChannelReady(f) if f.clone().clear(FundedStateFlags::ALL.into()).is_empty()) { self.context.channel_state.set_our_channel_ready(); true - } else if matches!(self.context.channel_state, ChannelState::AwaitingChannelReady(f) if f & !FundedStateFlags::ALL == AwaitingChannelReadyFlags::THEIR_CHANNEL_READY) { + } else if matches!(self.context.channel_state, ChannelState::AwaitingChannelReady(f) if f.clone().clear(FundedStateFlags::ALL.into()) == AwaitingChannelReadyFlags::THEIR_CHANNEL_READY) { self.context.channel_state = ChannelState::ChannelReady(self.context.channel_state.with_funded_state_flags_mask().into()); self.context.update_time_counter += 1; true - } else if matches!(self.context.channel_state, ChannelState::AwaitingChannelReady(f) if f & !FundedStateFlags::ALL == AwaitingChannelReadyFlags::OUR_CHANNEL_READY) { + } else if matches!(self.context.channel_state, ChannelState::AwaitingChannelReady(f) if f.clone().clear(FundedStateFlags::ALL.into()) == AwaitingChannelReadyFlags::OUR_CHANNEL_READY) { // We got a reorg but not enough to trigger a force close, just ignore. false } else { @@ -6737,7 +7254,7 @@ impl Channel where // larger. If we don't know that time has moved forward, we can just set it to the last // time we saw and it will be ignored. let best_time = self.context.update_time_counter; - match self.do_best_block_updated(reorg_height, best_time, None::<(ChainHash, &&NodeSigner, &UserConfig)>, logger) { + match self.do_best_block_updated(reorg_height, best_time, None::<(ChainHash, &&dyn NodeSigner, &UserConfig)>, logger) { Ok((channel_ready, timed_out_htlcs, announcement_sigs)) => { assert!(channel_ready.is_none(), "We can't generate a funding with 0 confirmations?"); assert!(timed_out_htlcs.is_empty(), "We can't have accepted HTLCs with a timeout before our funding confirmation?"); @@ -7122,7 +7639,7 @@ impl Channel where return Err(ChannelError::Ignore("Cannot send an HTLC while disconnected from channel counterparty".to_owned())); } - let need_holding_cell = self.context.channel_state.should_force_holding_cell(); + let need_holding_cell = !self.context.channel_state.can_generate_new_commitment(); log_debug!(logger, "Pushing new outbound HTLC with hash {} for {} msat {}", payment_hash, amount_msat, if force_holding_cell { "into holding cell" } @@ -7219,6 +7736,7 @@ impl Channel where self.context.latest_monitor_update_id += 1; let monitor_update = ChannelMonitorUpdate { update_id: self.context.latest_monitor_update_id, + counterparty_node_id: Some(self.context.counterparty_node_id), updates: vec![ChannelMonitorUpdateStep::LatestCounterpartyCommitmentTXInfo { commitment_txid: counterparty_commitment_txid, htlc_outputs: htlcs.clone(), @@ -7227,7 +7745,8 @@ impl Channel where 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()), - }] + }], + channel_id: Some(self.context.channel_id()), }; self.context.channel_state.set_awaiting_remote_revoke(); monitor_update @@ -7411,15 +7930,18 @@ impl Channel where // From here on out, we may not fail! self.context.target_closing_feerate_sats_per_kw = target_feerate_sats_per_kw; self.context.channel_state.set_local_shutdown_sent(); + self.context.local_initiated_shutdown = Some(()); self.context.update_time_counter += 1; let monitor_update = if update_shutdown_script { self.context.latest_monitor_update_id += 1; let monitor_update = ChannelMonitorUpdate { update_id: self.context.latest_monitor_update_id, + counterparty_node_id: Some(self.context.counterparty_node_id), updates: vec![ChannelMonitorUpdateStep::ShutdownScript { scriptpubkey: self.get_closing_scriptpubkey(), }], + channel_id: Some(self.context.channel_id()), }; self.monitor_updating_paused(false, false, false, Vec::new(), Vec::new(), Vec::new()); self.push_ret_blockable_mon_update(monitor_update) @@ -7477,13 +7999,12 @@ impl OutboundV1Channel where SP::Target: SignerProvider { where ES::Target: EntropySource, F::Target: FeeEstimator { - let temporary_channel_id = temporary_channel_id.unwrap_or_else(|| ChannelId::temporary_from_entropy_source(entropy_source)); - let holder_selected_channel_reserve_satoshis = get_holder_selected_channel_reserve_satoshis(channel_value_satoshis, config); if holder_selected_channel_reserve_satoshis < MIN_CHAN_DUST_LIMIT_SATOSHIS { // Protocol level safety check in place, although it should never happen because // of `MIN_THEIR_CHAN_RESERVE_SATOSHIS` - return Err(APIError::APIMisuseError { err: format!("Holder selected channel reserve below implemention limit dust_limit_satoshis {}", holder_selected_channel_reserve_satoshis) }); + return Err(APIError::APIMisuseError { err: format!("Holder selected channel reserve below \ + implemention limit dust_limit_satoshis {}", holder_selected_channel_reserve_satoshis) }); } let channel_keys_id = signer_provider.generate_channel_keys_id(false, channel_value_satoshis, user_id); @@ -7576,7 +8097,7 @@ impl OutboundV1Channel where SP::Target: SignerProvider { // Now that we're past error-generating stuff, update our local state: self.context.channel_state = ChannelState::FundingNegotiated; - self.context.channel_id = funding_txo.to_channel_id(); + self.context.channel_id = ChannelId::v1_from_funding_outpoint(funding_txo); // 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. @@ -7592,9 +8113,14 @@ impl OutboundV1Channel where SP::Target: SignerProvider { let funding_created = self.get_funding_created_msg(logger); if funding_created.is_none() { - if !self.context.signer_pending_funding { - log_trace!(logger, "funding_created awaiting signer; setting signer_pending_funding"); - self.context.signer_pending_funding = true; + #[cfg(not(async_signing))] { + panic!("Failed to get signature for new funding creation"); + } + #[cfg(async_signing)] { + if !self.context.signer_pending_funding { + log_trace!(logger, "funding_created awaiting signer; setting signer_pending_funding"); + self.context.signer_pending_funding = true; + } } } @@ -7610,37 +8136,7 @@ impl OutboundV1Channel where SP::Target: SignerProvider { where F::Target: FeeEstimator { - if !self.context.is_outbound() || - !matches!( - self.context.channel_state, ChannelState::NegotiatingFunding(flags) - if flags == NegotiatingFundingFlags::OUR_INIT_SENT - ) - { - return Err(()); - } - if self.context.channel_type == ChannelTypeFeatures::only_static_remote_key() { - // We've exhausted our options - return Err(()); - } - // We support opening a few different types of channels. Try removing our additional - // features one by one until we've either arrived at our default or the counterparty has - // accepted one. - // - // Due to the order below, we may not negotiate `option_anchors_zero_fee_htlc_tx` if the - // counterparty doesn't support `option_scid_privacy`. Since `get_initial_channel_type` - // checks whether the counterparty supports every feature, this would only happen if the - // counterparty is advertising the feature, but rejecting channels proposing the feature for - // 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::NonAnchorChannelFee); - assert!(!self.context.channel_transaction_parameters.channel_type_features.supports_anchors_nonzero_fee_htlc_tx()); - } else if self.context.channel_type.supports_scid_privacy() { - self.context.channel_type.clear_scid_privacy(); - } else { - self.context.channel_type = ChannelTypeFeatures::only_static_remote_key(); - } - self.context.channel_transaction_parameters.channel_type_features = self.context.channel_type.clone(); + self.context.maybe_downgrade_channel_features(fee_estimator)?; Ok(self.get_open_channel(chain_hash)) } @@ -7660,40 +8156,38 @@ impl OutboundV1Channel where SP::Target: SignerProvider { let keys = self.context.get_holder_pubkeys(); msgs::OpenChannel { - chain_hash, - temporary_channel_id: self.context.channel_id, - funding_satoshis: self.context.channel_value_satoshis, + common_fields: msgs::CommonOpenChannelFields { + chain_hash, + temporary_channel_id: self.context.channel_id, + funding_satoshis: self.context.channel_value_satoshis, + dust_limit_satoshis: self.context.holder_dust_limit_satoshis, + max_htlc_value_in_flight_msat: self.context.holder_max_htlc_value_in_flight_msat, + htlc_minimum_msat: self.context.holder_htlc_minimum_msat, + commitment_feerate_sat_per_1000_weight: self.context.feerate_per_kw as u32, + to_self_delay: self.context.get_holder_selected_contest_delay(), + max_accepted_htlcs: self.context.holder_max_accepted_htlcs, + funding_pubkey: keys.funding_pubkey, + revocation_basepoint: keys.revocation_basepoint.to_public_key(), + payment_basepoint: keys.payment_point, + delayed_payment_basepoint: keys.delayed_payment_basepoint.to_public_key(), + htlc_basepoint: keys.htlc_basepoint.to_public_key(), + first_per_commitment_point, + channel_flags: if self.context.config.announced_channel {1} else {0}, + shutdown_scriptpubkey: Some(match &self.context.shutdown_scriptpubkey { + Some(script) => script.clone().into_inner(), + None => Builder::new().into_script(), + }), + channel_type: Some(self.context.channel_type.clone()), + }, push_msat: self.context.channel_value_satoshis * 1000 - self.context.value_to_self_msat, - dust_limit_satoshis: self.context.holder_dust_limit_satoshis, - max_htlc_value_in_flight_msat: self.context.holder_max_htlc_value_in_flight_msat, channel_reserve_satoshis: self.context.holder_selected_channel_reserve_satoshis, - htlc_minimum_msat: self.context.holder_htlc_minimum_msat, - feerate_per_kw: self.context.feerate_per_kw as u32, - to_self_delay: self.context.get_holder_selected_contest_delay(), - max_accepted_htlcs: self.context.holder_max_accepted_htlcs, - funding_pubkey: keys.funding_pubkey, - revocation_basepoint: keys.revocation_basepoint.to_public_key(), - payment_point: keys.payment_point, - delayed_payment_basepoint: keys.delayed_payment_basepoint.to_public_key(), - htlc_basepoint: keys.htlc_basepoint.to_public_key(), - first_per_commitment_point, - channel_flags: if self.context.config.announced_channel {1} else {0}, - shutdown_scriptpubkey: Some(match &self.context.shutdown_scriptpubkey { - Some(script) => script.clone().into_inner(), - None => Builder::new().into_script(), - }), - channel_type: Some(self.context.channel_type.clone()), } } // Message handlers pub fn accept_channel(&mut self, msg: &msgs::AcceptChannel, default_limits: &ChannelHandshakeLimits, their_features: &InitFeatures) -> Result<(), ChannelError> { self.context.do_accept_channel_checks( - default_limits, their_features, msg.dust_limit_satoshis, msg.channel_reserve_satoshis, - msg.to_self_delay, msg.max_accepted_htlcs, msg.htlc_minimum_msat, - msg.max_htlc_value_in_flight_msat, msg.minimum_depth, &msg.channel_type, - &msg.shutdown_scriptpubkey, msg.funding_pubkey, msg.revocation_basepoint, msg.payment_point, - msg.delayed_payment_basepoint, msg.htlc_basepoint, msg.first_per_commitment_point) + default_limits, their_features, &msg.common_fields, msg.channel_reserve_satoshis) } /// Handles a funding_signed message from the remote end. @@ -7765,7 +8259,7 @@ impl OutboundV1Channel where SP::Target: SignerProvider { &self.context.channel_transaction_parameters, funding_redeemscript.clone(), self.context.channel_value_satoshis, obscure_factor, - holder_commitment_tx, best_block, self.context.counterparty_node_id); + holder_commitment_tx, best_block, self.context.counterparty_node_id, self.context.channel_id()); channel_monitor.provide_initial_counterparty_commitment_tx( counterparty_initial_bitcoin_tx.txid, Vec::new(), self.context.cur_counterparty_commitment_transaction_number, @@ -7800,7 +8294,7 @@ impl OutboundV1Channel where SP::Target: SignerProvider { /// Indicates that the signer may have some signatures for us, so we should retry if we're /// blocked. - #[allow(unused)] + #[cfg(async_signing)] pub fn signer_maybe_unblocked(&mut self, logger: &L) -> Option where L::Target: Logger { if self.context.signer_pending_funding && self.context.is_outbound() { log_trace!(logger, "Signer unblocked a funding_created"); @@ -7815,6 +8309,41 @@ pub(super) struct InboundV1Channel where SP::Target: SignerProvider { pub unfunded_context: UnfundedChannelContext, } +/// Fetches the [`ChannelTypeFeatures`] that will be used for a channel built from a given +/// [`msgs::CommonOpenChannelFields`]. +pub(super) fn channel_type_from_open_channel( + common_fields: &msgs::CommonOpenChannelFields, their_features: &InitFeatures, + our_supported_features: &ChannelTypeFeatures +) -> Result { + if let Some(channel_type) = &common_fields.channel_type { + if channel_type.supports_any_optional_bits() { + return Err(ChannelError::Close("Channel Type field contained optional bits - this is not allowed".to_owned())); + } + + // We only support the channel types defined by the `ChannelManager` in + // `provided_channel_type_features`. The channel type must always support + // `static_remote_key`. + if !channel_type.requires_static_remote_key() { + return Err(ChannelError::Close("Channel Type was not understood - we require static remote key".to_owned())); + } + // Make sure we support all of the features behind the channel type. + if !channel_type.is_subset(our_supported_features) { + return Err(ChannelError::Close("Channel Type contains unsupported features".to_owned())); + } + let announced_channel = if (common_fields.channel_flags & 1) == 1 { true } else { false }; + if channel_type.requires_scid_privacy() && announced_channel { + return Err(ChannelError::Close("SCID Alias/Privacy Channel Type cannot be set on a public channel".to_owned())); + } + Ok(channel_type.clone()) + } else { + let channel_type = ChannelTypeFeatures::from_init(&their_features); + if channel_type != ChannelTypeFeatures::only_static_remote_key() { + return Err(ChannelError::Close("Only static_remote_key is supported for non-negotiated channel types".to_owned())); + } + Ok(channel_type) + } +} + impl InboundV1Channel 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! @@ -7828,47 +8357,41 @@ impl InboundV1Channel where SP::Target: SignerProvider { F::Target: FeeEstimator, L::Target: Logger, { - let holder_selected_channel_reserve_satoshis = get_holder_selected_channel_reserve_satoshis(msg.funding_satoshis, config); + let logger = WithContext::from(logger, Some(counterparty_node_id), Some(msg.common_fields.temporary_channel_id)); + // First check the channel type is known, failing before we do anything else if we don't + // support this channel type. + let channel_type = channel_type_from_open_channel(&msg.common_fields, their_features, our_supported_features)?; + + let holder_selected_channel_reserve_satoshis = get_holder_selected_channel_reserve_satoshis(msg.common_fields.funding_satoshis, config); let counterparty_pubkeys = ChannelPublicKeys { - funding_pubkey: msg.funding_pubkey, - revocation_basepoint: RevocationBasepoint::from(msg.revocation_basepoint), - payment_point: msg.payment_point, - delayed_payment_basepoint: DelayedPaymentBasepoint::from(msg.delayed_payment_basepoint), - htlc_basepoint: HtlcBasepoint::from(msg.htlc_basepoint) + funding_pubkey: msg.common_fields.funding_pubkey, + revocation_basepoint: RevocationBasepoint::from(msg.common_fields.revocation_basepoint), + payment_point: msg.common_fields.payment_basepoint, + delayed_payment_basepoint: DelayedPaymentBasepoint::from(msg.common_fields.delayed_payment_basepoint), + htlc_basepoint: HtlcBasepoint::from(msg.common_fields.htlc_basepoint) }; + let chan = Self { context: ChannelContext::new_for_inbound_channel( fee_estimator, entropy_source, signer_provider, counterparty_node_id, - our_supported_features, their_features, user_id, config, current_chain_height, - logger, + &&logger, is_0conf, - 0, counterparty_pubkeys, - msg.channel_flags, - msg.channel_type.clone(), - msg.funding_satoshis, - msg.to_self_delay, + channel_type, holder_selected_channel_reserve_satoshis, msg.channel_reserve_satoshis, msg.push_msat, - msg.dust_limit_satoshis, - msg.htlc_minimum_msat, - msg.feerate_per_kw, - msg.max_accepted_htlcs, - msg.shutdown_scriptpubkey.clone(), - msg.max_htlc_value_in_flight_msat, - msg.temporary_channel_id, - msg.first_per_commitment_point, + msg.common_fields.clone(), )?, unfunded_context: UnfundedChannelContext { unfunded_channel_age_ticks: 0 } }; @@ -7906,25 +8429,27 @@ impl InboundV1Channel where SP::Target: SignerProvider { let keys = self.context.get_holder_pubkeys(); msgs::AcceptChannel { - temporary_channel_id: self.context.channel_id, - dust_limit_satoshis: self.context.holder_dust_limit_satoshis, - max_htlc_value_in_flight_msat: self.context.holder_max_htlc_value_in_flight_msat, + common_fields: msgs::CommonAcceptChannelFields { + temporary_channel_id: self.context.channel_id, + dust_limit_satoshis: self.context.holder_dust_limit_satoshis, + max_htlc_value_in_flight_msat: self.context.holder_max_htlc_value_in_flight_msat, + htlc_minimum_msat: self.context.holder_htlc_minimum_msat, + minimum_depth: self.context.minimum_depth.unwrap(), + to_self_delay: self.context.get_holder_selected_contest_delay(), + max_accepted_htlcs: self.context.holder_max_accepted_htlcs, + funding_pubkey: keys.funding_pubkey, + revocation_basepoint: keys.revocation_basepoint.to_public_key(), + payment_basepoint: keys.payment_point, + delayed_payment_basepoint: keys.delayed_payment_basepoint.to_public_key(), + htlc_basepoint: keys.htlc_basepoint.to_public_key(), + first_per_commitment_point, + shutdown_scriptpubkey: Some(match &self.context.shutdown_scriptpubkey { + Some(script) => script.clone().into_inner(), + None => Builder::new().into_script(), + }), + channel_type: Some(self.context.channel_type.clone()), + }, channel_reserve_satoshis: self.context.holder_selected_channel_reserve_satoshis, - htlc_minimum_msat: self.context.holder_htlc_minimum_msat, - minimum_depth: self.context.minimum_depth.unwrap(), - to_self_delay: self.context.get_holder_selected_contest_delay(), - max_accepted_htlcs: self.context.holder_max_accepted_htlcs, - funding_pubkey: keys.funding_pubkey, - revocation_basepoint: keys.revocation_basepoint.to_public_key(), - payment_point: keys.payment_point, - delayed_payment_basepoint: keys.delayed_payment_basepoint.to_public_key(), - htlc_basepoint: keys.htlc_basepoint.to_public_key(), - first_per_commitment_point, - shutdown_scriptpubkey: Some(match &self.context.shutdown_scriptpubkey { - Some(script) => script.clone().into_inner(), - None => Builder::new().into_script(), - }), - channel_type: Some(self.context.channel_type.clone()), #[cfg(taproot)] next_local_nonce: None, } @@ -8015,7 +8540,7 @@ impl InboundV1Channel where SP::Target: SignerProvider { // Now that we're past error-generating stuff, update our local state: self.context.channel_state = ChannelState::AwaitingChannelReady(AwaitingChannelReadyFlags::new()); - self.context.channel_id = funding_txo.to_channel_id(); + self.context.channel_id = ChannelId::v1_from_funding_outpoint(funding_txo); self.context.cur_counterparty_commitment_transaction_number -= 1; self.context.cur_holder_commitment_transaction_number -= 1; @@ -8033,7 +8558,7 @@ impl InboundV1Channel where SP::Target: SignerProvider { &self.context.channel_transaction_parameters, funding_redeemscript.clone(), self.context.channel_value_satoshis, obscure_factor, - holder_commitment_tx, best_block, self.context.counterparty_node_id); + holder_commitment_tx, best_block, self.context.counterparty_node_id, self.context.channel_id()); channel_monitor.provide_initial_counterparty_commitment_tx( counterparty_initial_commitment_tx.trust().txid(), Vec::new(), self.context.cur_counterparty_commitment_transaction_number + 1, @@ -8061,6 +8586,7 @@ impl InboundV1Channel where SP::Target: SignerProvider { } // A not-yet-funded outbound (from holder) channel using V2 channel establishment. +#[cfg(dual_funding)] pub(super) struct OutboundV2Channel where SP::Target: SignerProvider { pub context: ChannelContext, pub unfunded_context: UnfundedChannelContext, @@ -8083,7 +8609,7 @@ impl OutboundV2Channel where SP::Target: SignerProvider { let holder_signer = signer_provider.derive_channel_signer(funding_satoshis, channel_keys_id); let pubkeys = holder_signer.pubkeys().clone(); - let temporary_channel_id = ChannelId::temporary_v2_from_revocation_basepoint(&pubkeys.revocation_basepoint); + let temporary_channel_id = Some(ChannelId::temporary_v2_from_revocation_basepoint(&pubkeys.revocation_basepoint)); let holder_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis( funding_satoshis, MIN_CHAN_DUST_LIMIT_SATOSHIS); @@ -8125,13 +8651,26 @@ impl OutboundV2Channel where SP::Target: SignerProvider { Ok(chan) } + /// 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 `OpenChannelV2` message, otherwise the channel is failed. + pub(crate) fn maybe_handle_error_without_close( + &mut self, chain_hash: ChainHash, fee_estimator: &LowerBoundedFeeEstimator + ) -> Result + where + F::Target: FeeEstimator + { + self.context.maybe_downgrade_channel_features(fee_estimator)?; + Ok(self.get_open_channel_v2(chain_hash)) + } + pub fn get_open_channel_v2(&self, chain_hash: ChainHash) -> msgs::OpenChannelV2 { if self.context.have_received_message() { - panic!("Cannot generate an open_channel2 after we've moved forward"); + debug_assert!(false, "Cannot generate an open_channel2 after we've moved forward"); } if self.context.cur_holder_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER { - panic!("Tried to send an open_channel2 for a channel that has already advanced"); + debug_assert!(false, "Tried to send an open_channel2 for a channel that has already advanced"); } let first_per_commitment_point = self.context.holder_signer.as_ref() @@ -8143,36 +8682,38 @@ impl OutboundV2Channel where SP::Target: SignerProvider { let keys = self.context.get_holder_pubkeys(); msgs::OpenChannelV2 { - chain_hash, - temporary_channel_id: self.context.temporary_channel_id.unwrap(), - funding_satoshis: self.context.channel_value_satoshis, - dust_limit_satoshis: self.context.holder_dust_limit_satoshis, - max_htlc_value_in_flight_msat: self.context.holder_max_htlc_value_in_flight_msat, - htlc_minimum_msat: self.context.holder_htlc_minimum_msat, + common_fields: msgs::CommonOpenChannelFields { + chain_hash, + temporary_channel_id: self.context.temporary_channel_id.unwrap(), + funding_satoshis: self.context.channel_value_satoshis, + dust_limit_satoshis: self.context.holder_dust_limit_satoshis, + max_htlc_value_in_flight_msat: self.context.holder_max_htlc_value_in_flight_msat, + htlc_minimum_msat: self.context.holder_htlc_minimum_msat, + commitment_feerate_sat_per_1000_weight: self.context.feerate_per_kw, + to_self_delay: self.context.get_holder_selected_contest_delay(), + max_accepted_htlcs: self.context.holder_max_accepted_htlcs, + funding_pubkey: keys.funding_pubkey, + revocation_basepoint: keys.revocation_basepoint.to_public_key(), + payment_basepoint: keys.payment_point, + delayed_payment_basepoint: keys.delayed_payment_basepoint.to_public_key(), + htlc_basepoint: keys.htlc_basepoint.to_public_key(), + first_per_commitment_point, + channel_flags: if self.context.config.announced_channel {1} else {0}, + shutdown_scriptpubkey: Some(match &self.context.shutdown_scriptpubkey { + Some(script) => script.clone().into_inner(), + None => Builder::new().into_script(), + }), + channel_type: Some(self.context.channel_type.clone()), + }, funding_feerate_sat_per_1000_weight: self.context.feerate_per_kw, - commitment_feerate_sat_per_1000_weight: self.context.feerate_per_kw, - to_self_delay: self.context.get_holder_selected_contest_delay(), - max_accepted_htlcs: self.context.holder_max_accepted_htlcs, - funding_pubkey: keys.funding_pubkey, - revocation_basepoint: keys.revocation_basepoint.to_public_key(), - payment_basepoint: keys.payment_point, - delayed_payment_basepoint: keys.delayed_payment_basepoint.to_public_key(), - htlc_basepoint: keys.htlc_basepoint.to_public_key(), - first_per_commitment_point, second_per_commitment_point, - channel_flags: if self.context.config.announced_channel {1} else {0}, - shutdown_scriptpubkey: Some(match &self.context.shutdown_scriptpubkey { - Some(script) => script.clone().into_inner(), - None => Builder::new().into_script(), - }), - channel_type: Some(self.context.channel_type.clone()), locktime: self.dual_funding_context.funding_tx_locktime.to_consensus_u32(), require_confirmed_inputs: None, } } pub fn begin_interactive_funding_tx_construction(&mut self, signer_provider: &SP, - entropy_source: &ES, holder_node_id: PublicKey, funding_inputs: Vec<(TxIn, Transaction)> + entropy_source: &ES, holder_node_id: PublicKey, funding_inputs: Vec<(TxIn, TransactionU16LenLimited)> ) -> Result, APIError> where ES::Target: EntropySource { @@ -8181,7 +8722,7 @@ impl OutboundV2Channel where SP::Target: SignerProvider { } pub fn funding_tx_constructed( - mut self, counterparty_node_id: &PublicKey, mut signing_session: InteractiveTxSigningSession, signer_provider: &SP, logger: &L + mut self, counterparty_node_id: &PublicKey, mut signing_session: InteractiveTxSigningSession, _signer_provider: &SP, logger: &L ) -> Result<(Channel, msgs::CommitmentSigned, Option), (Self, ChannelError)> where L::Target: Logger @@ -8194,9 +8735,8 @@ impl OutboundV2Channel where SP::Target: SignerProvider { let mut output_index = None; let expected_spk = self.context.get_funding_redeemscript().to_v0_p2wsh(); // Splicing note: the channel value at this time is already the post-splice value, so no special handling is needed - let expected_output_amount = self.context.get_value_satoshis(); for (idx, outp) in signing_session.constructed_transaction.output.iter().enumerate() { - if outp.script_pubkey == expected_spk && outp.value == expected_output_amount { + if outp.script_pubkey == expected_spk && outp.value == self.context.get_value_satoshis() { if output_index.is_some() { return Err((self, ChannelError::Close("Multiple outputs matched the expected script and value".to_owned()))); } @@ -8211,7 +8751,7 @@ impl OutboundV2Channel where SP::Target: SignerProvider { self.context.holder_signer.as_mut().provide_channel_parameters(&self.context.channel_transaction_parameters); let commitment_signed = get_initial_commitment_signed(&mut self.context, signing_session.constructed_transaction.clone(), - signer_provider, pending_splice_opt.is_some(), logger); + pending_splice_opt.is_some(), logger); let commitment_signed = match commitment_signed { Ok(commitment_signed) => commitment_signed, Err(err) => return Err((self, err)), @@ -8260,15 +8800,11 @@ impl OutboundV2Channel where SP::Target: SignerProvider { // Now we can generate the `channel_id` since we have our counterparty's `revocation_basepoint`. self.context.channel_id = ChannelId::v2_from_revocation_basepoints( - &self.context.get_holder_pubkeys().revocation_basepoint, &RevocationBasepoint::from(msg.revocation_basepoint)); + &self.context.get_holder_pubkeys().revocation_basepoint, &RevocationBasepoint::from(msg.common_fields.revocation_basepoint)); self.dual_funding_context.their_funding_satoshis = msg.funding_satoshis; self.context.do_accept_channel_checks( - default_limits, their_features, msg.dust_limit_satoshis, get_v2_channel_reserve_satoshis( - msg.funding_satoshis + self.dual_funding_context.our_funding_satoshis, msg.dust_limit_satoshis), - msg.to_self_delay, msg.max_accepted_htlcs, msg.htlc_minimum_msat, - msg.max_htlc_value_in_flight_msat, msg.minimum_depth, &msg.channel_type, - &msg.shutdown_scriptpubkey, msg.funding_pubkey, msg.revocation_basepoint, msg.payment_basepoint, - msg.delayed_payment_basepoint, msg.htlc_basepoint, msg.first_per_commitment_point) + default_limits, their_features, &msg.common_fields, get_v2_channel_reserve_satoshis( + msg.common_fields.dust_limit_satoshis, self.context.channel_value_satoshis)) } } @@ -8294,19 +8830,26 @@ impl InboundV2Channel where SP::Target: SignerProvider { F::Target: FeeEstimator, L::Target: Logger, { - // TODO(dual_funding): Fix this - let channel_value_satoshis = funding_satoshis * 1000 + msg.funding_satoshis; + let channel_value_satoshis = funding_satoshis.saturating_add(msg.common_fields.funding_satoshis); let counterparty_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis( - channel_value_satoshis, msg.dust_limit_satoshis); + channel_value_satoshis, msg.common_fields.dust_limit_satoshis); let holder_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis( channel_value_satoshis, MIN_CHAN_DUST_LIMIT_SATOSHIS); - + + // First check the channel type is known, failing before we do anything else if we don't + // support this channel type. + if msg.common_fields.channel_type.is_none() { + return Err(ChannelError::Close(format!("Rejecting V2 channel {} missing channel_type", + msg.common_fields.temporary_channel_id))) + } + let channel_type = channel_type_from_open_channel(&msg.common_fields, their_features, our_supported_features)?; + let counterparty_pubkeys = ChannelPublicKeys { - funding_pubkey: msg.funding_pubkey, - revocation_basepoint: RevocationBasepoint(msg.revocation_basepoint), - payment_point: msg.payment_basepoint, - delayed_payment_basepoint: DelayedPaymentBasepoint(msg.delayed_payment_basepoint), - htlc_basepoint: HtlcBasepoint(msg.htlc_basepoint) + funding_pubkey: msg.common_fields.funding_pubkey, + revocation_basepoint: RevocationBasepoint(msg.common_fields.revocation_basepoint), + payment_point: msg.common_fields.payment_basepoint, + delayed_payment_basepoint: DelayedPaymentBasepoint(msg.common_fields.delayed_payment_basepoint), + htlc_basepoint: HtlcBasepoint(msg.common_fields.htlc_basepoint) }; let mut context = ChannelContext::new_for_inbound_channel( @@ -8314,7 +8857,6 @@ impl InboundV2Channel where SP::Target: SignerProvider { entropy_source, signer_provider, counterparty_node_id, - our_supported_features, their_features, user_id, config, @@ -8325,21 +8867,11 @@ impl InboundV2Channel where SP::Target: SignerProvider { funding_satoshis, counterparty_pubkeys, - msg.channel_flags, - msg.channel_type.clone(), - msg.funding_satoshis, - msg.to_self_delay, + channel_type, holder_selected_channel_reserve_satoshis, counterparty_selected_channel_reserve_satoshis, 0 /* push_msat not used in dual-funding */, - msg.dust_limit_satoshis, - msg.htlc_minimum_msat, - msg.commitment_feerate_sat_per_1000_weight, - msg.max_accepted_htlcs, - msg.shutdown_scriptpubkey.clone(), - msg.max_htlc_value_in_flight_msat, - msg.temporary_channel_id, - msg.first_per_commitment_point, + msg.common_fields.clone(), )?; let channel_id = ChannelId::v2_from_revocation_basepoints( &context.get_holder_pubkeys().revocation_basepoint, @@ -8351,7 +8883,7 @@ impl InboundV2Channel where SP::Target: SignerProvider { unfunded_context: UnfundedChannelContext { unfunded_channel_age_ticks: 0 }, dual_funding_context: DualFundingChannelContext { our_funding_satoshis: funding_satoshis, - their_funding_satoshis: msg.funding_satoshis, + their_funding_satoshis: msg.common_fields.funding_satoshis, funding_tx_locktime: LockTime::from_consensus(msg.locktime), funding_feerate_sat_per_1000_weight: msg.funding_feerate_sat_per_1000_weight, } @@ -8366,16 +8898,16 @@ impl InboundV2Channel where SP::Target: SignerProvider { /// [`msgs::AcceptChannelV2`]: crate::ln::msgs::AcceptChannelV2 pub fn accept_inbound_dual_funded_channel(&mut self) -> msgs::AcceptChannelV2 { if self.context.is_outbound() { - panic!("Tried to send accept_channel2 for an outbound channel?"); + debug_assert!(false, "Tried to send accept_channel for an outbound channel?"); } if !matches!( self.context.channel_state, ChannelState::NegotiatingFunding(flags) if flags == (NegotiatingFundingFlags::OUR_INIT_SENT | NegotiatingFundingFlags::THEIR_INIT_SENT) ) { - panic!("Tried to send accept_channel2 after channel had moved forward"); + debug_assert!(false, "Tried to send accept_channel2 after channel had moved forward"); } if self.context.cur_holder_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER { - panic!("Tried to send an accept_channel2 for a channel that has already advanced"); + debug_assert!(false, "Tried to send an accept_channel2 for a channel that has already advanced"); } self.generate_accept_channel_v2_message() @@ -8394,26 +8926,28 @@ impl InboundV2Channel where SP::Target: SignerProvider { let keys = self.context.get_holder_pubkeys(); msgs::AcceptChannelV2 { - temporary_channel_id: self.context.temporary_channel_id.unwrap(), + common_fields: msgs::CommonAcceptChannelFields { + temporary_channel_id: self.context.temporary_channel_id.unwrap(), + dust_limit_satoshis: self.context.holder_dust_limit_satoshis, + max_htlc_value_in_flight_msat: self.context.holder_max_htlc_value_in_flight_msat, + htlc_minimum_msat: self.context.holder_htlc_minimum_msat, + minimum_depth: self.context.minimum_depth.unwrap(), + to_self_delay: self.context.get_holder_selected_contest_delay(), + max_accepted_htlcs: self.context.holder_max_accepted_htlcs, + funding_pubkey: keys.funding_pubkey, + revocation_basepoint: keys.revocation_basepoint.to_public_key(), + payment_basepoint: keys.payment_point, + delayed_payment_basepoint: keys.delayed_payment_basepoint.to_public_key(), + htlc_basepoint: keys.htlc_basepoint.to_public_key(), + first_per_commitment_point, + shutdown_scriptpubkey: Some(match &self.context.shutdown_scriptpubkey { + Some(script) => script.clone().into_inner(), + None => Builder::new().into_script(), + }), + channel_type: Some(self.context.channel_type.clone()), + }, funding_satoshis: self.dual_funding_context.our_funding_satoshis, - dust_limit_satoshis: self.context.holder_dust_limit_satoshis, - max_htlc_value_in_flight_msat: self.context.holder_max_htlc_value_in_flight_msat, - htlc_minimum_msat: self.context.holder_htlc_minimum_msat, - minimum_depth: self.context.minimum_depth.unwrap(), - to_self_delay: self.context.get_holder_selected_contest_delay(), - max_accepted_htlcs: self.context.holder_max_accepted_htlcs, - funding_pubkey: keys.funding_pubkey, - revocation_basepoint: keys.revocation_basepoint.to_public_key(), - payment_basepoint: keys.payment_point, - delayed_payment_basepoint: keys.delayed_payment_basepoint.to_public_key(), - htlc_basepoint: keys.htlc_basepoint.to_public_key(), - first_per_commitment_point, second_per_commitment_point, - shutdown_scriptpubkey: Some(match &self.context.shutdown_scriptpubkey { - Some(script) => script.clone().into_inner(), - None => Builder::new().into_script(), - }), - channel_type: Some(self.context.channel_type.clone()), require_confirmed_inputs: None, } } @@ -8428,7 +8962,7 @@ impl InboundV2Channel where SP::Target: SignerProvider { } pub fn begin_interactive_funding_tx_construction(&mut self, signer_provider: &SP, - entropy_source: &ES, holder_node_id: PublicKey, funding_inputs: Vec<(TxIn, Transaction)> + entropy_source: &ES, holder_node_id: PublicKey, funding_inputs: Vec<(TxIn, TransactionU16LenLimited)> ) -> Result, APIError> where ES::Target: EntropySource { @@ -8437,7 +8971,7 @@ impl InboundV2Channel where SP::Target: SignerProvider { } pub fn funding_tx_constructed( - mut self, counterparty_node_id: &PublicKey, mut signing_session: InteractiveTxSigningSession, signer_provider: &SP, logger: &L + mut self, counterparty_node_id: &PublicKey, mut signing_session: InteractiveTxSigningSession, _signer_provider: &SP, logger: &L ) -> Result<(Channel, msgs::CommitmentSigned, Option), (Self, ChannelError)> where L::Target: Logger @@ -8466,11 +9000,10 @@ impl InboundV2Channel where SP::Target: SignerProvider { self.context.channel_transaction_parameters.funding_outpoint = Some(outpoint); self.context.holder_signer.as_mut().provide_channel_parameters(&self.context.channel_transaction_parameters); - let commitment_signed = get_initial_commitment_signed(&mut self.context, signing_session.constructed_transaction.clone(), - signer_provider, pending_splice_opt.is_some(), logger); + let commitment_signed = get_initial_commitment_signed(&mut self.context, signing_session.constructed_transaction.clone(), pending_splice_opt.is_some(), logger); let commitment_signed = match commitment_signed { Ok(commitment_signed) => commitment_signed, - Err(err) => return Err((self, err)), + Err(err) => return Err((self, ChannelError::Close(err.to_string()))), }; let (partly_signed_transaction, shared_signature) = if pending_splice_opt.is_some() { @@ -8566,6 +9099,7 @@ fn get_initial_channel_type(config: &UserConfig, their_features: &InitFeatures) ret } +#[cfg(dual_funding)] fn get_initial_counterparty_commitment_signature( context: &mut ChannelContext, logger: &L ) -> Result @@ -8581,14 +9115,16 @@ where ChannelSignerType::Ecdsa(ref ecdsa) => { Ok(ecdsa.sign_counterparty_commitment(&counterparty_initial_commitment_tx, Vec::new(), Vec::new(), &context.secp_ctx) .map_err(|_| ChannelError::Close("Failed to get signatures for new commitment_signed".to_owned()))?.0) - } + }, + // TODO (taproot|arik) #[cfg(taproot)] _ => todo!(), } } +#[cfg(dual_funding)] fn get_initial_commitment_signed( - context: &mut ChannelContext, transaction: Transaction, _signer_provider: &SP, is_splice: bool, logger: &L + context: &mut ChannelContext, transaction: Transaction, is_splice: bool, logger: &L ) -> Result where SP::Target: SignerProvider, @@ -8597,12 +9133,12 @@ where if !matches!( context.channel_state, ChannelState::NegotiatingFunding(flags) if flags == (NegotiatingFundingFlags::OUR_INIT_SENT | NegotiatingFundingFlags::THEIR_INIT_SENT)) { - panic!("Tried to get a funding_created messsage at a time other than immediately after initial handshake completion (or tried to get funding_created twice) {:?}", context.channel_state); + panic!("Tried to get a funding_created messsage at a time other than immediately after initial handshake completion (or tried to get funding_created twice)"); } if !is_splice { if context.commitment_secrets.get_min_seen_secret() != (1 << 48) || - context.cur_counterparty_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER || - context.cur_holder_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER { + context.cur_counterparty_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER || + context.cur_holder_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER { panic!("Should not have advanced channel commitment tx numbers prior to initial commitment_signed"); } } @@ -8721,6 +9257,8 @@ impl Writeable for Channel where SP::Target: SignerProvider { let mut channel_state = self.context.channel_state; if matches!(channel_state, ChannelState::AwaitingChannelReady(_)|ChannelState::ChannelReady(_)) { channel_state.set_peer_disconnected(); + } else { + debug_assert!(false, "Pre-funded/shutdown channels should not be written"); } channel_state.to_u32().write(writer)?; } @@ -8822,6 +9360,8 @@ impl Writeable for Channel where SP::Target: SignerProvider { let mut holding_cell_skimmed_fees: Vec> = Vec::new(); let mut holding_cell_blinding_points: Vec> = Vec::new(); + // Vec of (htlc_id, failure_code, sha256_of_onion) + let mut malformed_htlcs: Vec<(u64, u16, [u8; 32])> = Vec::new(); (self.context.holding_cell_htlc_updates.len() as u64).write(writer)?; for update in self.context.holding_cell_htlc_updates.iter() { match update { @@ -8849,6 +9389,18 @@ impl Writeable for Channel where SP::Target: SignerProvider { htlc_id.write(writer)?; err_packet.write(writer)?; } + &HTLCUpdateAwaitingACK::FailMalformedHTLC { + htlc_id, failure_code, sha256_of_onion + } => { + // We don't want to break downgrading by adding a new variant, so write a dummy + // `::FailHTLC` variant and write the real malformed error as an optional TLV. + malformed_htlcs.push((htlc_id, failure_code, sha256_of_onion)); + + let dummy_err_packet = msgs::OnionErrorPacket { data: Vec::new() }; + 2u8.write(writer)?; + htlc_id.write(writer)?; + dummy_err_packet.write(writer)?; + } } } @@ -9011,6 +9563,8 @@ impl Writeable for Channel where SP::Target: SignerProvider { (38, self.context.is_batch_funding, option), (39, pending_outbound_blinding_points, optional_vec), (41, holding_cell_blinding_points, optional_vec), + (43, malformed_htlcs, optional_vec), // Added in 0.0.119 + (45, self.context.local_initiated_shutdown, option), // Added in 0.0.122 }); Ok(()) @@ -9249,7 +9803,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch let channel_update_status = Readable::read(reader)?; #[cfg(any(test, fuzzing))] - let mut historical_inbound_htlc_fulfills = HashSet::new(); + let mut historical_inbound_htlc_fulfills = new_hash_set(); #[cfg(any(test, fuzzing))] { let htlc_fulfills_len: u64 = Readable::read(reader)?; @@ -9299,9 +9853,13 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch let mut is_batch_funding: Option<()> = None; + let mut local_initiated_shutdown: Option<()> = None; + let mut pending_outbound_blinding_points_opt: Option>> = None; let mut holding_cell_blinding_points_opt: Option>> = None; + let mut malformed_htlcs: Option> = None; + read_tlv_fields!(reader, { (0, announcement_sigs, option), (1, minimum_depth, option), @@ -9330,6 +9888,8 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch (38, is_batch_funding, option), (39, pending_outbound_blinding_points_opt, optional_vec), (41, holding_cell_blinding_points_opt, optional_vec), + (43, malformed_htlcs, optional_vec), // Added in 0.0.119 + (45, local_initiated_shutdown, option), }); let (channel_keys_id, holder_signer) = if let Some(channel_keys_id) = channel_keys_id { @@ -9424,6 +9984,22 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch if iter.next().is_some() { return Err(DecodeError::InvalidValue) } } + if let Some(malformed_htlcs) = malformed_htlcs { + for (malformed_htlc_id, failure_code, sha256_of_onion) in malformed_htlcs { + let htlc_idx = holding_cell_htlc_updates.iter().position(|htlc| { + if let HTLCUpdateAwaitingACK::FailHTLC { htlc_id, err_packet } = htlc { + let matches = *htlc_id == malformed_htlc_id; + if matches { debug_assert!(err_packet.data.is_empty()) } + matches + } else { false } + }).ok_or(DecodeError::InvalidValue)?; + let malformed_htlc = HTLCUpdateAwaitingACK::FailMalformedHTLC { + htlc_id: malformed_htlc_id, failure_code, sha256_of_onion + }; + let _ = core::mem::replace(&mut holding_cell_htlc_updates[htlc_idx], malformed_htlc); + } + } + Ok(Channel { context: ChannelContext { user_id, @@ -9545,11 +10121,13 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch channel_type: channel_type.unwrap(), channel_keys_id, + local_initiated_shutdown, + blocked_monitor_updates: blocked_monitor_updates.unwrap(), + #[cfg(dual_funding)] interactive_tx_constructor: None, - // pending_monitor_updates: Vec::new(), pending_splice_pre: None, pending_splice_post: None, }, @@ -9569,6 +10147,7 @@ mod tests { use bitcoin::blockdata::transaction::{Transaction, TxOut}; use bitcoin::blockdata::opcodes; use bitcoin::network::constants::Network; + use crate::ln::onion_utils::INVALID_ONION_BLINDING; use crate::ln::{PaymentHash, PaymentPreimage}; use crate::ln::channel_keys::{RevocationKey, RevocationBasepoint}; use crate::ln::channelmanager::{self, HTLCSource, PaymentId}; @@ -9601,6 +10180,18 @@ mod tests { use bitcoin::address::{WitnessProgram, WitnessVersion}; use crate::prelude::*; + #[test] + fn test_channel_state_order() { + use crate::ln::channel::NegotiatingFundingFlags; + use crate::ln::channel::AwaitingChannelReadyFlags; + use crate::ln::channel::ChannelReadyFlags; + + assert!(ChannelState::NegotiatingFunding(NegotiatingFundingFlags::new()) < ChannelState::FundingNegotiated); + assert!(ChannelState::FundingNegotiated < ChannelState::AwaitingChannelReady(AwaitingChannelReadyFlags::new())); + assert!(ChannelState::AwaitingChannelReady(AwaitingChannelReadyFlags::new()) < ChannelState::ChannelReady(ChannelReadyFlags::new())); + assert!(ChannelState::ChannelReady(ChannelReadyFlags::new()) < ChannelState::ShutdownComplete); + } + struct TestFeeEstimator { fee_est: u32 } @@ -9705,7 +10296,7 @@ mod tests { // same as the old fee. fee_est.fee_est = 500; let open_channel_msg = node_a_chan.get_open_channel(ChainHash::using_genesis_block(network)); - assert_eq!(open_channel_msg.feerate_per_kw, original_fee); + assert_eq!(open_channel_msg.common_fields.commitment_feerate_sat_per_1000_weight, original_fee); } #[test] @@ -9736,7 +10327,7 @@ mod tests { // Node B --> Node A: accept channel, explicitly setting B's dust limit. let mut accept_channel_msg = node_b_chan.accept_inbound_channel(); - accept_channel_msg.dust_limit_satoshis = 546; + accept_channel_msg.common_fields.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; @@ -10054,7 +10645,7 @@ mod tests { // Node B --> Node A: accept channel, explicitly setting B's dust limit. let mut accept_channel_msg = node_b_chan.accept_inbound_channel(); - accept_channel_msg.dust_limit_satoshis = 546; + accept_channel_msg.common_fields.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; @@ -10105,24 +10696,36 @@ mod tests { } #[test] - fn blinding_point_skimmed_fee_ser() { - // Ensure that channel blinding points and skimmed fees are (de)serialized properly. + fn blinding_point_skimmed_fee_malformed_ser() { + // Ensure that channel blinding points, skimmed fees, and malformed HTLCs are (de)serialized + // properly. + let logger = test_utils::TestLogger::new(); let feeest = LowerBoundedFeeEstimator::new(&TestFeeEstimator{fee_est: 15000}); let secp_ctx = Secp256k1::new(); let seed = [42; 32]; let network = Network::Testnet; + let best_block = BestBlock::from_network(network); let keys_provider = test_utils::TestKeysInterface::new(&seed, network); let node_b_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap()); let config = UserConfig::default(); let features = channelmanager::provided_init_features(&config); - let outbound_chan = OutboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider, node_b_node_id, &features, 10000000, 100000, 42, &config, 0, 42, None).unwrap(); - let mut chan = Channel { - context: outbound_chan.context, - #[cfg(dual_funding)] - dual_funding_channel_context: None, - #[cfg(dual_funding)] - interactive_tx_signing_session: None, + let mut outbound_chan = OutboundV1Channel::<&TestKeysInterface>::new( + &feeest, &&keys_provider, &&keys_provider, node_b_node_id, &features, 10000000, 100000, 42, &config, 0, 42, None + ).unwrap(); + let inbound_chan = InboundV1Channel::<&TestKeysInterface>::new( + &feeest, &&keys_provider, &&keys_provider, node_b_node_id, &channelmanager::provided_channel_type_features(&config), + &features, &outbound_chan.get_open_channel(ChainHash::using_genesis_block(network)), 7, &config, 0, &&logger, false + ).unwrap(); + outbound_chan.accept_channel(&inbound_chan.get_accept_channel_message(), &config.channel_handshake_limits, &features).unwrap(); + let tx = Transaction { version: 1, lock_time: LockTime::ZERO, input: Vec::new(), output: vec![TxOut { + value: 10000000, script_pubkey: outbound_chan.context.get_funding_redeemscript(), + }]}; + let funding_outpoint = OutPoint{ txid: tx.txid(), index: 0 }; + let funding_created = outbound_chan.get_funding_created(tx.clone(), funding_outpoint, false, &&logger).map_err(|_| ()).unwrap().unwrap(); + let mut chan = match inbound_chan.funding_created(&funding_created, best_block, &&keys_provider, &&logger) { + Ok((chan, _, _)) => chan, + Err((_, e)) => panic!("{}", e), }; let dummy_htlc_source = HTLCSource::OutboundRoute { @@ -10177,13 +10780,19 @@ mod tests { payment_preimage: PaymentPreimage([42; 32]), htlc_id: 0, }; - let mut holding_cell_htlc_updates = Vec::with_capacity(10); - for i in 0..10 { - if i % 3 == 0 { + let dummy_holding_cell_failed_htlc = |htlc_id| HTLCUpdateAwaitingACK::FailHTLC { + htlc_id, err_packet: msgs::OnionErrorPacket { data: vec![42] } + }; + let dummy_holding_cell_malformed_htlc = |htlc_id| HTLCUpdateAwaitingACK::FailMalformedHTLC { + htlc_id, failure_code: INVALID_ONION_BLINDING, sha256_of_onion: [0; 32], + }; + let mut holding_cell_htlc_updates = Vec::with_capacity(12); + for i in 0..12 { + if i % 5 == 0 { holding_cell_htlc_updates.push(dummy_holding_cell_add_htlc.clone()); - } else if i % 3 == 1 { + } else if i % 5 == 1 { holding_cell_htlc_updates.push(dummy_holding_cell_claim_htlc.clone()); - } else { + } else if i % 5 == 2 { let mut dummy_add = dummy_holding_cell_add_htlc.clone(); if let HTLCUpdateAwaitingACK::AddHTLC { ref mut blinding_point, ref mut skimmed_fee_msat, .. @@ -10192,6 +10801,10 @@ mod tests { *skimmed_fee_msat = Some(42); } else { panic!() } holding_cell_htlc_updates.push(dummy_add); + } else if i % 5 == 3 { + holding_cell_htlc_updates.push(dummy_holding_cell_malformed_htlc(i as u64)); + } else { + holding_cell_htlc_updates.push(dummy_holding_cell_failed_htlc(i as u64)); } } chan.context.holding_cell_htlc_updates = holding_cell_htlc_updates.clone(); @@ -10206,7 +10819,7 @@ mod tests { assert_eq!(decoded_chan.context.holding_cell_htlc_updates, holding_cell_htlc_updates); } - #[cfg(feature = "_test_vectors")] + #[cfg(all(feature = "_test_vectors", not(feature = "grind_signatures")))] #[test] fn outbound_commitment_test() { use bitcoin::sighash; @@ -10227,7 +10840,7 @@ mod tests { // Test vectors from BOLT 3 Appendices C and F (anchors): let feeest = TestFeeEstimator{fee_est: 15000}; - let logger : Arc = Arc::new(test_utils::TestLogger::new()); + let logger : Arc = Arc::new(test_utils::TestLogger::new()); let secp_ctx = Secp256k1::new(); let mut signer = InMemorySigner::new( @@ -11005,7 +11618,7 @@ mod tests { channel_type_features.set_zero_conf_required(); let mut open_channel_msg = node_a_chan.get_open_channel(ChainHash::using_genesis_block(network)); - open_channel_msg.channel_type = Some(channel_type_features); + open_channel_msg.common_fields.channel_type = Some(channel_type_features); let node_b_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[7; 32]).unwrap()); let res = InboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider, node_b_node_id, &channelmanager::provided_channel_type_features(&config), @@ -11088,7 +11701,7 @@ mod tests { // Set `channel_type` to `None` to force the implicit feature negotiation. let mut open_channel_msg = channel_a.get_open_channel(ChainHash::using_genesis_block(network)); - open_channel_msg.channel_type = None; + open_channel_msg.common_fields.channel_type = None; // Since A supports both `static_remote_key` and `option_anchors`, but B only accepts // `static_remote_key`, it will fail the channel. @@ -11134,7 +11747,7 @@ mod tests { ).unwrap(); let mut open_channel_msg = channel_a.get_open_channel(ChainHash::using_genesis_block(network)); - open_channel_msg.channel_type = Some(simple_anchors_channel_type.clone()); + open_channel_msg.common_fields.channel_type = Some(simple_anchors_channel_type.clone()); let res = InboundV1Channel::<&TestKeysInterface>::new( &fee_estimator, &&keys_provider, &&keys_provider, node_id_a, @@ -11161,7 +11774,7 @@ mod tests { ).unwrap(); let mut accept_channel_msg = channel_b.get_accept_channel_message(); - accept_channel_msg.channel_type = Some(simple_anchors_channel_type.clone()); + accept_channel_msg.common_fields.channel_type = Some(simple_anchors_channel_type.clone()); let res = channel_a.accept_channel( &accept_channel_msg, &config.channel_handshake_limits, &simple_anchors_init diff --git a/lightning/src/ln/channel_id.rs b/lightning/src/ln/channel_id.rs index 9404320cec2..90efe3c94be 100644 --- a/lightning/src/ln/channel_id.rs +++ b/lightning/src/ln/channel_id.rs @@ -14,15 +14,16 @@ use crate::io; use crate::ln::msgs::DecodeError; use crate::sign::EntropySource; use crate::util::ser::{Readable, Writeable, Writer}; +use super::channel_keys::RevocationBasepoint; -use bitcoin::hashes::Hash as _; -use bitcoin::hashes::sha256::Hash as Sha256; - +use bitcoin::hashes::{ + Hash as _, + HashEngine as _, + sha256::Hash as Sha256, +}; use core::fmt; use core::ops::Deref; -use super::channel_keys::RevocationBasepoint; - /// 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): @@ -80,13 +81,17 @@ impl ChannelId { ours: &RevocationBasepoint, theirs: &RevocationBasepoint, ) -> Self { - let (lesser_point, greater_point) = if *ours < *theirs { + let ours = ours.0.serialize(); + let theirs = theirs.0.serialize(); + let (lesser, greater) = if ours < theirs { (ours, theirs) } else { (theirs, ours) }; - - Self(Sha256::hash(&[lesser_point.0.serialize(), greater_point.0.serialize()].concat()).to_byte_array()) + let mut engine = Sha256::engine(); + engine.input(&lesser[..]); + engine.input(&greater[..]); + Self(Sha256::from_engine(engine).to_byte_array()) } /// Create temporary _v2_ channel ID by concatenating a zeroed out basepoint with the holder @@ -117,9 +122,17 @@ impl fmt::Display for ChannelId { #[cfg(test)] mod tests { + use bitcoin::hashes::{ + Hash as _, + HashEngine as _, + hex::FromHex as _, + sha256::Hash as Sha256, + }; + use bitcoin::secp256k1::PublicKey; use hex::DisplayHex; use crate::ln::ChannelId; + use crate::ln::channel_keys::RevocationBasepoint; use crate::util::ser::{Readable, Writeable}; use crate::util::test_utils; use crate::prelude::*; @@ -165,4 +178,29 @@ mod tests { let channel_id = ChannelId::v1_from_funding_txid(&[2; 32], 1); assert_eq!(format!("{}", &channel_id), "0202020202020202020202020202020202020202020202020202020202020203"); } + + #[test] + fn test_channel_id_v2_from_basepoints() { + // Ours greater than theirs + let ours = RevocationBasepoint(PublicKey::from_slice(&>::from_hex("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c").unwrap()[..]).unwrap()); + let theirs = RevocationBasepoint(PublicKey::from_slice(&>::from_hex("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619").unwrap()[..]).unwrap()); + + let mut engine = Sha256::engine(); + engine.input(&theirs.0.serialize()); + engine.input(&ours.0.serialize()); + let expected_id = ChannelId(Sha256::from_engine(engine).to_byte_array()); + + assert_eq!(ChannelId::v2_from_revocation_basepoints(&ours, &theirs), expected_id); + + // Theirs greater than ours + let ours = RevocationBasepoint(PublicKey::from_slice(&>::from_hex("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007").unwrap()[..]).unwrap()); + let theirs = RevocationBasepoint(PublicKey::from_slice(&>::from_hex("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619").unwrap()[..]).unwrap()); + + let mut engine = Sha256::engine(); + engine.input(&ours.0.serialize()); + engine.input(&theirs.0.serialize()); + let expected_id = ChannelId(Sha256::from_engine(engine).to_byte_array()); + + assert_eq!(ChannelId::v2_from_revocation_basepoints(&ours, &theirs), expected_id); + } } diff --git a/lightning/src/ln/channel_keys.rs b/lightning/src/ln/channel_keys.rs index ed173b0ac52..e7658aa150a 100644 --- a/lightning/src/ln/channel_keys.rs +++ b/lightning/src/ln/channel_keys.rs @@ -25,126 +25,125 @@ use bitcoin::secp256k1::PublicKey; use bitcoin::hashes::sha256::Hash as Sha256; macro_rules! doc_comment { - ($x:expr, $($tt:tt)*) => { - #[doc = $x] - $($tt)* - }; + ($x:expr, $($tt:tt)*) => { + #[doc = $x] + $($tt)* + }; } macro_rules! basepoint_impl { - ($BasepointT:ty) => { - impl $BasepointT { - /// Get inner Public Key - pub fn to_public_key(&self) -> PublicKey { - self.0 - } - } - - impl From for $BasepointT { - fn from(value: PublicKey) -> Self { - Self(value) - } - } - - } + ($BasepointT:ty) => { + impl $BasepointT { + /// Get inner Public Key + pub fn to_public_key(&self) -> PublicKey { + self.0 + } + } + + impl From for $BasepointT { + fn from(value: PublicKey) -> Self { + Self(value) + } + } + + } } macro_rules! key_impl { - ($BasepointT:ty, $KeyName:expr) => { - doc_comment! { - concat!("Generate ", $KeyName, " using per_commitment_point"), - pub fn from_basepoint( - secp_ctx: &Secp256k1, - basepoint: &$BasepointT, - per_commitment_point: &PublicKey, - ) -> Self { - Self(derive_public_key(secp_ctx, per_commitment_point, &basepoint.0)) - } - } - - doc_comment! { - concat!("Generate ", $KeyName, " from privkey"), - pub fn from_secret_key(secp_ctx: &Secp256k1, sk: &SecretKey) -> Self { - Self(PublicKey::from_secret_key(&secp_ctx, &sk)) - } - } - - /// Get inner Public Key - pub fn to_public_key(&self) -> PublicKey { - self.0 - } - } + ($BasepointT:ty, $KeyName:expr) => { + doc_comment! { + concat!("Derive a public ", $KeyName, " using one node's `per_commitment_point` and its countersignatory's `basepoint`"), + pub fn from_basepoint( + secp_ctx: &Secp256k1, + countersignatory_basepoint: &$BasepointT, + per_commitment_point: &PublicKey, + ) -> Self { + Self(derive_public_key(secp_ctx, per_commitment_point, &countersignatory_basepoint.0)) + } + } + + doc_comment! { + concat!("Build a ", $KeyName, " directly from an already-derived private key"), + pub fn from_secret_key(secp_ctx: &Secp256k1, sk: &SecretKey) -> Self { + Self(PublicKey::from_secret_key(&secp_ctx, &sk)) + } + } + + /// Get inner Public Key + pub fn to_public_key(&self) -> PublicKey { + self.0 + } + } } macro_rules! key_read_write { - ($SelfT:ty) => { - impl Writeable for $SelfT { - fn write(&self, w: &mut W) -> Result<(), io::Error> { - self.0.serialize().write(w) - } - } - - impl Readable for $SelfT { - fn read(r: &mut R) -> Result { - let key: PublicKey = Readable::read(r)?; - Ok(Self(key)) - } - } - } + ($SelfT:ty) => { + impl Writeable for $SelfT { + fn write(&self, w: &mut W) -> Result<(), io::Error> { + self.0.serialize().write(w) + } + } + + impl Readable for $SelfT { + fn read(r: &mut R) -> Result { + let key: PublicKey = Readable::read(r)?; + Ok(Self(key)) + } + } + } } -/// Master key used in conjunction with per_commitment_point to generate [`local_delayedpubkey`](https://github.com/lightning/bolts/blob/master/03-transactions.md#key-derivation) for the latest state of a channel. -/// A watcher can be given a [DelayedPaymentBasepoint] to generate per commitment [DelayedPaymentKey] to create justice transactions. +/// Base key used in conjunction with a `per_commitment_point` to generate a [`DelayedPaymentKey`]. +/// +/// The delayed payment key is used to pay the commitment state broadcaster their +/// non-HTLC-encumbered funds after a delay to give their counterparty a chance to punish if the +/// state broadcasted was previously revoked. #[derive(PartialEq, Eq, Clone, Copy, Debug, Hash)] pub struct DelayedPaymentBasepoint(pub PublicKey); basepoint_impl!(DelayedPaymentBasepoint); key_read_write!(DelayedPaymentBasepoint); -/// [delayedpubkey](https://github.com/lightning/bolts/blob/master/03-transactions.md#localpubkey-local_htlcpubkey-remote_htlcpubkey-local_delayedpubkey-and-remote_delayedpubkey-derivation) -/// To allow a counterparty to contest a channel state published by a node, Lightning protocol sets delays for some of the outputs, before can be spend. -/// For example a commitment transaction has to_local output encumbered by a delay, negotiated at the channel establishment flow. -/// To spend from such output a node has to generate a script using, among others, a local delayed payment key. + +/// A derived key built from a [`DelayedPaymentBasepoint`] and `per_commitment_point`. +/// +/// The delayed payment key is used to pay the commitment state broadcaster their +/// non-HTLC-encumbered funds after a delay. This delay gives their counterparty a chance to +/// punish and claim all the channel funds if the state broadcasted was previously revoked. +/// +/// [See the BOLT specs] +/// (https://github.com/lightning/bolts/blob/master/03-transactions.md#localpubkey-local_htlcpubkey-remote_htlcpubkey-local_delayedpubkey-and-remote_delayedpubkey-derivation) +/// for more information on key derivation details. #[derive(PartialEq, Eq, Clone, Copy, Debug)] pub struct DelayedPaymentKey(pub PublicKey); impl DelayedPaymentKey { - key_impl!(DelayedPaymentBasepoint, "delayedpubkey"); + key_impl!(DelayedPaymentBasepoint, "delayedpubkey"); } key_read_write!(DelayedPaymentKey); -/// Master key used in conjunction with per_commitment_point to generate a [localpubkey](https://github.com/lightning/bolts/blob/master/03-transactions.md#key-derivation) for the latest state of a channel. -/// Also used to generate a commitment number in a commitment transaction or as a Payment Key for a remote node (not us) in an anchor output if `option_static_remotekey` is enabled. -/// Shared by both nodes in a channel establishment message flow. -#[derive(PartialEq, Eq, Clone, Copy, Debug, Hash)] -pub struct PaymentBasepoint(pub PublicKey); -basepoint_impl!(PaymentBasepoint); -key_read_write!(PaymentBasepoint); - - -/// [localpubkey](https://github.com/lightning/bolts/blob/master/03-transactions.md#localpubkey-local_htlcpubkey-remote_htlcpubkey-local_delayedpubkey-and-remote_delayedpubkey-derivation) is a child key of a payment basepoint, -/// that enables a secure hash-lock for off-chain payments without risk of funds getting stuck or stolen. A payment key is normally shared with a counterparty so that it can generate -/// a commitment transaction's to_remote ouput, which our node can claim in case the counterparty force closes the channel. -#[derive(PartialEq, Eq, Clone, Copy, Debug)] -pub struct PaymentKey(pub PublicKey); - -impl PaymentKey { - key_impl!(PaymentBasepoint, "localpubkey"); -} -key_read_write!(PaymentKey); - -/// Master key used in conjunction with per_commitment_point to generate [htlcpubkey](https://github.com/lightning/bolts/blob/master/03-transactions.md#key-derivation) for the latest state of a channel. +/// Base key used in conjunction with a `per_commitment_point` to generate an [`HtlcKey`]. +/// +/// HTLC keys are used to ensure only the recipient of an HTLC can claim it on-chain with the HTLC +/// preimage and that only the sender of an HTLC can claim it on-chain after it has timed out. +/// Thus, both channel counterparties' HTLC keys will appears in each HTLC output's script. #[derive(PartialEq, Eq, Clone, Copy, Debug, Hash)] pub struct HtlcBasepoint(pub PublicKey); basepoint_impl!(HtlcBasepoint); key_read_write!(HtlcBasepoint); - -/// [htlcpubkey](https://github.com/lightning/bolts/blob/master/03-transactions.md#localpubkey-local_htlcpubkey-remote_htlcpubkey-local_delayedpubkey-and-remote_delayedpubkey-derivation) is a child key of an htlc basepoint, -/// that enables secure routing of payments in onion scheme without a risk of them getting stuck or diverted. It is used to claim the funds in successful or timed out htlc outputs. +/// A derived key built from a [`HtlcBasepoint`] and `per_commitment_point`. +/// +/// HTLC keys are used to ensure only the recipient of an HTLC can claim it on-chain with the HTLC +/// preimage and that only the sender of an HTLC can claim it on-chain after it has timed out. +/// Thus, both channel counterparties' HTLC keys will appears in each HTLC output's script. +/// +/// [See the BOLT specs] +/// (https://github.com/lightning/bolts/blob/master/03-transactions.md#localpubkey-local_htlcpubkey-remote_htlcpubkey-local_delayedpubkey-and-remote_delayedpubkey-derivation) +/// for more information on key derivation details. #[derive(PartialEq, Eq, Clone, Copy, Debug)] pub struct HtlcKey(pub PublicKey); impl HtlcKey { - key_impl!(HtlcBasepoint, "htlcpubkey"); + key_impl!(HtlcBasepoint, "htlcpubkey"); } key_read_write!(HtlcKey); @@ -156,7 +155,6 @@ fn derive_public_key(secp_ctx: &Secp256k1, per_commitm sha.input(&per_commitment_point.serialize()); sha.input(&base_point.serialize()); let res = Sha256::from_engine(sha).to_byte_array(); - let hashkey = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&res).expect("Hashes should always be valid keys unless SHA-256 is broken")); @@ -172,68 +170,71 @@ basepoint_impl!(RevocationBasepoint); key_read_write!(RevocationBasepoint); -/// [htlcpubkey](https://github.com/lightning/bolts/blob/master/03-transactions.md#localpubkey-local_htlcpubkey-remote_htlcpubkey-local_delayedpubkey-and-remote_delayedpubkey-derivation) is a child key of a revocation basepoint, -/// that enables a node to create a justice transaction punishing a counterparty for an attempt to steal funds. Used to in generation of commitment and htlc outputs. +/// The revocation key is used to allow a channel party to revoke their state - giving their +/// counterparty the required material to claim all of their funds if they broadcast that state. +/// +/// Each commitment transaction has a revocation key based on the basepoint and +/// per_commitment_point which is used in both commitment and HTLC transactions. +/// +/// See [the BOLT spec for derivation details] +/// (https://github.com/lightning/bolts/blob/master/03-transactions.md#revocationpubkey-derivation) #[derive(PartialEq, Eq, Clone, Copy, Debug, Hash)] pub struct RevocationKey(pub PublicKey); impl RevocationKey { - /// Derives a per-commitment-transaction revocation public key from its constituent parts. This is - /// the public equivalend of derive_private_revocation_key - using only public keys to derive a - /// public key instead of private keys. - /// - /// Only the cheating participant owns a valid witness to propagate a revoked - /// commitment transaction, thus per_commitment_point always come from cheater - /// and revocation_base_point always come from punisher, which is the broadcaster - /// of the transaction spending with this key knowledge. - /// - /// Note that this is infallible iff we trust that at least one of the two input keys are randomly - /// generated (ie our own). - pub fn from_basepoint( - secp_ctx: &Secp256k1, - basepoint: &RevocationBasepoint, - per_commitment_point: &PublicKey, - ) -> Self { - let rev_append_commit_hash_key = { - let mut sha = Sha256::engine(); - sha.input(&basepoint.to_public_key().serialize()); - sha.input(&per_commitment_point.serialize()); - - Sha256::from_engine(sha).to_byte_array() - }; - let commit_append_rev_hash_key = { - let mut sha = Sha256::engine(); - sha.input(&per_commitment_point.serialize()); - sha.input(&basepoint.to_public_key().serialize()); - - Sha256::from_engine(sha).to_byte_array() - }; - - let countersignatory_contrib = basepoint.to_public_key().mul_tweak(&secp_ctx, &Scalar::from_be_bytes(rev_append_commit_hash_key).unwrap()) - .expect("Multiplying a valid public key by a hash is expected to never fail per secp256k1 docs"); - let broadcaster_contrib = (&per_commitment_point).mul_tweak(&secp_ctx, &Scalar::from_be_bytes(commit_append_rev_hash_key).unwrap()) - .expect("Multiplying a valid public key by a hash is expected to never fail per secp256k1 docs"); - let pk = countersignatory_contrib.combine(&broadcaster_contrib) - .expect("Addition only fails if the tweak is the inverse of the key. This is not possible when the tweak commits to the key."); - Self(pk) - } - - /// Get inner Public Key - pub fn to_public_key(&self) -> PublicKey { - self.0 - } + /// Derives a per-commitment-transaction revocation public key from one party's per-commitment + /// point and the other party's [`RevocationBasepoint`]. This is the public equivalent of + /// [`chan_utils::derive_private_revocation_key`] - using only public keys to derive a public + /// key instead of private keys. + /// + /// Note that this is infallible iff we trust that at least one of the two input keys are randomly + /// generated (ie our own). + /// + /// [`chan_utils::derive_private_revocation_key`]: crate::ln::chan_utils::derive_private_revocation_key + pub fn from_basepoint( + secp_ctx: &Secp256k1, + countersignatory_basepoint: &RevocationBasepoint, + per_commitment_point: &PublicKey, + ) -> Self { + let rev_append_commit_hash_key = { + let mut sha = Sha256::engine(); + sha.input(&countersignatory_basepoint.to_public_key().serialize()); + sha.input(&per_commitment_point.serialize()); + + Sha256::from_engine(sha).to_byte_array() + }; + let commit_append_rev_hash_key = { + let mut sha = Sha256::engine(); + sha.input(&per_commitment_point.serialize()); + sha.input(&countersignatory_basepoint.to_public_key().serialize()); + + Sha256::from_engine(sha).to_byte_array() + }; + + let countersignatory_contrib = countersignatory_basepoint.to_public_key().mul_tweak(&secp_ctx, &Scalar::from_be_bytes(rev_append_commit_hash_key).unwrap()) + .expect("Multiplying a valid public key by a hash is expected to never fail per secp256k1 docs"); + let broadcaster_contrib = (&per_commitment_point).mul_tweak(&secp_ctx, &Scalar::from_be_bytes(commit_append_rev_hash_key).unwrap()) + .expect("Multiplying a valid public key by a hash is expected to never fail per secp256k1 docs"); + let pk = countersignatory_contrib.combine(&broadcaster_contrib) + .expect("Addition only fails if the tweak is the inverse of the key. This is not possible when the tweak commits to the key."); + Self(pk) + } + + /// Get inner Public Key + pub fn to_public_key(&self) -> PublicKey { + self.0 + } } key_read_write!(RevocationKey); - #[cfg(test)] mod test { - use bitcoin::secp256k1::{Secp256k1, SecretKey, PublicKey}; - use bitcoin::hashes::hex::FromHex; - use super::derive_public_key; + use bitcoin::secp256k1::{Secp256k1, SecretKey, PublicKey}; + use bitcoin::hashes::hex::FromHex; + use super::derive_public_key; - #[test] + #[test] fn test_key_derivation() { // Test vectors from BOLT 3 Appendix E: let secp_ctx = Secp256k1::new(); @@ -248,6 +249,6 @@ mod test { assert_eq!(per_commitment_point.serialize()[..], >::from_hex("025f7117a78150fe2ef97db7cfc83bd57b2e2c0d0dd25eaf467a4a1c2a45ce1486").unwrap()[..]); assert_eq!(derive_public_key(&secp_ctx, &per_commitment_point, &base_point).serialize()[..], - >::from_hex("0235f2dbfaa89b57ec7b055afe29849ef7ddfeb1cefdb9ebdc43f5494984db29e5").unwrap()[..]); + >::from_hex("0235f2dbfaa89b57ec7b055afe29849ef7ddfeb1cefdb9ebdc43f5494984db29e5").unwrap()[..]); } } diff --git a/lightning/src/ln/channel_splice.rs b/lightning/src/ln/channel_splice.rs index 481d503e094..e87f9337aba 100644 --- a/lightning/src/ln/channel_splice.rs +++ b/lightning/src/ln/channel_splice.rs @@ -13,6 +13,7 @@ use crate::chain::transaction::OutPoint; use crate::ln::ChannelId; use crate::ln::channel::ChannelError; use crate::prelude::*; +use crate::util::ser::TransactionU16LenLimited; use bitcoin::{ScriptBuf, Sequence, Transaction, TxIn, Witness}; use core::convert::TryFrom; @@ -99,7 +100,7 @@ impl PendingSpliceInfoPost { } /// Get a transaction input that is the previous funding transaction - pub(super) fn get_input_of_previous_funding(&self) -> Result<(TxIn, Transaction), ChannelError> { + pub(super) fn get_input_of_previous_funding(&self) -> Result<(TxIn, TransactionU16LenLimited), ChannelError> { if let Some(pre_funding_transaction) = &self.pre_funding_transaction { if let Some(pre_funding_txo) = &self.pre_funding_txo { Ok(( @@ -109,7 +110,7 @@ impl PendingSpliceInfoPost { sequence: Sequence::ZERO, witness: Witness::new(), }, - pre_funding_transaction.clone(), + TransactionU16LenLimited(pre_funding_transaction.clone()), )) } else { Err(ChannelError::Warn("Internal error: Missing previous funding transaction outpoint".to_string())) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 347277de9e3..bf70ca87127 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -31,7 +31,9 @@ use bitcoin::hash_types::{BlockHash, Txid}; use bitcoin::secp256k1::{SecretKey,PublicKey}; use bitcoin::secp256k1::Secp256k1; -use bitcoin::{secp256k1, Sequence, TxIn}; +use bitcoin::{secp256k1, Sequence}; +#[cfg(dual_funding)] +use bitcoin::TxIn; use crate::blinded_path::BlindedPath; use crate::blinded_path::payment::{PaymentConstraints, ReceiveTlvs}; @@ -46,20 +48,20 @@ use crate::events::{Event, EventHandler, EventsProvider, MessageSendEvent, Messa // construct one themselves. use crate::ln::{inbound_payment, ChannelId, PaymentHash, PaymentPreimage, PaymentSecret}; use crate::ln::channel::{Channel, ChannelPhase, ChannelContext, ChannelError, ChannelUpdateStatus, ShutdownResult, UnfundedChannelContext, UpdateFulfillCommitFetch, OutboundV1Channel, InboundV1Channel, WithChannelContext}; +pub use crate::ln::channel::{InboundHTLCDetails, InboundHTLCStateDetails, OutboundHTLCDetails, OutboundHTLCStateDetails}; #[cfg(dual_funding)] use crate::ln::channel::DualFundingChannelContext; #[cfg(dual_funding)] use crate::ln::channel::{InboundV2Channel, OutboundV2Channel}; +#[cfg(dual_funding)] use crate::ln::channel_splice::PendingSpliceInfoPre; use crate::ln::features::{Bolt12InvoiceFeatures, ChannelFeatures, ChannelTypeFeatures, InitFeatures, NodeFeatures}; #[cfg(any(feature = "_test_utils", test))] use crate::ln::features::Bolt11InvoiceFeatures; #[cfg(dual_funding)] use crate::ln::interactivetxs::InteractiveTxMessageSend; -use crate::routing::gossip::NetworkGraph; -use crate::routing::router::{BlindedTail, DefaultRouter, InFlightHtlcs, Path, Payee, PaymentParameters, Route, RouteParameters, Router}; -use crate::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringFeeParameters}; -use crate::ln::onion_payment::{check_incoming_htlc_cltv, create_recv_pending_htlc_info, create_fwd_pending_htlc_info, decode_incoming_update_add_htlc_onion, InboundOnionErr, NextPacketDetails}; +use crate::routing::router::{BlindedTail, InFlightHtlcs, Path, Payee, PaymentParameters, Route, RouteParameters, Router}; +use crate::ln::onion_payment::{check_incoming_htlc_cltv, create_recv_pending_htlc_info, create_fwd_pending_htlc_info, decode_incoming_update_add_htlc_onion, InboundHTLCErr, NextPacketDetails}; use crate::ln::msgs; use crate::ln::onion_utils; use crate::ln::onion_utils::{HTLCFailReason, INVALID_ONION_BLINDING}; @@ -70,22 +72,39 @@ use crate::ln::msgs::CommitmentUpdate; use crate::ln::outbound_payment; use crate::ln::outbound_payment::{Bolt12PaymentError, OutboundPayments, PaymentAttempts, PendingOutboundPayment, SendAlongPathArgs, StaleExpiration}; use crate::ln::wire::Encode; -use crate::offers::invoice::{BlindedPayInfo, Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, DerivedSigningPubkey, InvoiceBuilder}; +use crate::offers::invoice::{BlindedPayInfo, Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, DerivedSigningPubkey, ExplicitSigningPubkey, InvoiceBuilder, UnsignedBolt12Invoice}; use crate::offers::invoice_error::InvoiceError; +use crate::offers::invoice_request::{DerivedPayerId, InvoiceRequestBuilder}; use crate::offers::merkle::SignError; -use crate::offers::offer::{DerivedMetadata, Offer, OfferBuilder}; +use crate::offers::offer::{Offer, OfferBuilder}; use crate::offers::parse::Bolt12SemanticError; use crate::offers::refund::{Refund, RefundBuilder}; -use crate::onion_message::{Destination, OffersMessage, OffersMessageHandler, PendingOnionMessage, new_pending_onion_message}; -use crate::sign::{EntropySource, KeysManager, NodeSigner, Recipient, SignerProvider}; +use crate::onion_message::messenger::{Destination, MessageRouter, PendingOnionMessage, new_pending_onion_message}; +use crate::onion_message::offers::{OffersMessage, OffersMessageHandler}; +use crate::sign::{EntropySource, NodeSigner, Recipient, SignerProvider}; use crate::sign::ecdsa::WriteableEcdsaChannelSigner; use crate::util::config::{UserConfig, ChannelConfig, ChannelConfigUpdate}; use crate::util::wakers::{Future, Notifier}; use crate::util::scid_utils::fake_scid; use crate::util::string::UntrustedString; use crate::util::ser::{BigSize, FixedLengthReader, Readable, ReadableArgs, MaybeReadable, Writeable, Writer, VecWriter}; +#[cfg(dual_funding)] +use crate::util::ser::TransactionU16LenLimited; use crate::util::logger::{Level, Logger, WithContext}; use crate::util::errors::APIError; +#[cfg(not(c_bindings))] +use { + crate::offers::offer::DerivedMetadata, + crate::routing::router::DefaultRouter, + crate::routing::gossip::NetworkGraph, + crate::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringFeeParameters}, + crate::sign::KeysManager, +}; +#[cfg(c_bindings)] +use { + crate::offers::offer::OfferWithDerivedMetadataBuilder, + crate::offers::refund::RefundMaybeWithDerivedMetadataBuilder, +}; use alloc::collections::{btree_map, BTreeMap}; @@ -124,6 +143,7 @@ use std::str; /// Information about where a received HTLC('s onion) has indicated the HTLC should go. #[derive(Clone)] // See Channel::revoke_and_ack for why, tl;dr: Rust bug +#[cfg_attr(test, derive(Debug, PartialEq))] pub enum PendingHTLCRouting { /// An HTLC which should be forwarded on to another node. Forward { @@ -168,6 +188,8 @@ pub enum PendingHTLCRouting { /// [`Event::PaymentClaimable::onion_fields`] as /// [`RecipientOnionFields::custom_tlvs`]. custom_tlvs: Vec<(u64, Vec)>, + /// Set if this HTLC is the final hop in a multi-hop blinded path. + requires_blinded_error: bool, }, /// The onion indicates that this is for payment to us but which contains the preimage for /// claiming included, and is unrelated to any invoice we'd previously generated (aka a @@ -200,23 +222,24 @@ pub enum PendingHTLCRouting { } /// Information used to forward or fail this HTLC that is being forwarded within a blinded path. -#[derive(Clone, Copy, Hash, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] pub struct BlindedForward { /// The `blinding_point` that was set in the inbound [`msgs::UpdateAddHTLC`], or in the inbound /// onion payload if we're the introduction node. Useful for calculating the next hop's /// [`msgs::UpdateAddHTLC::blinding_point`]. pub inbound_blinding_point: PublicKey, - // Another field will be added here when we support forwarding as a non-intro node. + /// If needed, this determines how this HTLC should be failed backwards, based on whether we are + /// the introduction node. + pub failure: BlindedFailure, } impl PendingHTLCRouting { // Used to override the onion failure code and data if the HTLC is blinded. fn blinded_failure(&self) -> Option { - // TODO: needs update when we support receiving to multi-hop blinded paths - if let Self::Forward { blinded: Some(_), .. } = self { - Some(BlindedFailure::FromIntroductionNode) - } else { - None + match self { + Self::Forward { blinded: Some(BlindedForward { failure, .. }), .. } => Some(*failure), + Self::Receive { requires_blinded_error: true, .. } => Some(BlindedFailure::FromBlindedNode), + _ => None, } } } @@ -224,6 +247,7 @@ impl PendingHTLCRouting { /// Information about an incoming HTLC, including the [`PendingHTLCRouting`] describing where it /// should go next. #[derive(Clone)] // See Channel::revoke_and_ack for why, tl;dr: Rust bug +#[cfg_attr(test, derive(Debug, PartialEq))] pub struct PendingHTLCInfo { /// Further routing details based on whether the HTLC is being forwarded or received. pub routing: PendingHTLCRouting, @@ -278,6 +302,7 @@ pub(super) enum PendingHTLCStatus { Fail(HTLCFailureMsg), } +#[cfg_attr(test, derive(Clone, Debug, PartialEq))] pub(super) struct PendingAddHTLCInfo { pub(super) forward_info: PendingHTLCInfo, @@ -294,19 +319,30 @@ pub(super) struct PendingAddHTLCInfo { prev_user_channel_id: u128, } +#[cfg_attr(test, derive(Clone, Debug, PartialEq))] pub(super) enum HTLCForwardInfo { AddHTLC(PendingAddHTLCInfo), FailHTLC { htlc_id: u64, err_packet: msgs::OnionErrorPacket, }, + FailMalformedHTLC { + htlc_id: u64, + failure_code: u16, + sha256_of_onion: [u8; 32], + }, } -// Used for failing blinded HTLCs backwards correctly. -#[derive(Clone, Debug, Hash, PartialEq, Eq)] -enum BlindedFailure { +/// Whether this blinded HTLC is being failed backwards by the introduction node or a blinded node, +/// which determines the failure message that should be used. +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] +pub enum BlindedFailure { + /// This HTLC is being failed backwards by the introduction node, and thus should be failed with + /// [`msgs::UpdateFailHTLC`] and error code `0x8000|0x4000|24`. FromIntroductionNode, - // Another variant will be added here for non-intro nodes. + /// This HTLC is being failed backwards by a blinded node within the path, and thus should be + /// failed with [`msgs::UpdateFailMalformedHTLC`] and error code `0x8000|0x4000|24`. + FromBlindedNode, } /// Tracks the inbound corresponding to an outbound HTLC @@ -549,9 +585,8 @@ impl Into for FailureCode { struct MsgHandleErrInternal { err: msgs::LightningError, - chan_id: Option<(ChannelId, u128)>, // If Some a channel of ours has been closed + closes_channel: bool, shutdown_finish: Option<(ShutdownResult, Option)>, - channel_capacity: Option, } impl MsgHandleErrInternal { #[inline] @@ -566,17 +601,16 @@ impl MsgHandleErrInternal { }, }, }, - chan_id: None, + closes_channel: false, shutdown_finish: None, - channel_capacity: None, } } #[inline] fn from_no_close(err: msgs::LightningError) -> Self { - Self { err, chan_id: None, shutdown_finish: None, channel_capacity: None } + Self { err, closes_channel: false, shutdown_finish: None } } #[inline] - fn from_finish_shutdown(err: String, channel_id: ChannelId, user_channel_id: u128, shutdown_res: ShutdownResult, channel_update: Option, channel_capacity: u64) -> Self { + fn from_finish_shutdown(err: String, channel_id: ChannelId, shutdown_res: ShutdownResult, channel_update: Option) -> Self { let err_msg = msgs::ErrorMessage { channel_id, data: err.clone() }; let action = if shutdown_res.monitor_update.is_some() { // We have a closing `ChannelMonitorUpdate`, which means the channel was funded and we @@ -588,9 +622,8 @@ impl MsgHandleErrInternal { }; Self { err: LightningError { err, action }, - chan_id: Some((channel_id, user_channel_id)), + closes_channel: true, shutdown_finish: Some((shutdown_res, channel_update)), - channel_capacity: Some(channel_capacity) } } #[inline] @@ -621,14 +654,13 @@ impl MsgHandleErrInternal { }, }, }, - chan_id: None, + closes_channel: false, shutdown_finish: None, - channel_capacity: None, } } fn closes_channel(&self) -> bool { - self.chan_id.is_some() + self.closes_channel } } @@ -703,7 +735,7 @@ enum BackgroundEvent { /// /// Note that any such events are lost on shutdown, so in general they must be updates which /// are regenerated on startup. - ClosedMonitorUpdateRegeneratedOnStartup((OutPoint, ChannelMonitorUpdate)), + ClosedMonitorUpdateRegeneratedOnStartup((OutPoint, ChannelId, ChannelMonitorUpdate)), /// Handle a ChannelMonitorUpdate which may or may not close the channel and may unblock the /// channel to continue normal operation. /// @@ -717,6 +749,7 @@ enum BackgroundEvent { MonitorUpdateRegeneratedOnStartup { counterparty_node_id: PublicKey, funding_txo: OutPoint, + channel_id: ChannelId, update: ChannelMonitorUpdate }, /// Some [`ChannelMonitorUpdate`] (s) completed before we were serialized but we still have @@ -745,7 +778,7 @@ pub(crate) enum MonitorUpdateCompletionAction { /// outbound edge. EmitEventAndFreeOtherChannel { event: events::Event, - downstream_counterparty_and_funding_outpoint: Option<(PublicKey, OutPoint, RAAMonitorUpdateBlockingAction)>, + downstream_counterparty_and_funding_outpoint: Option<(PublicKey, OutPoint, ChannelId, RAAMonitorUpdateBlockingAction)>, }, /// Indicates we should immediately resume the operation of another channel, unless there is /// some other reason why the channel is blocked. In practice this simply means immediately @@ -763,6 +796,7 @@ pub(crate) enum MonitorUpdateCompletionAction { downstream_counterparty_node_id: PublicKey, downstream_funding_outpoint: OutPoint, blocking_action: RAAMonitorUpdateBlockingAction, + downstream_channel_id: ChannelId, }, } @@ -774,6 +808,9 @@ impl_writeable_tlv_based_enum_upgradable!(MonitorUpdateCompletionAction, (0, downstream_counterparty_node_id, required), (2, downstream_funding_outpoint, required), (4, blocking_action, required), + // Note that by the time we get past the required read above, downstream_funding_outpoint will be + // filled in, so we can safely unwrap it here. + (5, downstream_channel_id, (default_value, ChannelId::v1_from_funding_outpoint(downstream_funding_outpoint.0.unwrap()))), }, (2, EmitEventAndFreeOtherChannel) => { (0, event, upgradable_required), @@ -791,12 +828,16 @@ pub(crate) enum EventCompletionAction { ReleaseRAAChannelMonitorUpdate { counterparty_node_id: PublicKey, channel_funding_outpoint: OutPoint, + channel_id: ChannelId, }, } impl_writeable_tlv_based_enum!(EventCompletionAction, (0, ReleaseRAAChannelMonitorUpdate) => { (0, channel_funding_outpoint, required), (2, counterparty_node_id, required), + // Note that by the time we get past the required read above, channel_funding_outpoint will be + // filled in, so we can safely unwrap it here. + (3, channel_id, (default_value, ChannelId::v1_from_funding_outpoint(channel_funding_outpoint.0.unwrap()))), }; ); @@ -888,7 +929,16 @@ impl PeerState where SP::Target: SignerProvider { if require_disconnected && self.is_connected { return false } - self.channel_by_id.iter().filter(|(_, phase)| matches!(phase, ChannelPhase::Funded(_))).count() == 0 + !self.channel_by_id.iter().any(|(_, phase)| + match phase { + ChannelPhase::Funded(_) | ChannelPhase::UnfundedOutboundV1(_) => true, + ChannelPhase::UnfundedInboundV1(_) => false, + #[cfg(dual_funding)] + ChannelPhase::UnfundedOutboundV2(_) => true, + #[cfg(dual_funding)] + ChannelPhase::UnfundedInboundV2(_) => false, + } + ) && self.monitor_update_blocked_actions.is_empty() && self.in_flight_monitor_updates.is_empty() } @@ -967,6 +1017,7 @@ pub type SimpleArcChannelManager = ChannelManager< Arc>>, Arc, + Arc, Arc>>, Arc>>>, ProbabilisticScoringFeeParameters, ProbabilisticScorer>>, Arc>, @@ -997,6 +1048,7 @@ pub type SimpleRefChannelManager<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, M, T, F, L> = &'e DefaultRouter< &'f NetworkGraph<&'g L>, &'g L, + &'c KeysManager, &'h RwLock, &'g L>>, ProbabilisticScoringFeeParameters, ProbabilisticScorer<&'f NetworkGraph<&'g L>, &'g L> @@ -1161,7 +1213,7 @@ where // | // |__`peer_state` // | -// |__`id_to_peer` +// |__`outpoint_to_peer` // | // |__`short_to_chan_info` // | @@ -1255,11 +1307,7 @@ where /// See `ChannelManager` struct-level documentation for lock order requirements. outbound_scid_aliases: Mutex>, - /// `channel_id` -> `counterparty_node_id`. - /// - /// Only `channel_id`s are allowed as keys in this map, and not `temporary_channel_id`s. As - /// multiple channels with the same `temporary_channel_id` to different peers can exist, - /// allowing `temporary_channel_id`s in this map would cause collisions for such channels. + /// Channel funding outpoint -> `counterparty_node_id`. /// /// Note that this map should only be used for `MonitorEvent` handling, to be able to access /// the corresponding channel for the event, as we only have access to the `channel_id` during @@ -1277,7 +1325,10 @@ where /// required to access the channel with the `counterparty_node_id`. /// /// See `ChannelManager` struct-level documentation for lock order requirements. - id_to_peer: Mutex>, + #[cfg(not(test))] + outpoint_to_peer: Mutex>, + #[cfg(test)] + pub(crate) outpoint_to_peer: Mutex>, /// SCIDs (and outbound SCID aliases) -> `counterparty_node_id`s and `channel_id`s. /// @@ -1543,13 +1594,11 @@ pub const MIN_FINAL_CLTV_EXPIRY_DELTA: u16 = HTLC_FAIL_BACK_BUFFER as u16 + 3; // then waiting ANTI_REORG_DELAY to be reorg-safe on the outbound HLTC and // failing the corresponding htlc backward, and us now seeing the last block of ANTI_REORG_DELAY before // LATENCY_GRACE_PERIOD_BLOCKS. -#[deny(const_err)] #[allow(dead_code)] const CHECK_CLTV_EXPIRY_SANITY: u32 = MIN_CLTV_EXPIRY_DELTA as u32 - LATENCY_GRACE_PERIOD_BLOCKS - CLTV_CLAIM_BUFFER - ANTI_REORG_DELAY - LATENCY_GRACE_PERIOD_BLOCKS; // Check for ability of an attacker to make us fail on-chain by delaying an HTLC claim. See // ChannelMonitor::should_broadcast_holder_commitment_txn for a description of why this is needed. -#[deny(const_err)] #[allow(dead_code)] const CHECK_CLTV_EXPIRY_SANITY_2: u32 = MIN_CLTV_EXPIRY_DELTA as u32 - LATENCY_GRACE_PERIOD_BLOCKS - 2*CLTV_CLAIM_BUFFER; @@ -1631,9 +1680,6 @@ pub struct ChannelDetails { pub counterparty: ChannelCounterparty, /// The Channel's funding transaction output, if we've negotiated the funding transaction with /// our counterparty already. - /// - /// Note that, if this has been set, `channel_id` will be equivalent to - /// `funding_txo.unwrap().to_channel_id()`. pub funding_txo: Option, /// The features which this channel operates with. See individual features for more info. /// @@ -1799,6 +1845,14 @@ pub struct ChannelDetails { /// /// This field is only `None` for `ChannelDetails` objects serialized prior to LDK 0.0.109. pub config: Option, + /// Pending inbound HTLCs. + /// + /// This field is empty for objects serialized with LDK versions prior to 0.0.122. + pub pending_inbound_htlcs: Vec, + /// Pending outbound HTLCs. + /// + /// This field is empty for objects serialized with LDK versions prior to 0.0.122. + pub pending_outbound_htlcs: Vec, } impl ChannelDetails { @@ -1876,6 +1930,8 @@ impl ChannelDetails { inbound_htlc_maximum_msat: context.get_holder_htlc_maximum_msat(), config: Some(context.config()), channel_shutdown_state: Some(context.shutdown_state()), + pending_inbound_htlcs: context.get_pending_inbound_htlc_details(), + pending_outbound_htlcs: context.get_pending_outbound_htlc_details(), } } } @@ -1970,30 +2026,27 @@ macro_rules! handle_error { match $internal { Ok(msg) => Ok(msg), - Err(MsgHandleErrInternal { err, chan_id, shutdown_finish, channel_capacity }) => { + Err(MsgHandleErrInternal { err, shutdown_finish, .. }) => { let mut msg_events = Vec::with_capacity(2); if let Some((shutdown_res, update_option)) = shutdown_finish { + let counterparty_node_id = shutdown_res.counterparty_node_id; + let channel_id = shutdown_res.channel_id; + let logger = WithContext::from( + &$self.logger, Some(counterparty_node_id), Some(channel_id), + ); + log_error!(logger, "Force-closing channel: {}", err.err); + $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() }, - counterparty_node_id: Some($counterparty_node_id), - channel_capacity_sats: channel_capacity, - }, None)); - } + } else { + log_error!($self.logger, "Got non-closing error: {}", err.err); } - let logger = WithContext::from( - &$self.logger, Some($counterparty_node_id), chan_id.map(|(chan_id, _)| chan_id) - ); - log_error!(logger, "{}", err.err); if let msgs::ErrorAction::IgnoreError = err.action { } else { msg_events.push(events::MessageSendEvent::HandleError { @@ -2019,7 +2072,9 @@ macro_rules! handle_error { macro_rules! update_maps_on_chan_removal { ($self: expr, $channel_context: expr) => {{ - $self.id_to_peer.lock().unwrap().remove(&$channel_context.channel_id()); + if let Some(outpoint) = $channel_context.get_funding_txo() { + $self.outpoint_to_peer.lock().unwrap().remove(&outpoint); + } let mut short_to_chan_info = $self.short_to_chan_info.write().unwrap(); if let Some(short_id) = $channel_context.get_short_channel_id() { short_to_chan_info.remove(&short_id); @@ -2051,12 +2106,11 @@ macro_rules! convert_chan_phase_err { let logger = WithChannelContext::from(&$self.logger, &$channel.context); log_error!(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); - 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)) + let reason = ClosureReason::ProcessingError { err: msg.clone() }; + let shutdown_res = $channel.context.force_shutdown(true, reason); + let err = + MsgHandleErrInternal::from_finish_shutdown(msg, *$channel_id, shutdown_res, $channel_update); + (true, err) }, } }; @@ -2160,6 +2214,7 @@ macro_rules! emit_channel_pending_event { counterparty_node_id: $channel.context.get_counterparty_node_id(), user_channel_id: $channel.context.get_user_id(), funding_txo: $channel.context.get_funding_txo().unwrap().into_bitcoin_outpoint(), + channel_type: Some($channel.context.get_channel_type().clone()), }, None)); $channel.context.set_channel_pending_event_emitted(); } @@ -2186,7 +2241,7 @@ macro_rules! handle_monitor_update_completion { let logger = WithChannelContext::from(&$self.logger, &$chan.context); let mut updates = $chan.monitor_updating_restored(&&logger, &$self.node_signer, $self.chain_hash, &$self.default_configuration, - $self.best_block.read().unwrap().height()); + $self.best_block.read().unwrap().height); let counterparty_node_id = $chan.context.get_counterparty_node_id(); let channel_update = if updates.channel_ready.is_some() && $chan.context.is_usable() { // We only send a channel_update in the case where we are just now sending a @@ -2301,7 +2356,7 @@ macro_rules! handle_new_monitor_update { 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, $funding_txo: expr, $channel_id: expr, $update: expr, $peer_state_lock: expr, $peer_state: expr, $per_peer_state_lock: expr, $chan: 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 @@ -2440,14 +2495,14 @@ where best_block: RwLock::new(params.best_block), - outbound_scid_aliases: Mutex::new(HashSet::new()), - pending_inbound_payments: Mutex::new(HashMap::new()), + outbound_scid_aliases: Mutex::new(new_hash_set()), + pending_inbound_payments: Mutex::new(new_hash_map()), pending_outbound_payments: OutboundPayments::new(), - forward_htlcs: Mutex::new(HashMap::new()), - claimable_payments: Mutex::new(ClaimablePayments { claimable_payments: HashMap::new(), pending_claiming_payments: HashMap::new() }), - pending_intercepted_htlcs: Mutex::new(HashMap::new()), - id_to_peer: Mutex::new(HashMap::new()), - short_to_chan_info: FairRwLock::new(HashMap::new()), + forward_htlcs: Mutex::new(new_hash_map()), + claimable_payments: Mutex::new(ClaimablePayments { claimable_payments: new_hash_map(), pending_claiming_payments: new_hash_map() }), + pending_intercepted_htlcs: Mutex::new(new_hash_map()), + outpoint_to_peer: Mutex::new(new_hash_map()), + short_to_chan_info: FairRwLock::new(new_hash_map()), our_network_pubkey: node_signer.get_node_id(Recipient::Node).unwrap(), secp_ctx, @@ -2459,7 +2514,7 @@ where highest_seen_timestamp: AtomicUsize::new(current_timestamp as usize), - per_peer_state: FairRwLock::new(HashMap::new()), + per_peer_state: FairRwLock::new(new_hash_map()), pending_events: Mutex::new(VecDeque::new()), pending_events_processor: AtomicBool::new(false), @@ -2486,7 +2541,7 @@ where } fn create_and_insert_outbound_scid_alias(&self) -> u64 { - let height = self.best_block.read().unwrap().height(); + let height = self.best_block.read().unwrap().height; let mut outbound_scid_alias = 0; let mut i = 0; loop { @@ -2550,21 +2605,9 @@ where /// randomized value for inbound channels. `user_channel_id` has no meaning inside of LDK, it /// is simply copied to events and otherwise ignored. /// - /// `funnding_satoshis` is the amount we are contributing to the channel. + /// `funding_satoshis` is the amount we are contributing to the channel. /// Raises [`APIError::APIMisuseError`] when `funding_satoshis` > 2**24. /// - /// The `funding_inputs` parameter accepts ([`TxIn`], [`Transaction`]) pairs which will - /// be used to contribute `funding_satoshis` towards the channel (minus any mining fees due). - /// Raises [`APIError::APIMisuseError`] if the total value of the provided `funding_inputs` is - /// less than `funding_satoshis`. - // TODO(dual_funding): Describe error relating to inputs not being able to cover fees payable by us. - /// - /// LDK will create a change output for any non-dust amounts that remain after subtracting contribution - /// to channel capacity and fees from the provided input amounts. - // TODO(dual_funding): We could allow a list of such outputs to be provided so that the user may - /// be able to do some more interesting things at the same time as funding a channel, like making - /// some low priority on-chain payment. - /// /// The `funding_conf_target` parameter sets the priority of the funding transaction for appropriate /// fee estimation. If `None`, then [`ConfirmationTarget::Normal`] is used. /// @@ -2587,6 +2630,7 @@ where /// [`Event::FundingGenerationReady::temporary_channel_id`]: events::Event::FundingGenerationReady::temporary_channel_id /// [`Event::ChannelClosed::channel_id`]: events::Event::ChannelClosed::channel_id /// [`ConfirmationTarget::Normal`]: chain::chaininterface::ConfirmationTarget + #[cfg(dual_funding)] pub fn create_dual_funded_channel(&self, their_network_key: PublicKey, funding_satoshis: u64, funding_conf_target: Option, user_channel_id: u128, override_config: Option) -> Result @@ -2595,8 +2639,9 @@ where user_channel_id, None, override_config) } - fn create_channel_internal(&self, is_v2: bool, their_network_key: PublicKey, funding_satoshis: u64, - funding_conf_target: Option, push_msat: u64, user_channel_id: u128, + // TODO(dual_funding): Remove param _-prefix once #[cfg(dual_funding)] is dropped. + fn create_channel_internal(&self, _is_v2: bool, their_network_key: PublicKey, funding_satoshis: u64, + _funding_conf_target: Option, push_msat: u64, user_channel_id: u128, temporary_channel_id: Option, override_config: Option, ) -> Result { if funding_satoshis < 1000 { @@ -2625,11 +2670,12 @@ where let config = if override_config.is_some() { override_config.as_ref().unwrap() } else { &self.default_configuration }; // TODO(dual_funding): Merge this with below when cfg is removed. + #[cfg(not(dual_funding))] let (channel_phase, msg_send_event) = { let channel = { match OutboundV1Channel::new(&self.fee_estimator, &self.entropy_source, &self.signer_provider, their_network_key, their_features, funding_satoshis, push_msat, user_channel_id, config, - self.best_block.read().unwrap().height(), outbound_scid_alias, temporary_channel_id) + self.best_block.read().unwrap().height, outbound_scid_alias, temporary_channel_id) { Ok(res) => res, Err(e) => { @@ -2647,12 +2693,12 @@ where }; #[cfg(dual_funding)] - let (channel_phase, msg_send_event) = if is_v2 { + let (channel_phase, msg_send_event) = if _is_v2 { let channel = { match OutboundV2Channel::new(&self.fee_estimator, &self.entropy_source, &self.signer_provider, their_network_key, their_features, funding_satoshis, user_channel_id, config, - self.best_block.read().unwrap().height(), outbound_scid_alias, - funding_conf_target.unwrap_or(ConfirmationTarget::NonAnchorChannelFee)) + self.best_block.read().unwrap().height, outbound_scid_alias, + _funding_conf_target.unwrap_or(ConfirmationTarget::NonAnchorChannelFee)) { Ok(res) => res, Err(e) => { @@ -2661,17 +2707,17 @@ where }, } }; - let msg = channel.get_open_channel_v2(self.chain_hash); + let res = channel.get_open_channel_v2(self.chain_hash); let event = events::MessageSendEvent::SendOpenChannelV2 { node_id: their_network_key, - msg, + msg: res, }; (ChannelPhase::UnfundedOutboundV2(channel), event) } else { let channel = { match OutboundV1Channel::new(&self.fee_estimator, &self.entropy_source, &self.signer_provider, their_network_key, their_features, funding_satoshis, push_msat, user_channel_id, config, - self.best_block.read().unwrap().height(), outbound_scid_alias, temporary_channel_id) + self.best_block.read().unwrap().height, outbound_scid_alias, temporary_channel_id) { Ok(res) => res, Err(e) => { @@ -2787,13 +2833,13 @@ where fn list_funded_channels_with_filter)) -> bool + Copy>(&self, f: Fn) -> Vec { // 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 + // a scid or a scid alias, and the `outpoint_to_peer` shouldn't be used outside // of the ChannelMonitor handling. Therefore reallocations may still occur, but is // unlikely as the `short_to_chan_info` map often contains 2 entries for // the same channel. let mut res = Vec::with_capacity(self.short_to_chan_info.read().unwrap().len()); { - let best_block_height = self.best_block.read().unwrap().height(); + let best_block_height = self.best_block.read().unwrap().height; 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(); @@ -2820,13 +2866,13 @@ where pub fn list_channels(&self) -> Vec { // 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 + // a scid or a scid alias, and the `outpoint_to_peer` shouldn't be used outside // of the ChannelMonitor handling. Therefore reallocations may still occur, but is // unlikely as the `short_to_chan_info` map often contains 2 entries for // the same channel. let mut res = Vec::with_capacity(self.short_to_chan_info.read().unwrap().len()); { - let best_block_height = self.best_block.read().unwrap().height(); + let best_block_height = self.best_block.read().unwrap().height; 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(); @@ -2856,7 +2902,7 @@ where /// Gets the list of channels we have with a given counterparty, in random order. pub fn list_channels_with_counterparty(&self, counterparty_node_id: &PublicKey) -> Vec { - let best_block_height = self.best_block.read().unwrap().height(); + let best_block_height = self.best_block.read().unwrap().height; let per_peer_state = self.per_peer_state.read().unwrap(); if let Some(peer_state_mutex) = per_peer_state.get(counterparty_node_id) { @@ -2911,26 +2957,6 @@ where .collect() } - /// Helper function that issues the channel close events - fn issue_channel_close_events(&self, context: &ChannelContext, 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::DiscardFunding { - channel_id: context.channel_id(), transaction - }, None)); - }, - None => {}, - } - pending_events_lock.push_back((events::Event::ChannelClosed { - channel_id: context.channel_id(), - user_channel_id: context.get_user_id(), - 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: &ChannelId, counterparty_node_id: &PublicKey, target_feerate_sats_per_1000_weight: Option, override_shutdown_script: Option) -> Result<(), APIError> { let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); @@ -2968,13 +2994,12 @@ where // 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(), chan.context.channel_id(), monitor_update, + handle_new_monitor_update!(self, funding_txo_opt.unwrap(), monitor_update, peer_state_lock, peer_state, per_peer_state, chan); } } else { - self.issue_channel_close_events(chan_phase_entry.get().context(), ClosureReason::HolderForceClosed); let mut chan_phase = remove_channel_phase!(self, chan_phase_entry); - shutdown_result = Some(chan_phase.context_mut().force_shutdown(false)); + shutdown_result = Some(chan_phase.context_mut().force_shutdown(false, ClosureReason::HolderForceClosed)); } }, hash_map::Entry::Vacant(_) => { @@ -3071,14 +3096,16 @@ where let logger = WithContext::from( &self.logger, Some(shutdown_res.counterparty_node_id), Some(shutdown_res.channel_id), ); - log_debug!(logger, "Finishing closure of channel with {} HTLCs to fail", shutdown_res.dropped_outbound_htlcs.len()); + + log_debug!(logger, "Finishing closure of channel due to {} with {} HTLCs to fail", + shutdown_res.closure_reason, shutdown_res.dropped_outbound_htlcs.len()); for htlc_source in shutdown_res.dropped_outbound_htlcs.drain(..) { let (source, payment_hash, counterparty_node_id, channel_id) = htlc_source; let reason = HTLCFailReason::from_failure_code(0x4000 | 8); let receiver = HTLCDestination::NextHopChannel { node_id: Some(counterparty_node_id), channel_id }; self.fail_htlc_backwards_internal(&source, &payment_hash, &reason, receiver); } - if let Some((_, funding_txo, monitor_update)) = shutdown_res.monitor_update { + if let Some((_, funding_txo, _channel_id, monitor_update)) = shutdown_res.monitor_update { // There isn't anything we can do if we get an update failure - we're already // force-closing. The monitor update on the required in-memory copy should broadcast // the latest local state, which is the best we can do anyway. Thus, it is safe to @@ -3096,8 +3123,7 @@ where 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)); + shutdown_results.push(chan.context_mut().force_shutdown(false, ClosureReason::FundingBatchClosure)); } } has_uncompleted_channel = Some(has_uncompleted_channel.map_or(!state, |v| v || !state)); @@ -3107,6 +3133,24 @@ where "Closing a batch where all channels have completed initial monitor update", ); } + + { + let mut pending_events = self.pending_events.lock().unwrap(); + pending_events.push_back((events::Event::ChannelClosed { + channel_id: shutdown_res.channel_id, + user_channel_id: shutdown_res.user_channel_id, + reason: shutdown_res.closure_reason, + counterparty_node_id: Some(shutdown_res.counterparty_node_id), + channel_capacity_sats: Some(shutdown_res.channel_capacity_satoshis), + channel_funding_txo: shutdown_res.channel_funding_txo, + }, None)); + + if let Some(transaction) = shutdown_res.unbroadcasted_funding_tx { + pending_events.push_back((events::Event::DiscardFunding { + channel_id: shutdown_res.channel_id, transaction + }, None)); + } + } for shutdown_result in shutdown_results.drain(..) { self.finish_close_channel(shutdown_result); } @@ -3129,24 +3173,23 @@ where let logger = WithContext::from(&self.logger, Some(*peer_node_id), Some(*channel_id)); if let hash_map::Entry::Occupied(chan_phase_entry) = peer_state.channel_by_id.entry(channel_id.clone()) { log_error!(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.finish_close_channel(chan.context.force_shutdown(broadcast, closure_reason)); (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)); + self.finish_close_channel(chan_phase.context_mut().force_shutdown(false, closure_reason)); // Unfunded channel has no update (None, chan_phase.context().get_counterparty_node_id()) }, - // TODO(dual_funding): Combine this match arm with above. + // TODO(dual_funding): Combine this match arm with above once #[cfg(dual_funding)] is removed. #[cfg(dual_funding)] ChannelPhase::UnfundedOutboundV2(_) | ChannelPhase::UnfundedInboundV2(_) => { - self.finish_close_channel(chan_phase.context_mut().force_shutdown(false)); + self.finish_close_channel(chan_phase.context_mut().force_shutdown(false, closure_reason)); // Unfunded channel has no update (None, chan_phase.context().get_counterparty_node_id()) }, @@ -3213,8 +3256,8 @@ where /// the latest local transaction(s). Fails if `channel_id` is unknown to the manager, or if the /// `counterparty_node_id` isn't the counterparty of the corresponding channel. /// - /// You can always get the latest local transaction(s) to broadcast from - /// [`ChannelMonitor::get_latest_holder_commitment_txn`]. + /// You can always broadcast the latest local transaction(s) via + /// [`ChannelMonitor::broadcast_latest_holder_commitment_txn`]. 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) @@ -3245,11 +3288,13 @@ where msg, &self.node_signer, &self.logger, &self.secp_ctx )?; - let is_blinded = match next_hop { + let is_intro_node_forward = match next_hop { onion_utils::Hop::Forward { - next_hop_data: msgs::InboundOnionPayload::BlindedForward { .. }, .. + next_hop_data: msgs::InboundOnionPayload::BlindedForward { + intro_node_blinding_point: Some(_), .. + }, .. } => true, - _ => false, // TODO: update this when we support receiving to multi-hop blinded paths + _ => false, }; macro_rules! return_err { @@ -3259,7 +3304,17 @@ where WithContext::from(&self.logger, Some(*counterparty_node_id), Some(msg.channel_id)), "Failed to accept/forward incoming HTLC: {}", $msg ); - let (err_code, err_data) = if is_blinded { + // If `msg.blinding_point` is set, we must always fail with malformed. + if msg.blinding_point.is_some() { + return Err(HTLCFailureMsg::Malformed(msgs::UpdateFailMalformedHTLC { + channel_id: msg.channel_id, + htlc_id: msg.htlc_id, + sha256_of_onion: [0; 32], + failure_code: INVALID_ONION_BLINDING, + })); + } + + let (err_code, err_data) = if is_intro_node_forward { (INVALID_ONION_BLINDING, &[0; 32][..]) } else { ($err_code, $data) }; return Err(HTLCFailureMsg::Relay(msgs::UpdateFailHTLC { @@ -3357,7 +3412,7 @@ where None }; - let cur_height = self.best_block.read().unwrap().height() + 1; + let cur_height = self.best_block.read().unwrap().height + 1; if let Err((err_msg, code)) = check_incoming_htlc_cltv( cur_height, outgoing_cltv_value, msg.cltv_expiry @@ -3412,6 +3467,16 @@ where { let logger = WithContext::from(&self.logger, Some(*counterparty_node_id), Some(msg.channel_id)); log_info!(logger, "Failed to accept/forward incoming HTLC: {}", $msg); + if msg.blinding_point.is_some() { + return PendingHTLCStatus::Fail(HTLCFailureMsg::Malformed( + msgs::UpdateFailMalformedHTLC { + channel_id: msg.channel_id, + htlc_id: msg.htlc_id, + sha256_of_onion: [0; 32], + failure_code: INVALID_ONION_BLINDING, + } + )) + } return PendingHTLCStatus::Fail(HTLCFailureMsg::Relay(msgs::UpdateFailHTLC { channel_id: msg.channel_id, htlc_id: msg.htlc_id, @@ -3424,7 +3489,7 @@ where match decoded_hop { onion_utils::Hop::Receive(next_hop_data) => { // OUR PAYMENT! - let current_height: u32 = self.best_block.read().unwrap().height(); + let current_height: u32 = self.best_block.read().unwrap().height; match create_recv_pending_htlc_info(next_hop_data, shared_secret, msg.payment_hash, msg.amount_msat, msg.cltv_expiry, None, allow_underpay, msg.skimmed_fee_msat, current_height, self.default_configuration.accept_mpp_keysend) @@ -3436,14 +3501,14 @@ where // delay) once they've send us a commitment_signed! PendingHTLCStatus::Forward(info) }, - Err(InboundOnionErr { err_code, err_data, msg }) => return_err!(msg, err_code, &err_data) + Err(InboundHTLCErr { 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 } => { match create_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) + Err(InboundHTLCErr { err_code, err_data, msg }) => return_err!(msg, err_code, &err_data) } } } @@ -3597,7 +3662,7 @@ where }, onion_packet, None, &self.fee_estimator, &&logger); match break_chan_phase_entry!(self, send_res, chan_phase_entry) { Some(monitor_update) => { - match handle_new_monitor_update!(self, funding_txo, channel_id, monitor_update, peer_state_lock, peer_state, per_peer_state, chan) { + 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 @@ -3684,7 +3749,7 @@ where /// [`PeerManager::process_events`]: crate::ln::peer_handler::PeerManager::process_events /// [`ChannelMonitorUpdateStatus::InProgress`]: crate::chain::ChannelMonitorUpdateStatus::InProgress pub fn send_payment_with_route(&self, route: &Route, payment_hash: PaymentHash, recipient_onion: RecipientOnionFields, payment_id: PaymentId) -> Result<(), PaymentSendFailure> { - let best_block_height = self.best_block.read().unwrap().height(); + 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, @@ -3695,7 +3760,7 @@ where /// Similar to [`ChannelManager::send_payment_with_route`], but will automatically find a route based on /// `route_params` and retry failed payment paths based on `retry_strategy`. pub fn send_payment(&self, payment_hash: PaymentHash, recipient_onion: RecipientOnionFields, payment_id: PaymentId, route_params: RouteParameters, retry_strategy: Retry) -> Result<(), RetryableSendFailure> { - let best_block_height = self.best_block.read().unwrap().height(); + let best_block_height = self.best_block.read().unwrap().height; let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); self.pending_outbound_payments .send_payment(payment_hash, recipient_onion, payment_id, retry_strategy, route_params, @@ -3706,7 +3771,7 @@ where #[cfg(test)] pub(super) fn test_send_payment_internal(&self, route: &Route, payment_hash: PaymentHash, recipient_onion: RecipientOnionFields, keysend_preimage: Option, payment_id: PaymentId, recv_value_msat: Option, onion_session_privs: Vec<[u8; 32]>) -> Result<(), PaymentSendFailure> { - let best_block_height = self.best_block.read().unwrap().height(); + 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, @@ -3715,7 +3780,7 @@ where #[cfg(test)] pub(crate) fn test_add_new_pending_payment(&self, payment_hash: PaymentHash, recipient_onion: RecipientOnionFields, payment_id: PaymentId, route: &Route) -> Result, PaymentSendFailure> { - let best_block_height = self.best_block.read().unwrap().height(); + let best_block_height = self.best_block.read().unwrap().height; self.pending_outbound_payments.test_add_new_pending_payment(payment_hash, recipient_onion, payment_id, route, None, &self.entropy_source, best_block_height) } @@ -3725,7 +3790,7 @@ where } pub(super) fn send_payment_for_bolt12_invoice(&self, invoice: &Bolt12Invoice, payment_id: PaymentId) -> Result<(), Bolt12PaymentError> { - let best_block_height = self.best_block.read().unwrap().height(); + let best_block_height = self.best_block.read().unwrap().height; let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); self.pending_outbound_payments .send_payment_for_bolt12_invoice( @@ -3782,7 +3847,7 @@ where /// /// [`send_payment`]: Self::send_payment pub fn send_spontaneous_payment(&self, route: &Route, payment_preimage: Option, recipient_onion: RecipientOnionFields, payment_id: PaymentId) -> Result { - let best_block_height = self.best_block.read().unwrap().height(); + let best_block_height = self.best_block.read().unwrap().height; 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, @@ -3797,7 +3862,7 @@ where /// /// [`PaymentParameters::for_keysend`]: crate::routing::router::PaymentParameters::for_keysend pub fn send_spontaneous_payment_with_retry(&self, payment_preimage: Option, recipient_onion: RecipientOnionFields, payment_id: PaymentId, route_params: RouteParameters, retry_strategy: Retry) -> Result { - let best_block_height = self.best_block.read().unwrap().height(); + let best_block_height = self.best_block.read().unwrap().height; let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); self.pending_outbound_payments.send_spontaneous_payment(payment_preimage, recipient_onion, payment_id, retry_strategy, route_params, &self.router, self.list_usable_channels(), @@ -3809,7 +3874,7 @@ where /// [`PaymentHash`] of probes based on a static secret and a random [`PaymentId`], which allows /// us to easily discern them from real payments. pub fn send_probe(&self, path: Path) -> Result<(PaymentHash, PaymentId), PaymentSendFailure> { - let best_block_height = self.best_block.read().unwrap().height(); + 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, @@ -3871,7 +3936,7 @@ where ProbeSendFailure::RouteNotFound })?; - let mut used_liquidity_map = HashMap::with_capacity(first_hops.len()); + let mut used_liquidity_map = hash_map_with_capacity(first_hops.len()); let mut res = Vec::new(); @@ -3945,18 +4010,18 @@ where let mut peer_state_lock = peer_state_mutex.lock().unwrap(); let peer_state = &mut *peer_state_lock; - let (chan, msg_opt) = match peer_state.channel_by_id.remove(temporary_channel_id) { + let funding_txo; + let (mut chan, msg_opt) = match peer_state.channel_by_id.remove(temporary_channel_id) { Some(ChannelPhase::UnfundedOutboundV1(mut chan)) => { - let funding_txo = find_funding_output(&chan, &funding_transaction)?; + funding_txo = find_funding_output(&chan, &funding_transaction)?; let logger = WithChannelContext::from(&self.logger, &chan.context); let funding_res = chan.get_funding_created(funding_transaction, funding_txo, is_batch_funding, &&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); - let channel_capacity = chan.context.get_value_satoshis(); - (chan, MsgHandleErrInternal::from_finish_shutdown(msg, channel_id, user_id, shutdown_res, None, channel_capacity)) + let reason = ClosureReason::ProcessingError { err: msg.clone() }; + let shutdown_res = chan.context.force_shutdown(false, reason); + (chan, MsgHandleErrInternal::from_finish_shutdown(msg, channel_id, shutdown_res, None)) } else { unreachable!(); }); match funding_res { Ok(funding_msg) => (chan, funding_msg), @@ -3995,9 +4060,21 @@ where panic!("Generated duplicate funding txid?"); }, hash_map::Entry::Vacant(e) => { - let mut id_to_peer = self.id_to_peer.lock().unwrap(); - 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"); + let mut outpoint_to_peer = self.outpoint_to_peer.lock().unwrap(); + match outpoint_to_peer.entry(funding_txo) { + hash_map::Entry::Vacant(e) => { e.insert(chan.context.get_counterparty_node_id()); }, + hash_map::Entry::Occupied(o) => { + let err = format!( + "An existing channel using outpoint {} is open with peer {}", + funding_txo, o.get() + ); + mem::drop(outpoint_to_peer); + mem::drop(peer_state_lock); + mem::drop(per_peer_state); + let reason = ClosureReason::ProcessingError { err: err.clone() }; + self.finish_close_channel(chan.context.force_shutdown(true, reason)); + return Err(APIError::ChannelUnavailable { err }); + } } e.insert(ChannelPhase::UnfundedOutboundV1(chan)); } @@ -4075,7 +4152,7 @@ where })); } { - let height = self.best_block.read().unwrap().height(); + let height = self.best_block.read().unwrap().height; // Transactions are evaluated as final by network mempools if their locktime is strictly // lower than the next block height. However, the modules constituting our Lightning // node might not have perfect sync about their blockchain views. Thus, if the wallet @@ -4134,7 +4211,10 @@ where } 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)); + // TODO(dual_funding): We only do batch funding for V1 channels at the moment, but we'll probably + // need to fix this somehow to not rely on using the outpoint for the channel ID if we + // want to support V2 batching here as well. + funding_batch_state.push((ChannelId::v1_from_funding_outpoint(outpoint), *counterparty_node_id, false)); } Ok(outpoint) }) @@ -4161,11 +4241,12 @@ where .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)); + let closure_reason = ClosureReason::ProcessingError { err: e.clone() }; + shutdown_results.push(chan.context_mut().force_shutdown(false, closure_reason)); }); } } + mem::drop(funding_batch_states); for shutdown_result in shutdown_results.drain(..) { self.finish_close_channel(shutdown_result); } @@ -4188,6 +4269,14 @@ where #[cfg(dual_funding)] pub fn contribute_funding_inputs(&self, channel_id: &ChannelId, counterparty_node_id: &PublicKey, funding_inputs: Vec<(TxIn, Transaction)>) -> Result<(), APIError> { + let funding_inputs = funding_inputs.into_iter().map(|(txin, tx)| { + match TransactionU16LenLimited::new(tx) { + Ok(tx) => Ok((txin, tx)), + Err(err) => Err(err) + } + }).collect::, ()>>() + .map_err(|_| APIError::APIMisuseError { err: "One or more transactions had a serialized length exceeding 65535 bytes".into() })?; + let per_peer_state = self.per_peer_state.read().unwrap(); let peer_state_mutex = per_peer_state.get(counterparty_node_id) .ok_or_else(|| APIError::ChannelUnavailable { err: format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id) })?; @@ -4238,7 +4327,7 @@ where #[cfg(dual_funding)] pub fn funding_transaction_signed(&self, channel_id: &ChannelId, counterparty_node_id: &PublicKey, transaction: Transaction) -> Result<(), APIError> { - let witnesses: Vec<_> = transaction.input.clone().into_iter().enumerate().filter_map(|(_idx, input)| { + let witnesses: Vec<_> = transaction.input.into_iter().enumerate().filter_map(|(_idx, input)| { if input.witness.is_empty() { None } else { Some(input.witness) } }).collect(); @@ -4469,8 +4558,8 @@ where let mut per_source_pending_forward = [( payment.prev_short_channel_id, - payment.prev_channel_id, payment.prev_funding_outpoint, + payment.prev_channel_id, payment.prev_user_channel_id, vec![(pending_htlc_info, payment.prev_htlc_id)] )]; @@ -4497,8 +4586,8 @@ where let htlc_source = HTLCSource::PreviousHopData(HTLCPreviousHopData { short_channel_id: payment.prev_short_channel_id, user_channel_id: Some(payment.prev_user_channel_id), - channel_id: payment.prev_channel_id, outpoint: payment.prev_funding_outpoint, + channel_id: payment.prev_channel_id, htlc_id: payment.prev_htlc_id, incoming_packet_shared_secret: payment.forward_info.incoming_shared_secret, phantom_shared_secret: None, @@ -4522,9 +4611,9 @@ where let mut new_events = VecDeque::new(); let mut failed_forwards = Vec::new(); - let mut phantom_receives: Vec<(u64, ChannelId, OutPoint, u128, Vec<(PendingHTLCInfo, u64)>)> = Vec::new(); + let mut phantom_receives: Vec<(u64, OutPoint, ChannelId, u128, Vec<(PendingHTLCInfo, u64)>)> = Vec::new(); { - let mut forward_htlcs = HashMap::new(); + let mut forward_htlcs = new_hash_map(); mem::swap(&mut forward_htlcs, &mut self.forward_htlcs.lock().unwrap()); for (short_chan_id, mut pending_forwards) in forward_htlcs { @@ -4535,15 +4624,15 @@ where for forward_info in pending_forwards.drain(..) { match forward_info { HTLCForwardInfo::AddHTLC(PendingAddHTLCInfo { - prev_short_channel_id, prev_htlc_id, prev_channel_id, prev_funding_outpoint, prev_user_channel_id, - forward_info: PendingHTLCInfo { + prev_short_channel_id, prev_htlc_id, prev_channel_id, prev_funding_outpoint, + prev_user_channel_id, forward_info: PendingHTLCInfo { routing, incoming_shared_secret, payment_hash, outgoing_amt_msat, outgoing_cltv_value, .. } }) => { macro_rules! failure_handler { ($msg: expr, $err_code: expr, $err_data: expr, $phantom_ss: expr, $next_hop_unknown: expr) => { - let logger = WithContext::from(&self.logger, forwarding_counterparty, Some(prev_funding_outpoint.to_channel_id())); + let logger = WithContext::from(&self.logger, forwarding_counterparty, Some(prev_channel_id)); log_info!(logger, "Failed to accept/forward incoming HTLC: {}", $msg); let htlc_source = HTLCSource::PreviousHopData(HTLCPreviousHopData { @@ -4590,7 +4679,7 @@ where 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, &self.node_signer + payment_hash, None, &self.node_signer ) { Ok(res) => res, Err(onion_utils::OnionDecodeErr::Malformed { err_msg, err_code }) => { @@ -4607,14 +4696,14 @@ where }; match next_hop { onion_utils::Hop::Receive(hop_data) => { - let current_height: u32 = self.best_block.read().unwrap().height(); + let current_height: u32 = self.best_block.read().unwrap().height; match create_recv_pending_htlc_info(hop_data, incoming_shared_secret, payment_hash, outgoing_amt_msat, outgoing_cltv_value, Some(phantom_shared_secret), false, None, current_height, self.default_configuration.accept_mpp_keysend) { - Ok(info) => phantom_receives.push((prev_short_channel_id, prev_channel_id, prev_funding_outpoint, prev_user_channel_id, vec![(info, prev_htlc_id)])), - Err(InboundOnionErr { err_code, err_data, msg }) => failed_payment!(msg, err_code, err_data, Some(phantom_shared_secret)) + Ok(info) => phantom_receives.push((prev_short_channel_id, prev_funding_outpoint, prev_channel_id, prev_user_channel_id, vec![(info, prev_htlc_id)])), + Err(InboundHTLCErr { err_code, err_data, msg }) => failed_payment!(msg, err_code, err_data, Some(phantom_shared_secret)) } }, _ => panic!(), @@ -4626,7 +4715,7 @@ where fail_forward!(format!("Unknown short channel id {} for forward HTLC", short_chan_id), 0x4000 | 10, Vec::new(), None); } }, - HTLCForwardInfo::FailHTLC { .. } => { + HTLCForwardInfo::FailHTLC { .. } | HTLCForwardInfo::FailMalformedHTLC { .. } => { // Channel went away before we could fail it. This implies // the channel is now on chain and our counterparty is // trying to broadcast the HTLC-Timeout, but that's their @@ -4656,10 +4745,10 @@ where if let Some(ChannelPhase::Funded(ref mut chan)) = peer_state.channel_by_id.get_mut(&forward_chan_id) { let logger = WithChannelContext::from(&self.logger, &chan.context); for forward_info in pending_forwards.drain(..) { - match forward_info { + let queue_fail_htlc_res = match forward_info { HTLCForwardInfo::AddHTLC(PendingAddHTLCInfo { - prev_short_channel_id, prev_htlc_id, prev_channel_id, prev_funding_outpoint, prev_user_channel_id, - forward_info: PendingHTLCInfo { + prev_short_channel_id, prev_htlc_id, prev_channel_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, blinded, .. @@ -4676,7 +4765,7 @@ where incoming_packet_shared_secret: incoming_shared_secret, // Phantom payments are only PendingHTLCRouting::Receive. phantom_shared_secret: None, - blinded_failure: blinded.map(|_| BlindedFailure::FromIntroductionNode), + blinded_failure: blinded.map(|b| b.failure), }); let next_blinding_point = blinded.and_then(|b| { let encrypted_tlvs_ss = self.node_signer.ecdh( @@ -4703,26 +4792,35 @@ where )); continue; } + None }, 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!(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, &&logger - ) { - if let ChannelError::Ignore(msg) = e { - log_trace!(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; - } + Some((chan.queue_fail_htlc(htlc_id, err_packet, &&logger), htlc_id)) + }, + HTLCForwardInfo::FailMalformedHTLC { htlc_id, failure_code, sha256_of_onion } => { + log_trace!(logger, "Failing malformed HTLC back to channel with short id {} (backward HTLC ID {}) after delay", short_chan_id, htlc_id); + let res = chan.queue_fail_malformed_htlc( + htlc_id, failure_code, sha256_of_onion, &&logger + ); + Some((res, htlc_id)) }, + }; + if let Some((queue_fail_htlc_res, htlc_id)) = queue_fail_htlc_res { + if let Err(e) = queue_fail_htlc_res { + if let ChannelError::Ignore(msg) = e { + log_trace!(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_{{malformed_}}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 { @@ -4733,15 +4831,18 @@ where 'next_forwardable_htlc: for forward_info in pending_forwards.drain(..) { match forward_info { HTLCForwardInfo::AddHTLC(PendingAddHTLCInfo { - prev_short_channel_id, prev_htlc_id, prev_channel_id, prev_funding_outpoint, prev_user_channel_id, - forward_info: PendingHTLCInfo { + prev_short_channel_id, prev_htlc_id, prev_channel_id, prev_funding_outpoint, + prev_user_channel_id, forward_info: PendingHTLCInfo { routing, incoming_shared_secret, payment_hash, incoming_amt_msat, outgoing_amt_msat, skimmed_fee_msat, .. } }) => { let blinded_failure = routing.blinded_failure(); 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, custom_tlvs } => { + PendingHTLCRouting::Receive { + payment_data, payment_metadata, incoming_cltv_expiry, phantom_shared_secret, + custom_tlvs, requires_blinded_error: _ + } => { let _legacy_hop_data = Some(payment_data.clone()); let onion_fields = RecipientOnionFields { payment_secret: Some(payment_data.payment_secret), payment_metadata, custom_tlvs }; @@ -4792,7 +4893,7 @@ where debug_assert!(!committed_to_claimable); 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(), + &self.best_block.read().unwrap().height.to_be_bytes(), ); failed_forwards.push((HTLCSource::PreviousHopData(HTLCPreviousHopData { short_channel_id: $htlc.prev_hop.short_channel_id, @@ -4802,7 +4903,7 @@ where htlc_id: $htlc.prev_hop.htlc_id, incoming_packet_shared_secret: $htlc.prev_hop.incoming_packet_shared_secret, phantom_shared_secret, - blinded_failure: None, + blinded_failure, }), payment_hash, HTLCFailReason::reason(0x4000 | 15, htlc_msat_height_data), HTLCDestination::FailedPayment { payment_hash: $payment_hash }, @@ -4878,7 +4979,6 @@ where #[allow(unused_assignments)] { committed_to_claimable = true; } - let prev_channel_id = prev_funding_outpoint.to_channel_id(); htlcs.push(claimable_htlc); let amount_msat = htlcs.iter().map(|htlc| htlc.value).sum(); htlcs.iter_mut().for_each(|htlc| htlc.total_value_received = Some(amount_msat)); @@ -4931,7 +5031,7 @@ where } }; if let Some(min_final_cltv_expiry_delta) = min_final_cltv_expiry_delta { - let expected_min_expiry_height = (self.current_best_block().height() + min_final_cltv_expiry_delta as u32) as u64; + 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 {})", &payment_hash, cltv_expiry, expected_min_expiry_height); @@ -4976,7 +5076,7 @@ where }, }; }, - HTLCForwardInfo::FailHTLC { .. } => { + HTLCForwardInfo::FailHTLC { .. } | HTLCForwardInfo::FailMalformedHTLC { .. } => { panic!("Got pending fail of our own HTLC"); } } @@ -4985,7 +5085,7 @@ where } } - let best_block_height = self.best_block.read().unwrap().height(); + 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, |args| self.send_payment_along_path(args)); @@ -5022,23 +5122,23 @@ where for event in background_events.drain(..) { match event { - BackgroundEvent::ClosedMonitorUpdateRegeneratedOnStartup((funding_txo, update)) => { + BackgroundEvent::ClosedMonitorUpdateRegeneratedOnStartup((funding_txo, _channel_id, 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 } => { + BackgroundEvent::MonitorUpdateRegeneratedOnStartup { counterparty_node_id, funding_txo, channel_id, update } => { let mut updated_chan = false; { 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()) { + match peer_state.channel_by_id.entry(channel_id) { 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, chan.context.channel_id, update.clone(), + 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"); @@ -5088,10 +5188,6 @@ where // 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() { - if new_feerate != chan.context.get_feerate_sat_per_1000_weight() { - log_trace!(logger, "Channel {} does not qualify for a feerate change from {} to {}.", - chan_id, chan.context.get_feerate_sat_per_1000_weight(), new_feerate); - } return NotifyOption::SkipPersistNoEvents; } if !chan.context.is_live() { @@ -5185,8 +5281,7 @@ where log_error!(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)); + shutdown_channels.push(context.force_shutdown(false, ClosureReason::HolderForceClosed)); pending_msg_events.push(MessageSendEvent::HandleError { node_id: counterparty_node_id, action: msgs::ErrorAction::SendErrorMessage { @@ -5461,7 +5556,7 @@ where 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()); + htlc_msat_height_data.extend_from_slice(&self.best_block.read().unwrap().height.to_be_bytes()); HTLCFailReason::reason(failure_code.into(), htlc_msat_height_data) }, FailureCode::InvalidOnionPayload(data) => { @@ -5584,22 +5679,33 @@ where }, HTLCSource::PreviousHopData(HTLCPreviousHopData { ref short_channel_id, ref htlc_id, ref incoming_packet_shared_secret, - ref phantom_shared_secret, ref outpoint, ref blinded_failure, .. + ref phantom_shared_secret, outpoint: _, ref blinded_failure, ref channel_id, .. }) => { log_trace!( - WithContext::from(&self.logger, None, Some(outpoint.to_channel_id())), + WithContext::from(&self.logger, None, Some(*channel_id)), "Failing {}HTLC with payment_hash {} backwards from us: {:?}", if blinded_failure.is_some() { "blinded " } else { "" }, &payment_hash, onion_error ); - let err_packet = match blinded_failure { + let failure = match blinded_failure { Some(BlindedFailure::FromIntroductionNode) => { let blinded_onion_error = HTLCFailReason::reason(INVALID_ONION_BLINDING, vec![0; 32]); - blinded_onion_error.get_encrypted_failure_packet( + let err_packet = blinded_onion_error.get_encrypted_failure_packet( incoming_packet_shared_secret, phantom_shared_secret - ) + ); + HTLCForwardInfo::FailHTLC { htlc_id: *htlc_id, err_packet } + }, + Some(BlindedFailure::FromBlindedNode) => { + HTLCForwardInfo::FailMalformedHTLC { + htlc_id: *htlc_id, + failure_code: INVALID_ONION_BLINDING, + sha256_of_onion: [0; 32] + } }, None => { - onion_error.get_encrypted_failure_packet(incoming_packet_shared_secret, phantom_shared_secret) + let err_packet = onion_error.get_encrypted_failure_packet( + incoming_packet_shared_secret, phantom_shared_secret + ); + HTLCForwardInfo::FailHTLC { htlc_id: *htlc_id, err_packet } } }; @@ -5610,17 +5716,17 @@ where } match forward_htlcs.entry(*short_channel_id) { hash_map::Entry::Occupied(mut entry) => { - entry.get_mut().push(HTLCForwardInfo::FailHTLC { htlc_id: *htlc_id, err_packet }); + entry.get_mut().push(failure); }, hash_map::Entry::Vacant(entry) => { - entry.insert(vec!(HTLCForwardInfo::FailHTLC { htlc_id: *htlc_id, err_packet })); + entry.insert(vec!(failure)); } } mem::drop(forward_htlcs); if push_forward_ev { self.push_pending_forwards_ev(); } let mut pending_events = self.pending_events.lock().unwrap(); pending_events.push_back((events::Event::HTLCHandlingFailed { - prev_channel_id: outpoint.to_channel_id(), + prev_channel_id: *channel_id, failed_next_destination: destination, }, None)); }, @@ -5781,7 +5887,7 @@ where if !valid_mpp { for htlc in sources.drain(..) { 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()); + htlc_msat_height_data.extend_from_slice(&self.best_block.read().unwrap().height.to_be_bytes()); let source = HTLCSource::PreviousHopData(htlc.prev_hop); let reason = HTLCFailReason::reason(0x4000 | 15, htlc_msat_height_data); let receiver = HTLCDestination::FailedPayment { payment_hash }; @@ -5842,7 +5948,7 @@ where 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, prev_hop.channel_id, monitor_update, peer_state_lock, + 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 - @@ -5852,6 +5958,7 @@ where BackgroundEvent::MonitorUpdateRegeneratedOnStartup { counterparty_node_id, funding_txo: prev_hop.outpoint, + channel_id: prev_hop.channel_id, update: monitor_update.clone(), }); } @@ -5866,13 +5973,13 @@ where log_trace!(logger, "Completing monitor update completion action for channel {} as claim was redundant: {:?}", chan_id, action); - let (node_id, funding_outpoint, blocker) = + let (node_id, _funding_outpoint, channel_id, blocker) = if let MonitorUpdateCompletionAction::FreeOtherChannelImmediately { downstream_counterparty_node_id: node_id, downstream_funding_outpoint: funding_outpoint, - blocking_action: blocker, + blocking_action: blocker, downstream_channel_id: channel_id, } = action { - (node_id, funding_outpoint, blocker) + (node_id, funding_outpoint, channel_id, blocker) } else { debug_assert!(false, "Duplicate claims should always free another channel immediately"); @@ -5882,7 +5989,7 @@ where let mut peer_state = peer_state_mtx.lock().unwrap(); if let Some(blockers) = peer_state .actions_blocking_raa_monitor_updates - .get_mut(&funding_outpoint.to_channel_id()) + .get_mut(&channel_id) { let mut found_blocker = false; blockers.retain(|iter| { @@ -5907,9 +6014,11 @@ where } let preimage_update = ChannelMonitorUpdate { update_id: CLOSED_CHANNEL_UPDATE_ID, + counterparty_node_id: None, updates: vec![ChannelMonitorUpdateStep::PaymentPreimage { payment_preimage, }], + channel_id: Some(prev_hop.channel_id), }; if !during_init { @@ -5921,7 +6030,8 @@ where // 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!(WithContext::from(&self.logger, None, Some(prev_hop.channel_id)), "Critical error: failed to update channel monitor with preimage {:?}: {:?}", + log_error!(WithContext::from(&self.logger, None, Some(prev_hop.channel_id)), + "Critical error: failed to update channel monitor with preimage {:?}: {:?}", payment_preimage, update_res); } } else { @@ -5937,7 +6047,7 @@ where // complete the monitor update completion action from `completion_action`. self.pending_background_events.lock().unwrap().push( BackgroundEvent::ClosedMonitorUpdateRegeneratedOnStartup(( - prev_hop.outpoint, preimage_update, + prev_hop.outpoint, prev_hop.channel_id, preimage_update, ))); } // Note that we do process the completion action here. This totally could be a @@ -5954,8 +6064,9 @@ where } fn claim_funds_internal(&self, source: HTLCSource, payment_preimage: PaymentPreimage, - forwarded_htlc_value_msat: Option, from_onchain: bool, startup_replay: bool, - next_channel_counterparty_node_id: Option, next_channel_outpoint: OutPoint + forwarded_htlc_value_msat: Option, skimmed_fee_msat: Option, from_onchain: bool, + startup_replay: bool, next_channel_counterparty_node_id: Option, + next_channel_outpoint: OutPoint, next_channel_id: ChannelId, ) { match source { HTLCSource::OutboundRoute { session_priv, payment_id, path, .. } => { @@ -5965,7 +6076,7 @@ where debug_assert_eq!(pubkey, path.hops[0].pubkey); } let ev_completion_action = EventCompletionAction::ReleaseRAAChannelMonitorUpdate { - channel_funding_outpoint: next_channel_outpoint, + channel_funding_outpoint: next_channel_outpoint, channel_id: next_channel_id, counterparty_node_id: path.hops[0].pubkey, }; self.pending_outbound_payments.claim_htlc(payment_id, payment_preimage, @@ -5973,15 +6084,17 @@ where &self.logger); }, HTLCSource::PreviousHopData(hop_data) => { - let prev_outpoint = hop_data.outpoint; + let prev_channel_id = hop_data.channel_id; let completed_blocker = RAAMonitorUpdateBlockingAction::from_prev_hop_data(&hop_data); #[cfg(debug_assertions)] let claiming_chan_funding_outpoint = hop_data.outpoint; + #[cfg(debug_assertions)] + let claiming_channel_id = hop_data.channel_id; let res = self.claim_funds_from_hop(hop_data, payment_preimage, |htlc_claim_value_msat, definitely_duplicate| { let chan_to_release = if let Some(node_id) = next_channel_counterparty_node_id { - Some((node_id, next_channel_outpoint, completed_blocker)) + Some((node_id, next_channel_outpoint, next_channel_id, completed_blocker)) } else { // We can only get `None` here if we are processing a // `ChannelMonitor`-originated event, in which case we @@ -6018,7 +6131,7 @@ where }, // or the channel we'd unblock is already closed, BackgroundEvent::ClosedMonitorUpdateRegeneratedOnStartup( - (funding_txo, monitor_update) + (funding_txo, _channel_id, monitor_update) ) => { if *funding_txo == next_channel_outpoint { assert_eq!(monitor_update.updates.len(), 1); @@ -6034,7 +6147,7 @@ where BackgroundEvent::MonitorUpdatesComplete { channel_id, .. } => - *channel_id == claiming_chan_funding_outpoint.to_channel_id(), + *channel_id == claiming_channel_id, } }), "{:?}", *background_events); } @@ -6044,22 +6157,26 @@ where Some(MonitorUpdateCompletionAction::FreeOtherChannelImmediately { downstream_counterparty_node_id: other_chan.0, downstream_funding_outpoint: other_chan.1, - blocking_action: other_chan.2, + downstream_channel_id: other_chan.2, + blocking_action: other_chan.3, }) } else { None } } else { - let fee_earned_msat = if let Some(forwarded_htlc_value) = forwarded_htlc_value_msat { + let total_fee_earned_msat = if let Some(forwarded_htlc_value) = forwarded_htlc_value_msat { if let Some(claimed_htlc_value) = htlc_claim_value_msat { Some(claimed_htlc_value - forwarded_htlc_value) } else { None } } else { None }; + debug_assert!(skimmed_fee_msat <= total_fee_earned_msat, + "skimmed_fee_msat must always be included in total_fee_earned_msat"); Some(MonitorUpdateCompletionAction::EmitEventAndFreeOtherChannel { event: events::Event::PaymentForwarded { - fee_earned_msat, + total_fee_earned_msat, claim_from_onchain_tx: from_onchain, - prev_channel_id: Some(prev_outpoint.to_channel_id()), - next_channel_id: Some(next_channel_outpoint.to_channel_id()), + prev_channel_id: Some(prev_channel_id), + next_channel_id: Some(next_channel_id), outbound_amount_forwarded_msat: forwarded_htlc_value_msat, + skimmed_fee_msat, }, downstream_counterparty_and_funding_outpoint: chan_to_release, }) @@ -6108,16 +6225,17 @@ where event, downstream_counterparty_and_funding_outpoint } => { self.pending_events.lock().unwrap().push_back((event, None)); - if let Some((node_id, funding_outpoint, blocker)) = downstream_counterparty_and_funding_outpoint { - self.handle_monitor_update_release(node_id, funding_outpoint, Some(blocker)); + if let Some((node_id, funding_outpoint, channel_id, blocker)) = downstream_counterparty_and_funding_outpoint { + self.handle_monitor_update_release(node_id, funding_outpoint, channel_id, Some(blocker)); } }, MonitorUpdateCompletionAction::FreeOtherChannelImmediately { - downstream_counterparty_node_id, downstream_funding_outpoint, blocking_action, + downstream_counterparty_node_id, downstream_funding_outpoint, downstream_channel_id, blocking_action, } => { self.handle_monitor_update_release( downstream_counterparty_node_id, downstream_funding_outpoint, + downstream_channel_id, Some(blocking_action), ); }, @@ -6132,7 +6250,7 @@ where commitment_update: Option, order: RAACommitmentOrder, pending_forwards: Vec<(PendingHTLCInfo, u64)>, funding_broadcastable: Option, channel_ready: Option, announcement_sigs: Option) - -> Option<(u64, ChannelId, OutPoint, u128, Vec<(PendingHTLCInfo, u64)>)> { + -> Option<(u64, OutPoint, ChannelId, u128, Vec<(PendingHTLCInfo, u64)>)> { let logger = WithChannelContext::from(&self.logger, &channel.context); log_trace!(logger, "Handling channel resumption for channel {} with {} RAA, {} commitment update, {} pending forwards, {}broadcasting funding, {} channel ready, {} announcement", &channel.context.channel_id(), @@ -6147,7 +6265,7 @@ where let counterparty_node_id = channel.context.get_counterparty_node_id(); if !pending_forwards.is_empty() { htlc_forwards = Some((channel.context.get_short_channel_id().unwrap_or(channel.context.outbound_scid_alias()), - channel.context.channel_id(), channel.context.get_funding_txo().unwrap(), channel.context.get_user_id(), pending_forwards)); + channel.context.get_funding_txo().unwrap(), channel.context.channel_id(), channel.context.get_user_id(), pending_forwards)); } if let Some(msg) = channel_ready { @@ -6201,16 +6319,16 @@ where htlc_forwards } - fn channel_monitor_updated(&self, funding_txo: &OutPoint, highest_applied_update_id: u64, counterparty_node_id: Option<&PublicKey>) { + fn channel_monitor_updated(&self, funding_txo: &OutPoint, channel_id: &ChannelId, highest_applied_update_id: u64, counterparty_node_id: Option<&PublicKey>) { debug_assert!(self.total_consistency_lock.try_write().is_err()); // Caller holds read lock let counterparty_node_id = match counterparty_node_id { Some(cp_id) => cp_id.clone(), None => { // TODO: Once we can rely on the counterparty_node_id from the - // monitor event, this and the id_to_peer map should be removed. - let id_to_peer = self.id_to_peer.lock().unwrap(); - match id_to_peer.get(&funding_txo.to_channel_id()) { + // monitor event, this and the outpoint_to_peer map should be removed. + let outpoint_to_peer = self.outpoint_to_peer.lock().unwrap(); + match outpoint_to_peer.get(funding_txo) { Some(cp_id) => cp_id.clone(), None => return, } @@ -6223,11 +6341,11 @@ where peer_state_lock = peer_state_mutex_opt.unwrap().lock().unwrap(); let peer_state = &mut *peer_state_lock; let channel = - if let Some(ChannelPhase::Funded(chan)) = peer_state.channel_by_id.get_mut(&funding_txo.to_channel_id()) { + if let Some(ChannelPhase::Funded(chan)) = peer_state.channel_by_id.get_mut(channel_id) { chan } else { let update_actions = peer_state.monitor_update_blocked_actions - .remove(&funding_txo.to_channel_id()).unwrap_or(Vec::new()); + .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); @@ -6248,8 +6366,9 @@ where 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`] or - /// [`Event::OpenChannelV2Request`] where we are not contributing any funds. + /// Accepts a request to open a channel after a [`Event::OpenChannelRequest`]. + // TODO(dual_funding): Make these part of doc comments when #[cfg(dual_funding)] is dropped. + // Can also be called after a [`Event::OpenChannelV2Request`] when we are not contributing any funds. /// /// The `temporary_channel_id` parameter indicates which inbound channel should be accepted, /// and the `counterparty_node_id` parameter is the id of the peer which has requested to open @@ -6264,15 +6383,18 @@ where /// used to accept such channels. /// /// [`Event::OpenChannelRequest`]: events::Event::OpenChannelRequest - /// [`Event::OpenChannelV2Request`]: events::Event::OpenChannelV2Request + // TODO(dual_funding): Make these part of doc comments when #[cfg(dual_funding)] is dropped. + // [`Event::OpenChannelV2Request`]: events::Event::OpenChannelV2Request /// [`Event::ChannelClosed::user_channel_id`]: events::Event::ChannelClosed::user_channel_id 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, 0) } - /// Accepts a request to open a channel after a [`Event::OpenChannelRequest`] or - /// [`Event::OpenChannelV2Request`] where we are not contributing any funds, treating it as + /// Accepts a request to open a channel after a [`Event::OpenChannelRequest`], treating it as /// confirmed immediately. + // TODO(dual_funding): Make these part of doc comments when #[cfg(dual_funding)] is dropped. + // Can also be called after a [`Event::OpenChannelV2Request`] when we are not contributing any, + // funds, treating it as confirmed immediately. /// /// The `user_channel_id` parameter will be provided back in /// [`Event::ChannelClosed::user_channel_id`] to allow tracking of which events correspond @@ -6288,6 +6410,7 @@ where /// does not pay to the correct script the correct amount, *you will lose funds*. /// /// [`Event::OpenChannelRequest`]: events::Event::OpenChannelRequest + // TODO(dual_funding): Make these part of doc comments when #[cfg(dual_funding)] is dropped. /// [`Event::OpenChannelV2Request`]: events::Event::OpenChannelV2Request /// [`Event::ChannelClosed::user_channel_id`]: events::Event::ChannelClosed::user_channel_id pub fn accept_inbound_channel_from_trusted_peer_0conf(&self, temporary_channel_id: &ChannelId, counterparty_node_id: &PublicKey, user_channel_id: u128) -> Result<(), APIError> { @@ -6305,20 +6428,9 @@ where /// [`Event::ChannelClosed::user_channel_id`] to allow tracking of which events correspond /// with which `accept_inbound_channel_*` call. /// - /// `funnding_satoshis` is the amount we are contributing to the channel. + /// `funding_satoshis` is the amount we are contributing to the channel. /// Raises [`APIError::APIMisuseError`] when `funding_satoshis` > 2**24. /// - /// The `funding_inputs` parameter accepts ([`TxIn`], [`Transaction`]) pairs which will - /// be used to contribute `funding_satoshis` towards the channel (minus any mining fees due). - /// Raises [`APIError::APIMisuseError`] if the total value of the provided `funding_inputs` is - /// less than `funding_satoshis`. - // TODO(dual_funding): Describe error relating to inputs not being able to cover fees payable by us. - /// - /// LDK will create a change output for any non-dust amounts that remain after subtracting contribution - /// to channel capacity and fees from the provided input amounts. - // TODO(dual_funding): We could allow a list of such outputs to be provided so that the user may - /// be able to do some more interesting things at the same time as funding. - /// Note that this method will return an error and reject the channel, if it requires support /// for zero confirmations. // TODO(dual_funding): Discussion on complications with 0conf dual-funded channels where "locking" @@ -6328,23 +6440,31 @@ where /// /// [`Event::OpenChannelV2Request`]: events::Event::OpenChannelV2Request /// [`Event::ChannelClosed::user_channel_id`]: events::Event::ChannelClosed::user_channel_id + #[cfg(dual_funding)] pub fn accept_inbound_channel_with_contribution(&self, temporary_channel_id: &ChannelId, counterparty_node_id: &PublicKey, user_channel_id: u128, funding_satoshis: u64) -> Result<(), APIError> { self.do_accept_inbound_channel(temporary_channel_id, counterparty_node_id, false, user_channel_id, funding_satoshis) } + // TODO(dual_funding): Remove param _-prefix once #[cfg(dual_funding)] is dropped. fn do_accept_inbound_channel( &self, temporary_channel_id: &ChannelId, counterparty_node_id: &PublicKey, accept_0conf: bool, - user_channel_id: u128, funding_satoshis: u64, + user_channel_id: u128, _funding_satoshis: u64, ) -> Result<(), APIError> { + let logger = WithContext::from(&self.logger, Some(*counterparty_node_id), Some(*temporary_channel_id)); let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); let peers_without_funded_channels = self.peers_without_funded_channels(|peer| { peer.total_channel_count() > 0 }); let per_peer_state = self.per_peer_state.read().unwrap(); let peer_state_mutex = per_peer_state.get(counterparty_node_id) - .ok_or_else(|| APIError::ChannelUnavailable { err: format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id) })?; + .ok_or_else(|| { + let err_str = format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id); + log_error!(logger, "{}", err_str); + + APIError::ChannelUnavailable { err: err_str } + })?; 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; @@ -6355,25 +6475,48 @@ where // succeed. let mut channel_phase = 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(); + let best_block_height = self.best_block.read().unwrap().height; match unaccepted_channel.open_channel_msg { OpenChannelMessage::V1(open_channel_msg) => { - ChannelPhase::UnfundedInboundV1(InboundV1Channel::new(&self.fee_estimator, &self.entropy_source, &self.signer_provider, + let channel_res = InboundV1Channel::new(&self.fee_estimator, &self.entropy_source, &self.signer_provider, counterparty_node_id.clone(), &self.channel_type_features(), &peer_state.latest_features, &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() })?) + &self.logger, accept_0conf); + match channel_res { + Ok(channel) => Ok(ChannelPhase::UnfundedInboundV1(channel)), + Err(e) => { + let err_str = e.to_string(); + log_error!(logger, "{}", err_str); + + Err(APIError::ChannelUnavailable { err: err_str }) + }, + } }, #[cfg(dual_funding)] OpenChannelMessage::V2(open_channel_msg) => { - ChannelPhase::UnfundedInboundV2(InboundV2Channel::new(&self.fee_estimator, &self.entropy_source, &self.signer_provider, + let channel_res = InboundV2Channel::new(&self.fee_estimator, &self.entropy_source, &self.signer_provider, counterparty_node_id.clone(), &self.channel_type_features(), &peer_state.latest_features, - &open_channel_msg, funding_satoshis, user_channel_id, &self.default_configuration, best_block_height, - &self.logger).map_err(|e| APIError::ChannelUnavailable { err: e.to_string() })?) + &open_channel_msg, _funding_satoshis, user_channel_id, &self.default_configuration, best_block_height, + &self.logger); + match channel_res { + Ok(channel) => Ok(ChannelPhase::UnfundedInboundV2(channel)), + Err(e) => { + let err_str = e.to_string(); + log_error!(logger, "{}", err_str); + + Err(APIError::ChannelUnavailable { err: err_str }) + }, + } }, } + }, + None => { + let err_str = "No such channel awaiting to be accepted.".to_owned(); + log_error!(logger, "{}", err_str); + + Err(APIError::APIMisuseError { err: err_str }) } - _ => return 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 Inbound(V1/V2)Channel::new. @@ -6386,7 +6529,10 @@ where } }; 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() }); + let err_str = "Please use accept_inbound_channel_from_trusted_peer_0conf to accept channels with zero confirmations.".to_owned(); + log_error!(logger, "{}", err_str); + + return Err(APIError::APIMisuseError { err: err_str }); } 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 @@ -6399,7 +6545,10 @@ where } }; 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() }); + let err_str = "Too many peers with unfunded channels, refusing to accept new ones".to_owned(); + log_error!(logger, "{}", err_str); + + return Err(APIError::APIMisuseError { err: err_str }); } } @@ -6436,6 +6585,7 @@ where } /// Checks related to inputs and their amounts related to establishing dual-funded channels. + #[cfg(dual_funding)] fn dual_funding_amount_checks(funding_satoshis: u64, funding_inputs: &Vec<(TxIn, Transaction)>) -> Result<(), APIError> { if funding_satoshis < 1000 { @@ -6472,7 +6622,7 @@ where fn peers_without_funded_channels(&self, maybe_count_peer: Filter) -> usize where Filter: Fn(&PeerState) -> bool { let mut peers_without_funded_channels = 0; - let best_block_height = self.best_block.read().unwrap().height(); + let best_block_height = self.best_block.read().unwrap().height; { let peer_state_lock = self.per_peer_state.read().unwrap(); for (_, peer_mtx) in peer_state_lock.iter() { @@ -6507,10 +6657,13 @@ where num_unfunded_channels += 1; } }, - // TODO(dual_funding): Combine this match arm with above. + // TODO(dual_funding): Combine this match arm with above once #[cfg(dual_funding)] is removed. #[cfg(dual_funding)] - ChannelPhase::UnfundedInboundV2(_) => { - if phase.context().minimum_depth().unwrap_or(1) != 0 { + ChannelPhase::UnfundedInboundV2(chan) => { + // Only inbound V2 channels that are not 0conf and that we do not contribute to will be + // included in the unfunded count. + if chan.context.minimum_depth().unwrap_or(1) != 0 && + chan.dual_funding_context.our_funding_satoshis == 0 { num_unfunded_channels += 1; } }, @@ -6518,7 +6671,7 @@ where // Outbound channels don't contribute to the unfunded count in the DoS context. continue; }, - // TODO(dual_funding): Combine this match arm with above. + // TODO(dual_funding): Combine this match arm with above once #[cfg(dual_funding)] is removed. #[cfg(dual_funding)] ChannelPhase::UnfundedOutboundV2(_) => { // Outbound channels don't contribute to the unfunded count in the DoS context. @@ -6531,9 +6684,9 @@ where fn internal_open_channel(&self, counterparty_node_id: &PublicKey, msg: OpenChannelMessage) -> Result<(), MsgHandleErrInternal> { let (chain_hash, temporary_channel_id) = match msg.clone() { - OpenChannelMessage::V1(msg) => (msg.chain_hash, msg.temporary_channel_id), + OpenChannelMessage::V1(msg) => (msg.common_fields.chain_hash, msg.common_fields.temporary_channel_id), #[cfg(dual_funding)] - OpenChannelMessage::V2(msg) => (msg.chain_hash, msg.temporary_channel_id), + OpenChannelMessage::V2(msg) => (msg.common_fields.chain_hash, msg.common_fields.temporary_channel_id), }; // Do common open_channel(2) checks @@ -6541,11 +6694,13 @@ where // Note that the ChannelManager is NOT re-persisted on disk after this, so any changes are // likely to be lost on restart! if chain_hash != self.chain_hash { - return Err(MsgHandleErrInternal::send_err_msg_no_close("Unknown genesis block hash".to_owned(), temporary_channel_id.clone())); + return Err(MsgHandleErrInternal::send_err_msg_no_close("Unknown genesis block hash".to_owned(), + temporary_channel_id.clone())); } if !self.default_configuration.accept_inbound_channels { - return Err(MsgHandleErrInternal::send_err_msg_no_close("No inbound channels accepted".to_owned(), temporary_channel_id.clone())); + return Err(MsgHandleErrInternal::send_err_msg_no_close("No inbound channels accepted".to_owned(), + temporary_channel_id.clone())); } // Get the number of peers with channels, but without funded ones. We don't care too much @@ -6558,7 +6713,9 @@ where let peer_state_mutex = per_peer_state.get(counterparty_node_id) .ok_or_else(|| { debug_assert!(false); - MsgHandleErrInternal::send_err_msg_no_close(format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id), temporary_channel_id.clone()) + MsgHandleErrInternal::send_err_msg_no_close( + format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id), + temporary_channel_id.clone()) })?; let mut peer_state_lock = peer_state_mutex.lock().unwrap(); let peer_state = &mut *peer_state_lock; @@ -6575,16 +6732,19 @@ where temporary_channel_id.clone())); } - let best_block_height = self.best_block.read().unwrap().height(); + let best_block_height = self.best_block.read().unwrap().height; if Self::unfunded_channel_count(peer_state, best_block_height) >= MAX_UNFUNDED_CHANS_PER_PEER { return Err(MsgHandleErrInternal::send_err_msg_no_close( format!("Refusing more than {} unfunded channels.", MAX_UNFUNDED_CHANS_PER_PEER), temporary_channel_id.clone())); } - let channel_exists = peer_state.has_channel(&temporary_channel_id); + let channel_id = 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(), temporary_channel_id.clone())); + return Err(MsgHandleErrInternal::send_err_msg_no_close( + "temporary_channel_id collision for the same peer!".to_owned(), + temporary_channel_id.clone())); } // Version-specific checks and logic @@ -6594,11 +6754,11 @@ where 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(), + temporary_channel_id: msg.common_fields.temporary_channel_id.clone(), counterparty_node_id: counterparty_node_id.clone(), - funding_satoshis: msg.funding_satoshis, + funding_satoshis: msg.common_fields.funding_satoshis, push_msat: msg.push_msat, - channel_type: msg.channel_type.clone().unwrap(), + channel_type: msg.common_fields.channel_type.clone().unwrap(), }, None)); peer_state.inbound_channel_request_by_id.insert(temporary_channel_id, InboundChannelRequest { open_channel_msg: OpenChannelMessage::V1(msg.clone()), @@ -6616,17 +6776,17 @@ where &self.default_configuration, best_block_height, &self.logger, /*is_0conf=*/false) { Err(e) => { - return Err(MsgHandleErrInternal::from_chan_no_close(e, msg.temporary_channel_id)); + return Err(MsgHandleErrInternal::from_chan_no_close(e, msg.common_fields.temporary_channel_id)); }, Ok(res) => res }; 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())); + return Err(MsgHandleErrInternal::send_err_msg_no_close("No zero confirmation channels accepted".to_owned(), msg.common_fields.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())); + return Err(MsgHandleErrInternal::send_err_msg_no_close("No channels with anchor outputs accepted".to_owned(), msg.common_fields.temporary_channel_id.clone())); } let outbound_scid_alias = self.create_and_insert_outbound_scid_alias(); @@ -6645,10 +6805,10 @@ where if self.default_configuration.manually_accept_inbound_channels { let mut pending_events = self.pending_events.lock().unwrap(); pending_events.push_back((events::Event::OpenChannelV2Request { - temporary_channel_id: msg.temporary_channel_id.clone(), + temporary_channel_id: msg.common_fields.temporary_channel_id.clone(), counterparty_node_id: counterparty_node_id.clone(), - funding_satoshis: msg.funding_satoshis, - channel_type: msg.channel_type.clone().unwrap(), + funding_satoshis: msg.common_fields.funding_satoshis, + channel_type: msg.common_fields.channel_type.clone().unwrap(), }, None)); peer_state.inbound_channel_request_by_id.insert(temporary_channel_id, InboundChannelRequest { open_channel_msg: OpenChannelMessage::V2(msg.clone()), @@ -6667,25 +6827,25 @@ where best_block_height, &self.logger) { Err(e) => { - return Err(MsgHandleErrInternal::from_chan_no_close(e, msg.temporary_channel_id)); + return Err(MsgHandleErrInternal::from_chan_no_close(e, msg.common_fields.temporary_channel_id)); }, Ok(res) => res }; 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())); + return Err(MsgHandleErrInternal::send_err_msg_no_close("No zero confirmation channels accepted".to_owned(), msg.common_fields.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())); + return Err(MsgHandleErrInternal::send_err_msg_no_close("No channels with anchor outputs accepted".to_owned(), msg.common_fields.temporary_channel_id.clone())); } let outbound_scid_alias = self.create_and_insert_outbound_scid_alias(); channel.context.set_outbound_scid_alias(outbound_scid_alias); - channel.begin_interactive_funding_tx_construction(&self.signer_provider, &self.entropy_source, - self.get_our_node_id(), Vec::new()).map_err(|_| MsgHandleErrInternal::send_err_msg_no_close( - "Failed to start interactive transaction construction".to_owned(), msg.temporary_channel_id))?; + channel.begin_interactive_funding_tx_construction(&self.signer_provider, &self.entropy_source, self.get_our_node_id(), Vec::new()) + .map_err(|_| MsgHandleErrInternal::send_err_msg_no_close( + "Failed to start interactive transaction construction".to_owned(), msg.common_fields.temporary_channel_id))?; peer_state.pending_msg_events.push(events::MessageSendEvent::SendAcceptChannelV2 { node_id: counterparty_node_id.clone(), @@ -6705,11 +6865,11 @@ where let peer_state_mutex = per_peer_state.get(counterparty_node_id) .ok_or_else(|| { debug_assert!(false); - MsgHandleErrInternal::send_err_msg_no_close(format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id), msg.temporary_channel_id) + MsgHandleErrInternal::send_err_msg_no_close(format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id), msg.common_fields.temporary_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.temporary_channel_id) { + match peer_state.channel_by_id.entry(msg.common_fields.temporary_channel_id) { hash_map::Entry::Occupied(mut phase) => { match phase.get_mut() { ChannelPhase::UnfundedOutboundV1(chan) => { @@ -6717,16 +6877,16 @@ where (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)); + 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.common_fields.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)) + 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.common_fields.temporary_channel_id)) } }; let mut pending_events = self.pending_events.lock().unwrap(); pending_events.push_back((events::Event::FundingGenerationReady { - temporary_channel_id: msg.temporary_channel_id, + temporary_channel_id: msg.common_fields.temporary_channel_id, counterparty_node_id: *counterparty_node_id, channel_value_satoshis: value, output_script, @@ -6743,39 +6903,39 @@ where let peer_state_mutex = per_peer_state.get(counterparty_node_id) .ok_or_else(|| { debug_assert!(false); - MsgHandleErrInternal::send_err_msg_no_close(format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id), msg.temporary_channel_id) + MsgHandleErrInternal::send_err_msg_no_close(format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id), msg.common_fields.temporary_channel_id) })?; let mut peer_state_lock = peer_state_mutex.lock().unwrap(); let peer_state = &mut *peer_state_lock; - let (chan, new_channel_id, holder_funding_satoshis, counterparty_funding_satoshis, user_channel_id) = { - match peer_state.channel_by_id.remove(&msg.temporary_channel_id) { + let (chan, channel_id, holder_funding_satoshis, counterparty_funding_satoshis, user_channel_id) = { + match peer_state.channel_by_id.remove(&msg.common_fields.temporary_channel_id) { Some(phase) => { match phase { ChannelPhase::UnfundedOutboundV2(mut chan) => { if let Err(err) = chan.accept_channel_v2(&msg, &self.default_configuration.channel_handshake_limits, &peer_state.latest_features) { - let (_, res) = convert_chan_phase_err!(self, err, chan, &msg.temporary_channel_id, UNFUNDED_CHANNEL); + let (_, res) = convert_chan_phase_err!(self, err, chan, &msg.common_fields.temporary_channel_id, UNFUNDED_CHANNEL); let _: Result<(), _> = handle_error!(self, Err(res), *counterparty_node_id); } - let new_channel_id = chan.context.channel_id(); + let channel_id = chan.context.channel_id(); let holder_funding_satoshis = chan.dual_funding_context.our_funding_satoshis; let user_channel_id = chan.context.get_user_id(); - (chan, new_channel_id, holder_funding_satoshis, msg.funding_satoshis, user_channel_id) + (chan, channel_id, holder_funding_satoshis, msg.funding_satoshis, user_channel_id) }, _ => { - peer_state.channel_by_id.insert(msg.temporary_channel_id, phase); - return Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Got an unexpected accept_channel2 message from peer with counterparty_node_id {}", counterparty_node_id), msg.temporary_channel_id)); + peer_state.channel_by_id.insert(msg.common_fields.temporary_channel_id, phase); + return Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Got an unexpected accept_channel2 message from peer with counterparty_node_id {}", counterparty_node_id), msg.common_fields.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)) + 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.common_fields.temporary_channel_id)) } }; - peer_state.channel_by_id.insert(new_channel_id, ChannelPhase::UnfundedOutboundV2(chan)); + peer_state.channel_by_id.insert(chan.context.channel_id(), ChannelPhase::UnfundedOutboundV2(chan)); let mut pending_events = self.pending_events.lock().unwrap(); pending_events.push_back((events::Event::FundingInputsContributionReady { - channel_id: new_channel_id, + channel_id, counterparty_node_id: *counterparty_node_id, holder_funding_satoshis, counterparty_funding_satoshis, @@ -6796,50 +6956,61 @@ where let mut peer_state_lock = peer_state_mutex.lock().unwrap(); let peer_state = &mut *peer_state_lock; - let (chan, funding_msg_opt, monitor) = + let (mut chan, funding_msg_opt, monitor) = match peer_state.channel_by_id.remove(&msg.temporary_channel_id) { Some(ChannelPhase::UnfundedInboundV1(inbound_chan)) => { let logger = WithChannelContext::from(&self.logger, &inbound_chan.context); match inbound_chan.funding_created(msg, best_block, &self.signer_provider, &&logger) { Ok(res) => res, - Err((mut inbound_chan, err)) => { + Err((inbound_chan, err)) => { // We've already removed this inbound channel from the map in `PeerState` // above so at this point we just need to clean up any lingering entries // concerning this channel as it is safe to do so. - update_maps_on_chan_removal!(self, &inbound_chan.context); - 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, inbound_chan.context.get_value_satoshis())); + debug_assert!(matches!(err, ChannelError::Close(_))); + // Really we should be returning the channel_id the peer expects based + // on their funding info here, but they're horribly confused anyway, so + // there's not a lot we can do to save them. + return Err(convert_chan_phase_err!(self, err, &mut ChannelPhase::UnfundedInboundV1(inbound_chan), &msg.temporary_channel_id).1); }, } }, - Some(_) => { - 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)); + Some(mut phase) => { + let err_msg = format!("Got an unexpected funding_created message from peer with counterparty_node_id {}", counterparty_node_id); + let err = ChannelError::Close(err_msg); + return Err(convert_chan_phase_err!(self, err, &mut phase, &msg.temporary_channel_id).1); }, 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)) }; - match peer_state.channel_by_id.entry(chan.context.channel_id()) { + let funded_channel_id = chan.context.channel_id(); + + macro_rules! fail_chan { ($err: expr) => { { + // Note that at this point we've filled in the funding outpoint on our + // channel, but its actually in conflict with another channel. Thus, if + // we call `convert_chan_phase_err` immediately (thus calling + // `update_maps_on_chan_removal`), we'll remove the existing channel + // from `outpoint_to_peer`. Thus, we must first unset the funding outpoint + // on the channel. + let err = ChannelError::Close($err.to_owned()); + chan.unset_funding_info(msg.temporary_channel_id); + return Err(convert_chan_phase_err!(self, err, chan, &funded_channel_id, UNFUNDED_CHANNEL).1); + } } } + + match peer_state.channel_by_id.entry(funded_channel_id) { hash_map::Entry::Occupied(_) => { - Err(MsgHandleErrInternal::send_err_msg_no_close( - "Already had channel with the new channel_id".to_owned(), - chan.context.channel_id() - )) + fail_chan!("Already had channel with the new channel_id"); }, hash_map::Entry::Vacant(e) => { - let mut id_to_peer_lock = self.id_to_peer.lock().unwrap(); - match id_to_peer_lock.entry(chan.context.channel_id()) { + let mut outpoint_to_peer_lock = self.outpoint_to_peer.lock().unwrap(); + match outpoint_to_peer_lock.entry(monitor.get_funding_txo().0) { 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(), - chan.context.channel_id())) + fail_chan!("The funding_created message had the same funding_txid as an existing channel - funding is not possible"); }, hash_map::Entry::Vacant(i_e) => { 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); + mem::drop(outpoint_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 @@ -6862,13 +7033,7 @@ where } else { let logger = WithChannelContext::from(&self.logger, &chan.context); log_error!(logger, "Persisting initial ChannelMonitor failed, implying the funding outpoint was duplicated"); - let channel_id = match funding_msg_opt { - Some(msg) => msg.channel_id, - None => chan.context.channel_id(), - }; - 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(), - channel_id)); + fail_chan!("Duplicate funding outpoint"); } } } @@ -6899,7 +7064,7 @@ where let res = chan.funding_signed(&msg, best_block, &self.signer_provider, &&logger); match res { - Ok((chan, monitor)) => { + Ok((mut chan, monitor)) => { if let Ok(persist_status) = self.chain_monitor.watch_channel(chan.context.get_funding_txo().unwrap(), monitor) { // We really should be able to insert here without doing a second // lookup, but sadly rust stdlib doesn't currently allow keeping @@ -6911,6 +7076,11 @@ where Ok(()) } else { let e = ChannelError::Close("Channel funding outpoint was a duplicate".to_owned()); + // 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. + chan.unset_funding_info(msg.channel_id); return Err(convert_chan_phase_err!(self, e, &mut ChannelPhase::Funded(chan), &msg.channel_id).1); } }, @@ -7238,18 +7408,21 @@ where } } + #[cfg(dual_funding)] fn internal_tx_init_rbf(&self, counterparty_node_id: &PublicKey, msg: &msgs::TxInitRbf) { let _: Result<(), _> = handle_error!(self, Err(MsgHandleErrInternal::send_err_msg_no_close( "Dual-funded channels not supported".to_owned(), msg.channel_id.clone())), *counterparty_node_id); } + #[cfg(dual_funding)] fn internal_tx_ack_rbf(&self, counterparty_node_id: &PublicKey, msg: &msgs::TxAckRbf) { let _: Result<(), _> = handle_error!(self, Err(MsgHandleErrInternal::send_err_msg_no_close( "Dual-funded channels not supported".to_owned(), msg.channel_id.clone())), *counterparty_node_id); } + #[cfg(dual_funding)] fn internal_tx_abort(&self, counterparty_node_id: &PublicKey, msg: &msgs::TxAbort) { let _: Result<(), _> = handle_error!(self, Err(MsgHandleErrInternal::send_err_msg_no_close( "Dual-funded channels not supported".to_owned(), @@ -7350,7 +7523,7 @@ where } // 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(), chan.context.channel_id, monitor_update, + handle_new_monitor_update!(self, funding_txo_opt.unwrap(), monitor_update, peer_state_lock, peer_state, per_peer_state, chan); } }, @@ -7358,18 +7531,16 @@ where let context = phase.context_mut(); let logger = WithChannelContext::from(&self.logger, context); log_error!(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)); + finish_shutdown = Some(chan.context_mut().force_shutdown(false, ClosureReason::CounterpartyCoopClosedUnfundedChannel)); }, // TODO(dual_funding): Combine this match arm with above. #[cfg(dual_funding)] ChannelPhase::UnfundedInboundV2(_) | ChannelPhase::UnfundedOutboundV2(_) => { - let context = phase.context_mut(); + 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)); + finish_shutdown = Some(chan.context_mut().force_shutdown(false, ClosureReason::CounterpartyCoopClosedUnfundedChannel)); }, } } else { @@ -7438,7 +7609,6 @@ where msg: update }); } - self.issue_channel_close_events(&chan.context, ClosureReason::CooperativeClosure); } mem::drop(per_peer_state); if let Some(shutdown_result) = shutdown_result { @@ -7481,6 +7651,16 @@ where Err(e) => PendingHTLCStatus::Fail(e) }; let create_pending_htlc_status = |chan: &Channel, pending_forward_info: PendingHTLCStatus, error_code: u16| { + if msg.blinding_point.is_some() { + return PendingHTLCStatus::Fail(HTLCFailureMsg::Malformed( + msgs::UpdateFailMalformedHTLC { + channel_id: msg.channel_id, + htlc_id: msg.htlc_id, + sha256_of_onion: [0; 32], + failure_code: INVALID_ONION_BLINDING, + } + )) + } // 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. @@ -7520,7 +7700,7 @@ where 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 (htlc_source, forwarded_htlc_value, skimmed_fee_msat) = { let per_peer_state = self.per_peer_state.read().unwrap(); let peer_state_mutex = per_peer_state.get(counterparty_node_id) .ok_or_else(|| { @@ -7558,7 +7738,11 @@ where 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, false, Some(*counterparty_node_id), funding_txo); + self.claim_funds_internal(htlc_source, msg.payment_preimage.clone(), + Some(forwarded_htlc_value), skimmed_fee_msat, false, false, Some(*counterparty_node_id), + funding_txo, msg.channel_id + ); + Ok(()) } @@ -7617,6 +7801,7 @@ where } fn internal_commitment_signed(&self, counterparty_node_id: &PublicKey, msg: &msgs::CommitmentSigned) -> Result<(), MsgHandleErrInternal> { + #[cfg(dual_funding)] let best_block = *self.best_block.read().unwrap(); let per_peer_state = self.per_peer_state.read().unwrap(); let peer_state_mutex = per_peer_state.get(counterparty_node_id) @@ -7633,6 +7818,8 @@ where let logger = WithChannelContext::from(&self.logger, &chan.context); let funding_txo = chan.context.get_funding_txo(); + // TODO(dual_funding): Remove this allow directive after #[cfg(dual_funding)] is dropped. + #[allow(unused_mut)] let mut interactive_tx_signing_in_progress = false; #[cfg(dual_funding)] { @@ -7660,9 +7847,9 @@ where } } } else { - let monitor_update_opt = try_chan_phase_entry!(self, chan.commitment_signed(&msg, &self.logger), chan_phase_entry); + let monitor_update_opt = try_chan_phase_entry!(self, chan.commitment_signed(&msg, &&logger), chan_phase_entry); if let Some(monitor_update) = monitor_update_opt { - handle_new_monitor_update!(self, funding_txo.unwrap(), chan.context.channel_id(), monitor_update, peer_state_lock, + handle_new_monitor_update!(self, funding_txo.unwrap(), monitor_update, peer_state_lock, peer_state, per_peer_state, chan); } } @@ -7677,8 +7864,8 @@ where } #[inline] - fn forward_htlcs(&self, per_source_pending_forwards: &mut [(u64, ChannelId, OutPoint, u128, Vec<(PendingHTLCInfo, u64)>)]) { - for &mut (prev_short_channel_id, prev_channel_id, prev_funding_outpoint, prev_user_channel_id, ref mut pending_forwards) in per_source_pending_forwards { + fn forward_htlcs(&self, per_source_pending_forwards: &mut [(u64, OutPoint, ChannelId, u128, Vec<(PendingHTLCInfo, u64)>)]) { + for &mut (prev_short_channel_id, prev_funding_outpoint, prev_channel_id, prev_user_channel_id, ref mut pending_forwards) in per_source_pending_forwards { let mut push_forward_event = false; let mut new_intercept_events = VecDeque::new(); let mut failed_intercept_forwards = Vec::new(); @@ -7697,7 +7884,7 @@ where match forward_htlcs.entry(scid) { hash_map::Entry::Occupied(mut entry) => { entry.get_mut().push(HTLCForwardInfo::AddHTLC(PendingAddHTLCInfo { - prev_short_channel_id, prev_channel_id, prev_funding_outpoint, prev_htlc_id, prev_user_channel_id, forward_info })); + prev_short_channel_id, prev_funding_outpoint, prev_channel_id, prev_htlc_id, prev_user_channel_id, forward_info })); }, hash_map::Entry::Vacant(entry) => { if !is_our_scid && forward_info.incoming_amt_msat.is_some() && @@ -7715,16 +7902,16 @@ where intercept_id }, None)); entry.insert(PendingAddHTLCInfo { - prev_short_channel_id, prev_channel_id, prev_funding_outpoint, prev_htlc_id, prev_user_channel_id, forward_info }); + prev_short_channel_id, prev_funding_outpoint, prev_channel_id, prev_htlc_id, prev_user_channel_id, forward_info }); }, hash_map::Entry::Occupied(_) => { - let logger = WithContext::from(&self.logger, None, Some(prev_funding_outpoint.to_channel_id())); + let logger = WithContext::from(&self.logger, None, Some(prev_channel_id)); log_info!(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), - channel_id: prev_channel_id, outpoint: prev_funding_outpoint, + channel_id: prev_channel_id, htlc_id: prev_htlc_id, incoming_packet_shared_secret: forward_info.incoming_shared_secret, phantom_shared_secret: None, @@ -7744,7 +7931,7 @@ where push_forward_event = true; } entry.insert(vec!(HTLCForwardInfo::AddHTLC(PendingAddHTLCInfo { - prev_short_channel_id, prev_channel_id, prev_funding_outpoint, prev_htlc_id, prev_user_channel_id, forward_info }))); + prev_short_channel_id, prev_funding_outpoint, prev_channel_id, prev_htlc_id, prev_user_channel_id, forward_info }))); } } } @@ -7788,13 +7975,14 @@ where /// the [`ChannelMonitorUpdate`] in question. fn raa_monitor_updates_held(&self, actions_blocking_raa_monitor_updates: &BTreeMap>, - channel_funding_outpoint: OutPoint, counterparty_node_id: PublicKey + channel_funding_outpoint: OutPoint, channel_id: ChannelId, counterparty_node_id: PublicKey ) -> bool { actions_blocking_raa_monitor_updates - .get(&channel_funding_outpoint.to_channel_id()).map(|v| !v.is_empty()).unwrap_or(false) + .get(&channel_id).map(|v| !v.is_empty()).unwrap_or(false) || self.pending_events.lock().unwrap().iter().any(|(_, action)| { action == &Some(EventCompletionAction::ReleaseRAAChannelMonitorUpdate { channel_funding_outpoint, + channel_id, counterparty_node_id, }) }) @@ -7811,7 +7999,7 @@ where 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); + chan.context().get_funding_txo().unwrap(), channel_id, counterparty_node_id); } } false @@ -7833,7 +8021,7 @@ where 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, + &peer_state.actions_blocking_raa_monitor_updates, funding_txo, msg.channel_id, *counterparty_node_id) } else { false }; let (htlcs_to_fail, monitor_update_opt) = try_chan_phase_entry!(self, @@ -7841,7 +8029,7 @@ where 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, chan.context.channe_id(), monitor_update, + handle_new_monitor_update!(self, funding_txo, monitor_update, peer_state_lock, peer_state, per_peer_state, chan); } htlcs_to_fail @@ -7899,7 +8087,7 @@ where peer_state.pending_msg_events.push(events::MessageSendEvent::BroadcastChannelAnnouncement { msg: try_chan_phase_entry!(self, chan.announcement_signatures( - &self.node_signer, self.chain_hash, self.best_block.read().unwrap().height(), + &self.node_signer, self.chain_hash, self.best_block.read().unwrap().height, msg, &self.default_configuration ), chan_phase_entry), // Note that announcement_signatures fails if the channel cannot be announced, @@ -8310,29 +8498,31 @@ where let mut failed_channels = Vec::new(); let mut pending_monitor_events = self.chain_monitor.release_pending_monitor_events(); let has_pending_monitor_events = !pending_monitor_events.is_empty(); - for (funding_outpoint, mut monitor_events, counterparty_node_id) in pending_monitor_events.drain(..) { + for (funding_outpoint, channel_id, mut monitor_events, counterparty_node_id) in pending_monitor_events.drain(..) { for monitor_event in monitor_events.drain(..) { match monitor_event { MonitorEvent::HTLCEvent(htlc_update) => { - let logger = WithContext::from(&self.logger, counterparty_node_id, Some(funding_outpoint.to_channel_id())); + let logger = WithContext::from(&self.logger, counterparty_node_id, Some(channel_id)); if let Some(preimage) = htlc_update.payment_preimage { log_trace!(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, false, counterparty_node_id, funding_outpoint); + self.claim_funds_internal(htlc_update.source, preimage, + htlc_update.htlc_value_satoshis.map(|v| v * 1000), None, true, + false, counterparty_node_id, funding_outpoint, channel_id); } else { log_trace!(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 receiver = HTLCDestination::NextHopChannel { node_id: counterparty_node_id, 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::HolderForceClosed(funding_outpoint) => { + MonitorEvent::HolderForceClosed(_funding_outpoint) => { let counterparty_node_id_opt = match counterparty_node_id { Some(cp_id) => Some(cp_id), None => { // TODO: Once we can rely on the counterparty_node_id from the - // monitor event, this and the id_to_peer map should be removed. - let id_to_peer = self.id_to_peer.lock().unwrap(); - id_to_peer.get(&funding_outpoint.to_channel_id()).cloned() + // monitor event, this and the outpoint_to_peer map should be removed. + let outpoint_to_peer = self.outpoint_to_peer.lock().unwrap(); + outpoint_to_peer.get(&funding_outpoint).cloned() } }; if let Some(counterparty_node_id) = counterparty_node_id_opt { @@ -8341,15 +8531,14 @@ where 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_phase_entry) = peer_state.channel_by_id.entry(funding_outpoint.to_channel_id()) { + if let hash_map::Entry::Occupied(chan_phase_entry) = peer_state.channel_by_id.entry(channel_id) { if let ChannelPhase::Funded(mut chan) = remove_channel_phase!(self, chan_phase_entry) { - failed_channels.push(chan.context.force_shutdown(false)); + failed_channels.push(chan.context.force_shutdown(false, ClosureReason::HolderForceClosed)); 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::DisconnectPeer { @@ -8361,8 +8550,8 @@ where } } }, - MonitorEvent::Completed { funding_txo, monitor_update_id } => { - self.channel_monitor_updated(&funding_txo, monitor_update_id, counterparty_node_id.as_ref()); + MonitorEvent::Completed { funding_txo, channel_id, monitor_update_id } => { + self.channel_monitor_updated(&funding_txo, &channel_id, monitor_update_id, counterparty_node_id.as_ref()); }, } } @@ -8414,7 +8603,7 @@ where if let Some(monitor_update) = monitor_opt { has_monitor_update = true; - handle_new_monitor_update!(self, funding_txo.unwrap(), chan.context.channel_id(), monitor_update, + handle_new_monitor_update!(self, funding_txo.unwrap(), monitor_update, peer_state_lock, peer_state, per_peer_state, chan); continue 'peer_loop; } @@ -8440,8 +8629,7 @@ where /// attempted in every channel, or in the specifically provided channel. /// /// [`ChannelSigner`]: crate::sign::ChannelSigner - #[cfg(test)] // This is only implemented for one signer method, and should be private until we - // actually finish implementing it fully. + #[cfg(async_signing)] pub fn signer_unblocked(&self, channel_opt: Option<(PublicKey, ChannelId)>) { let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); @@ -8543,8 +8731,6 @@ where }); } - self.issue_channel_close_events(&chan.context, ClosureReason::CooperativeClosure); - log_info!(logger, "Broadcasting {}", log_tx!(tx)); self.tx_broadcaster.broadcast_transactions(&[&tx]); update_maps_on_chan_removal!(self, &chan.context); @@ -8588,54 +8774,69 @@ where // Channel::force_shutdown tries to make us do) as we may still be in initialization, // so we track the update internally and handle it when the user next calls // timer_tick_occurred, guaranteeing we're running normally. - if let Some((counterparty_node_id, funding_txo, update)) = failure.monitor_update.take() { + if let Some((counterparty_node_id, funding_txo, channel_id, update)) = failure.monitor_update.take() { assert_eq!(update.updates.len(), 1); if let ChannelMonitorUpdateStep::ChannelForceClosed { should_broadcast } = update.updates[0] { assert!(should_broadcast); } else { unreachable!(); } self.pending_background_events.lock().unwrap().push( BackgroundEvent::MonitorUpdateRegeneratedOnStartup { - counterparty_node_id, funding_txo, update + counterparty_node_id, funding_txo, update, channel_id, }); } self.finish_close_channel(failure); } } +} +macro_rules! create_offer_builder { ($self: ident, $builder: ty) => { /// Creates an [`OfferBuilder`] such that the [`Offer`] it builds is recognized by the /// [`ChannelManager`] when handling [`InvoiceRequest`] messages for the offer. The offer will /// not have an expiration unless otherwise set on the builder. /// /// # Privacy /// - /// Uses a one-hop [`BlindedPath`] for the offer with [`ChannelManager::get_our_node_id`] as the - /// introduction node and a derived signing pubkey for recipient privacy. As such, currently, - /// the node must be announced. Otherwise, there is no way to find a path to the introduction - /// node in order to send the [`InvoiceRequest`]. + /// Uses [`MessageRouter::create_blinded_paths`] to construct a [`BlindedPath`] for the offer. + /// However, if one is not found, uses a one-hop [`BlindedPath`] with + /// [`ChannelManager::get_our_node_id`] as the introduction node instead. In the latter case, + /// the node must be announced, otherwise, there is no way to find a path to the introduction in + /// order to send the [`InvoiceRequest`]. + /// + /// Also, uses a derived signing pubkey in the offer for recipient privacy. /// /// # Limitations /// /// Requires a direct connection to the introduction node in the responding [`InvoiceRequest`]'s /// reply path. /// + /// # Errors + /// + /// Errors if the parameterized [`Router`] is unable to create a blinded path for the offer. + /// /// This is not exported to bindings users as builder patterns don't map outside of move semantics. /// /// [`Offer`]: crate::offers::offer::Offer /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest pub fn create_offer_builder( - &self, description: String - ) -> OfferBuilder { - let node_id = self.get_our_node_id(); - let expanded_key = &self.inbound_payment_key; - let entropy = &*self.entropy_source; - let secp_ctx = &self.secp_ctx; - let path = self.create_one_hop_blinded_path(); + &$self, description: String + ) -> Result<$builder, Bolt12SemanticError> { + let node_id = $self.get_our_node_id(); + let expanded_key = &$self.inbound_payment_key; + let entropy = &*$self.entropy_source; + let secp_ctx = &$self.secp_ctx; + + let path = $self.create_blinded_path().map_err(|_| Bolt12SemanticError::MissingPaths)?; + let builder = OfferBuilder::deriving_signing_pubkey( + description, node_id, expanded_key, entropy, secp_ctx + ) + .chain_hash($self.chain_hash) + .path(path); - OfferBuilder::deriving_signing_pubkey(description, node_id, expanded_key, entropy, secp_ctx) - .chain_hash(self.chain_hash) - .path(path) + Ok(builder.into()) } +} } +macro_rules! create_refund_builder { ($self: ident, $builder: ty) => { /// Creates a [`RefundBuilder`] such that the [`Refund`] it builds is recognized by the /// [`ChannelManager`] when handling [`Bolt12Invoice`] messages for the refund. /// @@ -8658,10 +8859,13 @@ where /// /// # Privacy /// - /// Uses a one-hop [`BlindedPath`] for the refund with [`ChannelManager::get_our_node_id`] as - /// the introduction node and a derived payer id for payer privacy. As such, currently, the - /// node must be announced. Otherwise, there is no way to find a path to the introduction node - /// in order to send the [`Bolt12Invoice`]. + /// Uses [`MessageRouter::create_blinded_paths`] to construct a [`BlindedPath`] for the refund. + /// However, if one is not found, uses a one-hop [`BlindedPath`] with + /// [`ChannelManager::get_our_node_id`] as the introduction node instead. In the latter case, + /// the node must be announced, otherwise, there is no way to find a path to the introduction in + /// order to send the [`Bolt12Invoice`]. + /// + /// Also, uses a derived payer id in the refund for payer privacy. /// /// # Limitations /// @@ -8670,40 +8874,65 @@ where /// /// # Errors /// - /// Errors if a duplicate `payment_id` is provided given the caveats in the aforementioned link - /// or if `amount_msats` is invalid. + /// Errors if: + /// - a duplicate `payment_id` is provided given the caveats in the aforementioned link, + /// - `amount_msats` is invalid, or + /// - the parameterized [`Router`] is unable to create a blinded path for the refund. /// /// This is not exported to bindings users as builder patterns don't map outside of move semantics. /// /// [`Refund`]: crate::offers::refund::Refund /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice /// [`Bolt12Invoice::payment_paths`]: crate::offers::invoice::Bolt12Invoice::payment_paths + /// [Avoiding Duplicate Payments]: #avoiding-duplicate-payments pub fn create_refund_builder( - &self, description: String, amount_msats: u64, absolute_expiry: Duration, + &$self, description: String, amount_msats: u64, absolute_expiry: Duration, payment_id: PaymentId, retry_strategy: Retry, max_total_routing_fee_msat: Option - ) -> Result, Bolt12SemanticError> { - let node_id = self.get_our_node_id(); - let expanded_key = &self.inbound_payment_key; - let entropy = &*self.entropy_source; - let secp_ctx = &self.secp_ctx; - let path = self.create_one_hop_blinded_path(); + ) -> Result<$builder, Bolt12SemanticError> { + let node_id = $self.get_our_node_id(); + let expanded_key = &$self.inbound_payment_key; + let entropy = &*$self.entropy_source; + let secp_ctx = &$self.secp_ctx; + let path = $self.create_blinded_path().map_err(|_| Bolt12SemanticError::MissingPaths)?; let builder = RefundBuilder::deriving_payer_id( description, node_id, expanded_key, entropy, secp_ctx, amount_msats, payment_id )? - .chain_hash(self.chain_hash) + .chain_hash($self.chain_hash) .absolute_expiry(absolute_expiry) .path(path); let expiration = StaleExpiration::AbsoluteTimeout(absolute_expiry); - self.pending_outbound_payments + $self.pending_outbound_payments .add_new_awaiting_invoice( payment_id, expiration, retry_strategy, max_total_routing_fee_msat, ) .map_err(|_| Bolt12SemanticError::DuplicatePaymentId)?; - Ok(builder) + Ok(builder.into()) } +} } + +impl ChannelManager +where + M::Target: chain::Watch<::EcdsaSigner>, + T::Target: BroadcasterInterface, + ES::Target: EntropySource, + NS::Target: NodeSigner, + SP::Target: SignerProvider, + F::Target: FeeEstimator, + R::Target: Router, + L::Target: Logger, +{ + #[cfg(not(c_bindings))] + create_offer_builder!(self, OfferBuilder); + #[cfg(not(c_bindings))] + create_refund_builder!(self, RefundBuilder); + + #[cfg(c_bindings)] + create_offer_builder!(self, OfferWithDerivedMetadataBuilder); + #[cfg(c_bindings)] + create_refund_builder!(self, RefundMaybeWithDerivedMetadataBuilder); /// Pays for an [`Offer`] using the given parameters by creating an [`InvoiceRequest`] and /// enqueuing it to be sent via an onion message. [`ChannelManager`] will pay the actual @@ -8745,8 +8974,11 @@ where /// /// # Errors /// - /// Errors if a duplicate `payment_id` is provided given the caveats in the aforementioned link - /// or if the provided parameters are invalid for the offer. + /// Errors if: + /// - a duplicate `payment_id` is provided given the caveats in the aforementioned link, + /// - the provided parameters are invalid for the offer, + /// - the parameterized [`Router`] is unable to create a blinded reply path for the invoice + /// request. /// /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest /// [`InvoiceRequest::quantity`]: crate::offers::invoice_request::InvoiceRequest::quantity @@ -8764,9 +8996,11 @@ where let entropy = &*self.entropy_source; let secp_ctx = &self.secp_ctx; - let builder = offer + let builder: InvoiceRequestBuilder = offer .request_invoice_deriving_payer_id(expanded_key, entropy, secp_ctx, payment_id)? - .chain_hash(self.chain_hash)?; + .into(); + let builder = builder.chain_hash(self.chain_hash)?; + let builder = match quantity { None => builder, Some(quantity) => builder.quantity(quantity)?, @@ -8779,9 +9013,8 @@ where None => builder, Some(payer_note) => builder.payer_note(payer_note), }; - let invoice_request = builder.build_and_sign()?; - let reply_path = self.create_one_hop_blinded_path(); + let reply_path = self.create_blinded_path().map_err(|_| Bolt12SemanticError::MissingPaths)?; let expiration = StaleExpiration::TimerTicks(1); self.pending_outbound_payments @@ -8830,6 +9063,11 @@ where /// node meeting the aforementioned criteria, but there's no guarantee that they will be /// received and no retries will be made. /// + /// # Errors + /// + /// Errors if the parameterized [`Router`] is unable to create a blinded payment path or reply + /// path for the invoice. + /// /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice pub fn request_refund_payment(&self, refund: &Refund) -> Result<(), Bolt12SemanticError> { let expanded_key = &self.inbound_payment_key; @@ -8841,23 +9079,25 @@ where match self.create_inbound_payment(Some(amount_msats), relative_expiry, None) { Ok((payment_hash, payment_secret)) => { - let payment_paths = vec![ - self.create_one_hop_blinded_payment_path(payment_secret), - ]; - #[cfg(not(feature = "no-std"))] + let payment_paths = self.create_blinded_payment_paths(amount_msats, payment_secret) + .map_err(|_| Bolt12SemanticError::MissingPaths)?; + + #[cfg(feature = "std")] let builder = refund.respond_using_derived_keys( payment_paths, payment_hash, expanded_key, entropy )?; - #[cfg(feature = "no-std")] + #[cfg(not(feature = "std"))] let created_at = Duration::from_secs( self.highest_seen_timestamp.load(Ordering::Acquire) as u64 ); - #[cfg(feature = "no-std")] + #[cfg(not(feature = "std"))] let builder = refund.respond_using_derived_keys_no_std( payment_paths, payment_hash, created_at, expanded_key, entropy )?; + let builder: InvoiceBuilder = builder.into(); let invoice = builder.allow_mpp().build_and_sign(secp_ctx)?; - let reply_path = self.create_one_hop_blinded_path(); + let reply_path = self.create_blinded_path() + .map_err(|_| Bolt12SemanticError::MissingPaths)?; let mut pending_offers_messages = self.pending_offers_messages.lock().unwrap(); if refund.paths().is_empty() { @@ -8984,24 +9224,35 @@ where inbound_payment::get_payment_preimage(payment_hash, payment_secret, &self.inbound_payment_key) } - /// Creates a one-hop blinded path with [`ChannelManager::get_our_node_id`] as the introduction - /// node. - fn create_one_hop_blinded_path(&self) -> BlindedPath { - let entropy_source = self.entropy_source.deref(); + /// Creates a blinded path by delegating to [`MessageRouter::create_blinded_paths`]. + /// + /// Errors if the `MessageRouter` errors or returns an empty `Vec`. + fn create_blinded_path(&self) -> Result { + let recipient = self.get_our_node_id(); let secp_ctx = &self.secp_ctx; - BlindedPath::one_hop_for_message(self.get_our_node_id(), entropy_source, secp_ctx).unwrap() + + let peers = self.per_peer_state.read().unwrap() + .iter() + .filter(|(_, peer)| peer.lock().unwrap().latest_features.supports_onion_messages()) + .map(|(node_id, _)| *node_id) + .collect::>(); + + self.router + .create_blinded_paths(recipient, peers, secp_ctx) + .and_then(|paths| paths.into_iter().next().ok_or(())) } - /// Creates a one-hop blinded path with [`ChannelManager::get_our_node_id`] as the introduction - /// node. - fn create_one_hop_blinded_payment_path( - &self, payment_secret: PaymentSecret - ) -> (BlindedPayInfo, BlindedPath) { - let entropy_source = self.entropy_source.deref(); + /// Creates multi-hop blinded payment paths for the given `amount_msats` by delegating to + /// [`Router::create_blinded_payment_paths`]. + fn create_blinded_payment_paths( + &self, amount_msats: u64, payment_secret: PaymentSecret + ) -> Result, ()> { let secp_ctx = &self.secp_ctx; + let first_hops = self.list_usable_channels(); let payee_node_id = self.get_our_node_id(); - let max_cltv_expiry = self.best_block.read().unwrap().height() + LATENCY_GRACE_PERIOD_BLOCKS; + let max_cltv_expiry = self.best_block.read().unwrap().height + CLTV_FAR_FAR_AWAY + + LATENCY_GRACE_PERIOD_BLOCKS; let payee_tlvs = ReceiveTlvs { payment_secret, payment_constraints: PaymentConstraints { @@ -9009,10 +9260,9 @@ where htlc_minimum_msat: 1, }, }; - // TODO: Err for overflow? - BlindedPath::one_hop_for_payment( - payee_node_id, payee_tlvs, entropy_source, secp_ctx - ).unwrap() + self.router.create_blinded_payment_paths( + payee_node_id, first_hops, payee_tlvs, amount_msats, secp_ctx + ) } /// Gets a fake short channel id for use in receiving [phantom node payments]. These fake scids @@ -9020,7 +9270,7 @@ where /// /// [phantom node payments]: crate::sign::PhantomKeysManager pub fn get_phantom_scid(&self) -> u64 { - let best_block_height = self.best_block.read().unwrap().height(); + let best_block_height = self.best_block.read().unwrap().height; let short_to_chan_info = self.short_to_chan_info.read().unwrap(); loop { let scid_candidate = fake_scid::Namespace::Phantom.get_fake_scid(best_block_height, &self.chain_hash, &self.fake_scid_rand_bytes, &self.entropy_source); @@ -9050,7 +9300,7 @@ where /// Note that this method is not guaranteed to return unique values, you may need to call it a few /// times to get a unique scid. pub fn get_intercept_scid(&self) -> u64 { - let best_block_height = self.best_block.read().unwrap().height(); + let best_block_height = self.best_block.read().unwrap().height; let short_to_chan_info = self.short_to_chan_info.read().unwrap(); loop { let scid_candidate = fake_scid::Namespace::Intercept.get_fake_scid(best_block_height, &self.chain_hash, &self.fake_scid_rand_bytes, &self.entropy_source); @@ -9117,9 +9367,12 @@ where /// [`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 let any blocked [`ChannelMonitorUpdate`]s fly. - fn handle_monitor_update_release(&self, counterparty_node_id: PublicKey, channel_funding_outpoint: OutPoint, mut completed_blocker: Option) { + fn handle_monitor_update_release(&self, counterparty_node_id: PublicKey, + channel_funding_outpoint: OutPoint, channel_id: ChannelId, + mut completed_blocker: Option) { + let logger = WithContext::from( - &self.logger, Some(counterparty_node_id), Some(channel_funding_outpoint.to_channel_id()) + &self.logger, Some(counterparty_node_id), Some(channel_id), ); loop { let per_peer_state = self.per_peer_state.read().unwrap(); @@ -9129,29 +9382,30 @@ where if let Some(blocker) = completed_blocker.take() { // Only do this on the first iteration of the loop. if let Some(blockers) = peer_state.actions_blocking_raa_monitor_updates - .get_mut(&channel_funding_outpoint.to_channel_id()) + .get_mut(&channel_id) { blockers.retain(|iter| iter != &blocker); } } if self.raa_monitor_updates_held(&peer_state.actions_blocking_raa_monitor_updates, - channel_funding_outpoint, counterparty_node_id) { + channel_funding_outpoint, channel_id, counterparty_node_id) { // Check that, while holding the peer lock, we don't have anything else // blocking monitor updates for this channel. If we do, release the monitor // update(s) when those blockers complete. log_trace!(logger, "Delaying monitor unlock for channel {} as another channel's mon update needs to complete first", - &channel_funding_outpoint.to_channel_id()); + &channel_id); break; } - if let hash_map::Entry::Occupied(mut chan_phase_entry) = peer_state.channel_by_id.entry(channel_funding_outpoint.to_channel_id()) { + if let hash_map::Entry::Occupied(mut chan_phase_entry) = peer_state.channel_by_id.entry( + 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!(logger, "Unlocking monitor updating for channel {} and updating monitor", - channel_funding_outpoint.to_channel_id()); - handle_new_monitor_update!(self, channel_funding_outpoint, channel_funding_outpoint.to_channel_id(), monitor_update, + 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 @@ -9160,7 +9414,7 @@ where } } else { log_trace!(logger, "Unlocked monitor updating for channel {} without monitors to update", - channel_funding_outpoint.to_channel_id()); + channel_id); } } } @@ -9177,9 +9431,9 @@ where for action in actions { match action { EventCompletionAction::ReleaseRAAChannelMonitorUpdate { - channel_funding_outpoint, counterparty_node_id + channel_funding_outpoint, channel_id, counterparty_node_id } => { - self.handle_monitor_update_release(counterparty_node_id, channel_funding_outpoint, None); + self.handle_monitor_update_release(counterparty_node_id, channel_funding_outpoint, channel_id, None); } } } @@ -9294,9 +9548,9 @@ where fn filtered_block_connected(&self, header: &Header, txdata: &TransactionData, height: u32) { { let best_block = self.best_block.read().unwrap(); - assert_eq!(best_block.block_hash(), header.prev_blockhash, + assert_eq!(best_block.block_hash, header.prev_blockhash, "Blocks must be connected in chain-order - the connected header must build on the last connected header"); - assert_eq!(best_block.height(), height - 1, + assert_eq!(best_block.height, height - 1, "Blocks must be connected in chain-order - the connected block height must be one greater than the previous height"); } @@ -9311,9 +9565,9 @@ where let new_height = height - 1; { let mut best_block = self.best_block.write().unwrap(); - assert_eq!(best_block.block_hash(), header.block_hash(), + assert_eq!(best_block.block_hash, header.block_hash(), "Blocks must be disconnected in chain-order - the disconnected header must be the last connected header"); - assert_eq!(best_block.height(), height, + assert_eq!(best_block.height, height, "Blocks must be disconnected in chain-order - the disconnected block must have the correct height"); *best_block = BestBlock::new(header.prev_blockhash, new_height) } @@ -9347,7 +9601,7 @@ where self.do_chain_event(Some(height), |channel| channel.transactions_confirmed(&block_hash, height, txdata, self.chain_hash, &self.node_signer, &self.default_configuration, &&WithChannelContext::from(&self.logger, &channel.context)) .map(|(a, b)| (a, Vec::new(), b))); - let last_best_block_height = self.best_block.read().unwrap().height(); + let last_best_block_height = self.best_block.read().unwrap().height; if height < last_best_block_height { let timestamp = self.highest_seen_timestamp.load(Ordering::Acquire); self.do_chain_event(Some(last_best_block_height), |channel| channel.best_block_updated(last_best_block_height, timestamp as u32, self.chain_hash, &self.node_signer, &self.default_configuration, &&WithChannelContext::from(&self.logger, &channel.context))); @@ -9523,14 +9777,13 @@ where 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)); + let reason_message = format!("{}", reason); + failed_channels.push(channel.context.force_shutdown(true, reason)); 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::DisconnectPeer { @@ -9578,8 +9831,8 @@ where htlc_id: htlc.prev_htlc_id, incoming_packet_shared_secret: htlc.forward_info.incoming_shared_secret, phantom_shared_secret: None, - channel_id: htlc.prev_channel_id, outpoint: htlc.prev_funding_outpoint, + channel_id: htlc.prev_channel_id, blinded_failure: htlc.forward_info.routing.blinded_failure(), }); @@ -9591,7 +9844,7 @@ where HTLCFailReason::from_failure_code(0x2000 | 2), HTLCDestination::InvalidForward { requested_forward_scid })); let logger = WithContext::from( - &self.logger, None, Some(htlc.prev_funding_outpoint.to_channel_id()) + &self.logger, None, Some(htlc.prev_channel_id) ); log_trace!(logger, "Timing out intercepted HTLC with requested forward scid {}", requested_forward_scid); false @@ -9736,6 +9989,9 @@ where #[cfg(dual_funding)] fn handle_accept_channel_v2(&self, counterparty_node_id: &PublicKey, msg: &msgs::AcceptChannelV2) { + // Note that we never need to persist the updated ChannelManager for an inbound + // accept_channel2 message - pre-funded channels are never written so there should be no + // change to the contents. let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); let _ = handle_error!(self, self.internal_accept_channel_v2(counterparty_node_id, msg), *counterparty_node_id); } @@ -9935,10 +10191,12 @@ where } &mut chan.context }, - // Unfunded channels will always be removed. - ChannelPhase::UnfundedOutboundV1(chan) => { - &mut chan.context + // We retain UnfundedOutboundV1 channel for some time in case + // peer unexpectedly disconnects, and intends to reconnect again. + ChannelPhase::UnfundedOutboundV1(_) => { + return true; }, + // Unfunded inbound channels will always be removed. ChannelPhase::UnfundedInboundV1(chan) => { &mut chan.context }, @@ -9953,8 +10211,7 @@ where }; // 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)); + failed_channels.push(context.force_shutdown(false, ClosureReason::DisconnectedPeer)); false }); // Note that we don't bother generating any events for pre-accept channels - @@ -10049,8 +10306,8 @@ where return NotifyOption::SkipPersistNoEvents; } e.insert(Mutex::new(PeerState { - channel_by_id: HashMap::new(), - inbound_channel_request_by_id: HashMap::new(), + channel_by_id: new_hash_map(), + inbound_channel_request_by_id: new_hash_map(), latest_features: init_msg.features.clone(), pending_msg_events: Vec::new(), in_flight_monitor_updates: BTreeMap::new(), @@ -10063,7 +10320,7 @@ where 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(); + 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() @@ -10086,21 +10343,49 @@ where 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 + for (_, phase) in peer_state.channel_by_id.iter_mut() { + match phase { + ChannelPhase::Funded(chan) => { + let logger = WithChannelContext::from(&self.logger, &chan.context); + pending_msg_events.push(events::MessageSendEvent::SendChannelReestablish { + node_id: chan.context.get_counterparty_node_id(), + msg: chan.get_channel_reestablish(&&logger), + }); + } + + ChannelPhase::UnfundedOutboundV1(chan) => { + pending_msg_events.push(events::MessageSendEvent::SendOpenChannel { + node_id: chan.context.get_counterparty_node_id(), + msg: chan.get_open_channel(self.chain_hash), + }); + } + + // TODO(dual_funding): Combine this match arm with above once #[cfg(dual_funding)] is removed. + #[cfg(dual_funding)] + ChannelPhase::UnfundedOutboundV2(chan) => { + pending_msg_events.push(events::MessageSendEvent::SendOpenChannelV2 { + node_id: chan.context.get_counterparty_node_id(), + msg: chan.get_open_channel_v2(self.chain_hash), + }); + }, + + ChannelPhase::UnfundedInboundV1(_) => { + // Since unfunded inbound channel maps are cleared upon disconnecting a peer, + // they are not persisted and won't be recovered after a crash. + // Therefore, they shouldn't exist at this point. + debug_assert!(false); + } + + // TODO(dual_funding): Combine this match arm with above once #[cfg(dual_funding)] is removed. + #[cfg(dual_funding)] + ChannelPhase::UnfundedInboundV2(_channel) => { + // Since unfunded inbound channel maps are cleared upon disconnecting a peer, + // they are not persisted and won't be recovered after a crash. + // Therefore, they shouldn't exist at this point. + debug_assert!(false); + }, } - ).for_each(|chan| { - let logger = WithChannelContext::from(&self.logger, &chan.context); - pending_msg_events.push(events::MessageSendEvent::SendChannelReestablish { - node_id: chan.context.get_counterparty_node_id(), - msg: chan.get_channel_reestablish(&&logger), - }); - }); + } } return NotifyOption::SkipPersistHandleEvents; @@ -10176,14 +10461,29 @@ where 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(ChannelPhase::UnfundedOutboundV1(chan)) = peer_state.channel_by_id.get_mut(&msg.channel_id) { - if let Ok(msg) = chan.maybe_handle_error_without_close(self.chain_hash, &self.fee_estimator) { - peer_state.pending_msg_events.push(events::MessageSendEvent::SendOpenChannel { - node_id: *counterparty_node_id, - msg, - }); - return; - } + match peer_state.channel_by_id.get_mut(&msg.channel_id) { + Some(ChannelPhase::UnfundedOutboundV1(ref mut chan)) => { + if let Ok(msg) = chan.maybe_handle_error_without_close(self.chain_hash, &self.fee_estimator) { + peer_state.pending_msg_events.push(events::MessageSendEvent::SendOpenChannel { + node_id: *counterparty_node_id, + msg, + }); + return; + } + }, + #[cfg(dual_funding)] + Some(ChannelPhase::UnfundedOutboundV2(ref mut chan)) => { + if let Ok(msg) = chan.maybe_handle_error_without_close(self.chain_hash, &self.fee_estimator) { + peer_state.pending_msg_events.push(events::MessageSendEvent::SendOpenChannelV2 { + node_id: *counterparty_node_id, + msg, + }); + return; + } + }, + None | Some(ChannelPhase::UnfundedInboundV1(_) | ChannelPhase::Funded(_)) => (), + #[cfg(dual_funding)] + Some(ChannelPhase::UnfundedInboundV2(_)) => (), } } @@ -10313,7 +10613,7 @@ where let amount_msats = match InvoiceBuilder::::amount_msats( &invoice_request ) { - Ok(amount_msats) => Some(amount_msats), + Ok(amount_msats) => amount_msats, Err(error) => return Some(OffersMessage::InvoiceError(error.into())), }; let invoice_request = match invoice_request.verify(expanded_key, secp_ctx) { @@ -10323,64 +10623,78 @@ where return Some(OffersMessage::InvoiceError(error.into())); }, }; - let relative_expiry = DEFAULT_RELATIVE_EXPIRY.as_secs() as u32; - match self.create_inbound_payment(amount_msats, relative_expiry, None) { - Ok((payment_hash, payment_secret)) if invoice_request.keys.is_some() => { - let payment_paths = vec![ - self.create_one_hop_blinded_payment_path(payment_secret), - ]; - #[cfg(not(feature = "no-std"))] - let builder = invoice_request.respond_using_derived_keys( - payment_paths, payment_hash - ); - #[cfg(feature = "no-std")] - let created_at = Duration::from_secs( - self.highest_seen_timestamp.load(Ordering::Acquire) as u64 - ); - #[cfg(feature = "no-std")] - let builder = invoice_request.respond_using_derived_keys_no_std( - payment_paths, payment_hash, created_at - ); - match builder.and_then(|b| b.allow_mpp().build_and_sign(secp_ctx)) { - Ok(invoice) => Some(OffersMessage::Invoice(invoice)), - Err(error) => Some(OffersMessage::InvoiceError(error.into())), - } - }, - Ok((payment_hash, payment_secret)) => { - let payment_paths = vec![ - self.create_one_hop_blinded_payment_path(payment_secret), - ]; - #[cfg(not(feature = "no-std"))] - let builder = invoice_request.respond_with(payment_paths, payment_hash); - #[cfg(feature = "no-std")] - let created_at = Duration::from_secs( - self.highest_seen_timestamp.load(Ordering::Acquire) as u64 - ); - #[cfg(feature = "no-std")] - let builder = invoice_request.respond_with_no_std( - payment_paths, payment_hash, created_at - ); - let response = builder.and_then(|builder| builder.allow_mpp().build()) - .map_err(|e| OffersMessage::InvoiceError(e.into())) - .and_then(|invoice| - match invoice.sign(|invoice| self.node_signer.sign_bolt12_invoice(invoice)) { - Ok(invoice) => Ok(OffersMessage::Invoice(invoice)), - Err(SignError::Signing(())) => Err(OffersMessage::InvoiceError( - InvoiceError::from_string("Failed signing invoice".to_string()) - )), - Err(SignError::Verification(_)) => Err(OffersMessage::InvoiceError( - InvoiceError::from_string("Failed invoice signature verification".to_string()) - )), - }); - match response { - Ok(invoice) => Some(invoice), - Err(error) => Some(error), - } + let relative_expiry = DEFAULT_RELATIVE_EXPIRY.as_secs() as u32; + let (payment_hash, payment_secret) = match self.create_inbound_payment( + Some(amount_msats), relative_expiry, None + ) { + Ok((payment_hash, payment_secret)) => (payment_hash, payment_secret), + Err(()) => { + let error = Bolt12SemanticError::InvalidAmount; + return Some(OffersMessage::InvoiceError(error.into())); }, + }; + + let payment_paths = match self.create_blinded_payment_paths( + amount_msats, payment_secret + ) { + Ok(payment_paths) => payment_paths, Err(()) => { - Some(OffersMessage::InvoiceError(Bolt12SemanticError::InvalidAmount.into())) + let error = Bolt12SemanticError::MissingPaths; + return Some(OffersMessage::InvoiceError(error.into())); }, + }; + + #[cfg(not(feature = "std"))] + let created_at = Duration::from_secs( + self.highest_seen_timestamp.load(Ordering::Acquire) as u64 + ); + + if invoice_request.keys.is_some() { + #[cfg(feature = "std")] + let builder = invoice_request.respond_using_derived_keys( + payment_paths, payment_hash + ); + #[cfg(not(feature = "std"))] + let builder = invoice_request.respond_using_derived_keys_no_std( + payment_paths, payment_hash, created_at + ); + let builder: Result, _> = + builder.map(|b| b.into()); + match builder.and_then(|b| b.allow_mpp().build_and_sign(secp_ctx)) { + Ok(invoice) => Some(OffersMessage::Invoice(invoice)), + Err(error) => Some(OffersMessage::InvoiceError(error.into())), + } + } else { + #[cfg(feature = "std")] + let builder = invoice_request.respond_with(payment_paths, payment_hash); + #[cfg(not(feature = "std"))] + let builder = invoice_request.respond_with_no_std( + payment_paths, payment_hash, created_at + ); + let builder: Result, _> = + builder.map(|b| b.into()); + let response = builder.and_then(|builder| builder.allow_mpp().build()) + .map_err(|e| OffersMessage::InvoiceError(e.into())) + .and_then(|invoice| { + #[cfg(c_bindings)] + let mut invoice = invoice; + match invoice.sign(|invoice: &UnsignedBolt12Invoice| + self.node_signer.sign_bolt12_invoice(invoice) + ) { + Ok(invoice) => Ok(OffersMessage::Invoice(invoice)), + Err(SignError::Signing) => Err(OffersMessage::InvoiceError( + InvoiceError::from_string("Failed signing invoice".to_string()) + )), + Err(SignError::Verification(_)) => Err(OffersMessage::InvoiceError( + InvoiceError::from_string("Failed invoice signature verification".to_string()) + )), + } + }); + match response { + Ok(invoice) => Some(invoice), + Err(error) => Some(error), + } } }, OffersMessage::Invoice(invoice) => { @@ -10467,6 +10781,7 @@ pub fn provided_init_features(config: &UserConfig) -> InitFeatures { features.set_channel_type_optional(); features.set_scid_privacy_optional(); features.set_zero_conf_optional(); + features.set_route_blinding_optional(); if config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx { features.set_anchors_zero_fee_htlc_tx_optional(); } @@ -10527,6 +10842,8 @@ impl Writeable for ChannelDetails { (37, user_channel_id_high_opt, option), (39, self.feerate_sat_per_1000_weight, option), (41, self.channel_shutdown_state, option), + (43, self.pending_inbound_htlcs, optional_vec), + (45, self.pending_outbound_htlcs, optional_vec), }); Ok(()) } @@ -10565,6 +10882,8 @@ impl Readable for ChannelDetails { (37, user_channel_id_high_opt, option), (39, feerate_sat_per_1000_weight, option), (41, channel_shutdown_state, option), + (43, pending_inbound_htlcs, optional_vec), + (45, pending_outbound_htlcs, optional_vec), }); // `user_channel_id` used to be a single u64 value. In order to remain backwards compatible with @@ -10601,6 +10920,8 @@ impl Readable for ChannelDetails { inbound_htlc_maximum_msat, feerate_sat_per_1000_weight, channel_shutdown_state, + pending_inbound_htlcs: pending_inbound_htlcs.unwrap_or(Vec::new()), + pending_outbound_htlcs: pending_outbound_htlcs.unwrap_or(Vec::new()), }) } } @@ -10613,6 +10934,7 @@ impl_writeable_tlv_based!(PhantomRouteHints, { impl_writeable_tlv_based!(BlindedForward, { (0, inbound_blinding_point, required), + (1, failure, (default_value, BlindedFailure::FromIntroductionNode)), }); impl_writeable_tlv_based_enum!(PendingHTLCRouting, @@ -10627,6 +10949,7 @@ impl_writeable_tlv_based_enum!(PendingHTLCRouting, (2, incoming_cltv_expiry, required), (3, payment_metadata, option), (5, custom_tlvs, optional_vec), + (7, requires_blinded_error, (default_value, false)), }, (2, ReceiveKeysend) => { (0, payment_preimage, required), @@ -10721,7 +11044,8 @@ impl_writeable_tlv_based_enum!(PendingHTLCStatus, ; ); impl_writeable_tlv_based_enum!(BlindedFailure, - (0, FromIntroductionNode) => {}, ; + (0, FromIntroductionNode) => {}, + (2, FromBlindedNode) => {}, ; ); impl_writeable_tlv_based!(HTLCPreviousHopData, { @@ -10891,13 +11215,68 @@ impl_writeable_tlv_based!(PendingAddHTLCInfo, { (7, prev_channel_id, (default_value, ChannelId::v1_from_funding_outpoint(prev_funding_outpoint.0.unwrap()))), }); -impl_writeable_tlv_based_enum!(HTLCForwardInfo, - (1, FailHTLC) => { - (0, htlc_id, required), - (2, err_packet, required), - }; - (0, AddHTLC) -); +impl Writeable for HTLCForwardInfo { + fn write(&self, w: &mut W) -> Result<(), io::Error> { + const FAIL_HTLC_VARIANT_ID: u8 = 1; + match self { + Self::AddHTLC(info) => { + 0u8.write(w)?; + info.write(w)?; + }, + Self::FailHTLC { htlc_id, err_packet } => { + FAIL_HTLC_VARIANT_ID.write(w)?; + write_tlv_fields!(w, { + (0, htlc_id, required), + (2, err_packet, required), + }); + }, + Self::FailMalformedHTLC { htlc_id, failure_code, sha256_of_onion } => { + // Since this variant was added in 0.0.119, write this as `::FailHTLC` with an empty error + // packet so older versions have something to fail back with, but serialize the real data as + // optional TLVs for the benefit of newer versions. + FAIL_HTLC_VARIANT_ID.write(w)?; + let dummy_err_packet = msgs::OnionErrorPacket { data: Vec::new() }; + write_tlv_fields!(w, { + (0, htlc_id, required), + (1, failure_code, required), + (2, dummy_err_packet, required), + (3, sha256_of_onion, required), + }); + }, + } + Ok(()) + } +} + +impl Readable for HTLCForwardInfo { + fn read(r: &mut R) -> Result { + let id: u8 = Readable::read(r)?; + Ok(match id { + 0 => Self::AddHTLC(Readable::read(r)?), + 1 => { + _init_and_read_len_prefixed_tlv_fields!(r, { + (0, htlc_id, required), + (1, malformed_htlc_failure_code, option), + (2, err_packet, required), + (3, sha256_of_onion, option), + }); + if let Some(failure_code) = malformed_htlc_failure_code { + Self::FailMalformedHTLC { + htlc_id: _init_tlv_based_struct_field!(htlc_id, required), + failure_code, + sha256_of_onion: sha256_of_onion.ok_or(DecodeError::InvalidValue)?, + } + } else { + Self::FailHTLC { + htlc_id: _init_tlv_based_struct_field!(htlc_id, required), + err_packet: _init_tlv_based_struct_field!(err_packet, required), + } + } + }, + _ => return Err(DecodeError::InvalidValue), + }) + } +} impl_writeable_tlv_based!(PendingInboundPayment, { (0, payment_secret, required), @@ -10926,8 +11305,8 @@ where self.chain_hash.write(writer)?; { let best_block = self.best_block.read().unwrap(); - best_block.height().write(writer)?; - best_block.block_hash().write(writer)?; + best_block.height.write(writer)?; + best_block.block_hash.write(writer)?; } let mut serializable_peer_count: u64 = 0; @@ -11077,7 +11456,7 @@ where } // Encode without retry info for 0.0.101 compatibility. - let mut pending_outbound_payments_no_retry: HashMap> = HashMap::new(); + let mut pending_outbound_payments_no_retry: HashMap> = new_hash_map(); for (id, outbound) in pending_outbound_payments.iter() { match outbound { PendingOutboundPayment::Legacy { session_privs } | @@ -11105,7 +11484,7 @@ where for ((counterparty_id, _), peer_state) in per_peer_state.iter().zip(peer_states.iter()) { for (funding_outpoint, updates) in peer_state.in_flight_monitor_updates.iter() { if !updates.is_empty() { - if in_flight_monitor_updates.is_none() { in_flight_monitor_updates = Some(HashMap::new()); } + if in_flight_monitor_updates.is_none() { in_flight_monitor_updates = Some(new_hash_map()); } in_flight_monitor_updates.as_mut().unwrap().insert((counterparty_id, funding_outpoint), updates); } } @@ -11294,7 +11673,9 @@ where mut channel_monitors: Vec<&'a mut ChannelMonitor<::EcdsaSigner>>) -> Self { Self { entropy_source, node_signer, signer_provider, fee_estimator, chain_monitor, tx_broadcaster, router, logger, default_config, - channel_monitors: channel_monitors.drain(..).map(|monitor| { (monitor.get_funding_txo().0, monitor) }).collect() + channel_monitors: hash_map_from_iter( + channel_monitors.drain(..).map(|monitor| { (monitor.get_funding_txo().0, monitor) }) + ), } } } @@ -11341,18 +11722,20 @@ where let mut failed_htlcs = Vec::new(); let channel_count: u64 = Readable::read(reader)?; - let mut funding_txo_set = HashSet::with_capacity(cmp::min(channel_count as usize, 128)); - let mut funded_peer_channels: HashMap>> = 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 funding_txo_set = hash_set_with_capacity(cmp::min(channel_count as usize, 128)); + let mut funded_peer_channels: HashMap>> = hash_map_with_capacity(cmp::min(channel_count as usize, 128)); + let mut outpoint_to_peer = hash_map_with_capacity(cmp::min(channel_count as usize, 128)); + let mut short_to_chan_info = hash_map_with_capacity(cmp::min(channel_count as usize, 128)); let mut channel_closures = VecDeque::new(); let mut close_background_events = Vec::new(); + let mut funding_txo_to_channel_id = hash_map_with_capacity(channel_count as usize); for _ in 0..channel_count { let mut channel: Channel = Channel::read(reader, ( &args.entropy_source, &args.signer_provider, best_block_height, &provided_channel_type_features(&args.default_config) ))?; let logger = WithChannelContext::from(&args.logger, &channel.context); let funding_txo = channel.context.get_funding_txo().ok_or(DecodeError::InvalidValue)?; + funding_txo_to_channel_id.insert(funding_txo, channel.context.channel_id()); funding_txo_set.insert(funding_txo.clone()); if let Some(ref mut monitor) = args.channel_monitors.get_mut(&funding_txo) { if channel.get_cur_holder_commitment_transaction_number() > monitor.get_cur_holder_commitment_number() || @@ -11378,13 +11761,13 @@ where log_error!(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 mut shutdown_result = channel.context.force_shutdown(true); + let mut shutdown_result = channel.context.force_shutdown(true, ClosureReason::OutdatedChannelManager); if shutdown_result.unbroadcasted_batch_funding_txid.is_some() { return Err(DecodeError::InvalidValue); } - if let Some((counterparty_node_id, funding_txo, update)) = shutdown_result.monitor_update { + if let Some((counterparty_node_id, funding_txo, channel_id, update)) = shutdown_result.monitor_update { close_background_events.push(BackgroundEvent::MonitorUpdateRegeneratedOnStartup { - counterparty_node_id, funding_txo, update + counterparty_node_id, funding_txo, channel_id, update }); } failed_htlcs.append(&mut shutdown_result.dropped_outbound_htlcs); @@ -11394,6 +11777,7 @@ where reason: ClosureReason::OutdatedChannelManager, counterparty_node_id: Some(channel.context.get_counterparty_node_id()), channel_capacity_sats: Some(channel.context.get_value_satoshis()), + channel_funding_txo: channel.context.get_funding_txo(), }, None)); for (channel_htlc_source, payment_hash) in channel.inflight_htlc_sources() { let mut found_htlc = false; @@ -11421,8 +11805,8 @@ where 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_broadcast() { - id_to_peer.insert(channel.context.channel_id(), channel.context.get_counterparty_node_id()); + if let Some(funding_txo) = channel.context.get_funding_txo() { + outpoint_to_peer.insert(funding_txo, channel.context.get_counterparty_node_id()); } match funded_peer_channels.entry(channel.context.get_counterparty_node_id()) { hash_map::Entry::Occupied(mut entry) => { @@ -11430,7 +11814,7 @@ where by_id_map.insert(channel.context.channel_id(), ChannelPhase::Funded(channel)); }, hash_map::Entry::Vacant(entry) => { - let mut by_id_map = HashMap::new(); + let mut by_id_map = new_hash_map(); by_id_map.insert(channel.context.channel_id(), ChannelPhase::Funded(channel)); entry.insert(by_id_map); } @@ -11440,13 +11824,14 @@ where // If we were persisted and shut down while the initial ChannelMonitor persistence // was in-progress, we never broadcasted the funding transaction and can still // safely discard the channel. - let _ = channel.context.force_shutdown(false); + let _ = channel.context.force_shutdown(false, ClosureReason::DisconnectedPeer); channel_closures.push_back((events::Event::ChannelClosed { 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()), + channel_funding_txo: channel.context.get_funding_txo(), }, None)); } else { log_error!(logger, "Missing ChannelMonitor for channel {} needed by ChannelManager.", &channel.context.channel_id()); @@ -11461,19 +11846,22 @@ where for (funding_txo, monitor) in args.channel_monitors.iter() { if !funding_txo_set.contains(funding_txo) { let logger = WithChannelMonitor::from(&args.logger, monitor); + let channel_id = monitor.channel_id(); log_info!(logger, "Queueing monitor update to ensure missing channel {} is force closed", - &funding_txo.to_channel_id()); + &channel_id); let monitor_update = ChannelMonitorUpdate { update_id: CLOSED_CHANNEL_UPDATE_ID, + counterparty_node_id: None, updates: vec![ChannelMonitorUpdateStep::ChannelForceClosed { should_broadcast: true }], + channel_id: Some(monitor.channel_id()), }; - close_background_events.push(BackgroundEvent::ClosedMonitorUpdateRegeneratedOnStartup((*funding_txo, monitor_update))); + close_background_events.push(BackgroundEvent::ClosedMonitorUpdateRegeneratedOnStartup((*funding_txo, channel_id, monitor_update))); } } const MAX_ALLOC_SIZE: usize = 1024 * 64; let forward_htlcs_count: u64 = Readable::read(reader)?; - let mut forward_htlcs = HashMap::with_capacity(cmp::min(forward_htlcs_count as usize, 128)); + let mut forward_htlcs = hash_map_with_capacity(cmp::min(forward_htlcs_count as usize, 128)); for _ in 0..forward_htlcs_count { let short_channel_id = Readable::read(reader)?; let pending_forwards_count: u64 = Readable::read(reader)?; @@ -11499,7 +11887,7 @@ where let peer_state_from_chans = |channel_by_id| { PeerState { channel_by_id, - inbound_channel_request_by_id: HashMap::new(), + inbound_channel_request_by_id: new_hash_map(), latest_features: InitFeatures::empty(), pending_msg_events: Vec::new(), in_flight_monitor_updates: BTreeMap::new(), @@ -11510,10 +11898,10 @@ where }; 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>)>())); + let mut per_peer_state = hash_map_with_capacity(cmp::min(peer_count as usize, MAX_ALLOC_SIZE/mem::size_of::<(PublicKey, Mutex>)>())); for _ in 0..peer_count { let peer_pubkey = Readable::read(reader)?; - let peer_chans = funded_peer_channels.remove(&peer_pubkey).unwrap_or(HashMap::new()); + let peer_chans = funded_peer_channels.remove(&peer_pubkey).unwrap_or(new_hash_map()); 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)); @@ -11547,7 +11935,7 @@ where let highest_seen_timestamp: u32 = Readable::read(reader)?; let pending_inbound_payment_count: u64 = Readable::read(reader)?; - let mut pending_inbound_payments: HashMap = HashMap::with_capacity(cmp::min(pending_inbound_payment_count as usize, MAX_ALLOC_SIZE/(3*32))); + let mut pending_inbound_payments: HashMap = hash_map_with_capacity(cmp::min(pending_inbound_payment_count as usize, MAX_ALLOC_SIZE/(3*32))); for _ in 0..pending_inbound_payment_count { if pending_inbound_payments.insert(Readable::read(reader)?, Readable::read(reader)?).is_some() { return Err(DecodeError::InvalidValue); @@ -11556,11 +11944,11 @@ where let pending_outbound_payments_count_compat: u64 = Readable::read(reader)?; let mut pending_outbound_payments_compat: HashMap = - HashMap::with_capacity(cmp::min(pending_outbound_payments_count_compat as usize, MAX_ALLOC_SIZE/32)); + hash_map_with_capacity(cmp::min(pending_outbound_payments_count_compat as usize, MAX_ALLOC_SIZE/32)); for _ in 0..pending_outbound_payments_count_compat { let session_priv = Readable::read(reader)?; let payment = PendingOutboundPayment::Legacy { - session_privs: [session_priv].iter().cloned().collect() + session_privs: hash_set_from_iter([session_priv]), }; if pending_outbound_payments_compat.insert(PaymentId(session_priv), payment).is_some() { return Err(DecodeError::InvalidValue) @@ -11570,13 +11958,13 @@ where // pending_outbound_payments_no_retry is for compatibility with 0.0.101 clients. let mut pending_outbound_payments_no_retry: Option>> = None; let mut pending_outbound_payments = None; - let mut pending_intercepted_htlcs: Option> = Some(HashMap::new()); + let mut pending_intercepted_htlcs: Option> = Some(new_hash_map()); let mut received_network_pubkey: Option = None; let mut fake_scid_rand_bytes: Option<[u8; 32]> = None; let mut probing_cookie_secret: Option<[u8; 32]> = None; let mut claimable_htlc_purposes = None; let mut claimable_htlc_onion_fields = None; - let mut pending_claiming_payments = Some(HashMap::new()); + let mut pending_claiming_payments = Some(new_hash_map()); let mut monitor_update_blocked_actions_per_peer: Option>)>> = Some(Vec::new()); let mut events_override = None; let mut in_flight_monitor_updates: Option>> = None; @@ -11613,7 +12001,7 @@ where if pending_outbound_payments.is_none() && pending_outbound_payments_no_retry.is_none() { pending_outbound_payments = Some(pending_outbound_payments_compat); } else if pending_outbound_payments.is_none() { - let mut outbounds = HashMap::new(); + let mut outbounds = new_hash_map(); for (id, session_privs) in pending_outbound_payments_no_retry.unwrap().drain() { outbounds.insert(id, PendingOutboundPayment::Legacy { session_privs }); } @@ -11644,12 +12032,13 @@ where $chan_in_flight_upds.retain(|upd| upd.update_id > $monitor.get_latest_update_id()); for update in $chan_in_flight_upds.iter() { log_trace!($logger, "Replaying ChannelMonitorUpdate {} for {}channel {}", - update.update_id, $channel_info_log, &$funding_txo.to_channel_id()); + update.update_id, $channel_info_log, &$monitor.channel_id()); max_in_flight_update_id = cmp::max(max_in_flight_update_id, update.update_id); pending_background_events.push( BackgroundEvent::MonitorUpdateRegeneratedOnStartup { counterparty_node_id: $counterparty_node_id, funding_txo: $funding_txo, + channel_id: $monitor.channel_id(), update: update.clone(), }); } @@ -11660,7 +12049,7 @@ where pending_background_events.push( BackgroundEvent::MonitorUpdatesComplete { counterparty_node_id: $counterparty_node_id, - channel_id: $funding_txo.to_channel_id(), + channel_id: $monitor.channel_id(), }); } if $peer_state.in_flight_monitor_updates.insert($funding_txo, $chan_in_flight_upds).is_some() { @@ -11714,21 +12103,22 @@ where if let Some(in_flight_upds) = in_flight_monitor_updates { for ((counterparty_id, funding_txo), mut chan_in_flight_updates) in in_flight_upds { - let logger = WithContext::from(&args.logger, Some(counterparty_id), Some(funding_txo.to_channel_id())); + let channel_id = funding_txo_to_channel_id.get(&funding_txo).copied(); + let logger = WithContext::from(&args.logger, Some(counterparty_id), channel_id); if let Some(monitor) = args.channel_monitors.get(&funding_txo) { // Now that we've removed all the in-flight monitor updates for channels that are // still open, we need to replay any monitor updates that are for closed channels, // creating the neccessary peer_state entries as we go. let peer_state_mutex = per_peer_state.entry(counterparty_id).or_insert_with(|| { - Mutex::new(peer_state_from_chans(HashMap::new())) + Mutex::new(peer_state_from_chans(new_hash_map())) }); let mut peer_state = peer_state_mutex.lock().unwrap(); handle_in_flight_updates!(counterparty_id, chan_in_flight_updates, funding_txo, monitor, peer_state, logger, "closed "); } else { log_error!(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!(logger, " The ChannelMonitor for channel {} is missing.", - &funding_txo.to_channel_id()); + log_error!(logger, " The ChannelMonitor for channel {} is missing.", if let Some(channel_id) = + channel_id { channel_id.to_string() } else { format!("with outpoint {}", funding_txo) } ); log_error!(logger, " The chain::Watch API *requires* that monitors are persisted durably before returning,"); log_error!(logger, " client applications must ensure that ChannelMonitor data is always available and the latest to avoid funds loss!"); log_error!(logger, " Without the latest ChannelMonitor we cannot continue without risking funds."); @@ -11756,8 +12146,7 @@ where // We only rebuild the pending payments map if we were most recently serialized by // 0.0.102+ for (_, monitor) in args.channel_monitors.iter() { - let counterparty_opt = id_to_peer.get(&monitor.get_funding_txo().0.to_channel_id()); - let _chan_id = monitor.get_funding_txo().0.to_channel_id(); + let counterparty_opt = outpoint_to_peer.get(&monitor.get_funding_txo().0); if counterparty_opt.is_none() { let logger = WithChannelMonitor::from(&args.logger, monitor); for (htlc_source, (htlc, _)) in monitor.get_pending_or_resolved_outbound_htlcs() { @@ -11782,7 +12171,7 @@ where retry_strategy: None, attempts: PaymentAttempts::new(), payment_params: None, - session_privs: [session_priv_bytes].iter().map(|a| *a).collect(), + session_privs: hash_set_from_iter([session_priv_bytes]), payment_hash: htlc.payment_hash, 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 @@ -11817,7 +12206,7 @@ where if let HTLCForwardInfo::AddHTLC(htlc_info) = forward { if pending_forward_matches_htlc(&htlc_info) { log_info!(logger, "Removing pending to-forward HTLC with hash {} as it was forwarded to the closed channel {}", - &htlc.payment_hash, &monitor.get_funding_txo().0.to_channel_id()); + &htlc.payment_hash, &monitor.channel_id()); false } else { true } } else { true } @@ -11827,7 +12216,7 @@ where pending_intercepted_htlcs.as_mut().unwrap().retain(|intercepted_id, htlc_info| { if pending_forward_matches_htlc(&htlc_info) { log_info!(logger, "Removing pending intercepted HTLC with hash {} as it was forwarded to the closed channel {}", - &htlc.payment_hash, &monitor.get_funding_txo().0.to_channel_id()); + &htlc.payment_hash, &monitor.channel_id()); pending_events_read.retain(|(event, _)| { if let Event::HTLCIntercepted { intercept_id: ev_id, .. } = event { intercepted_id != ev_id @@ -11851,6 +12240,7 @@ where let compl_action = EventCompletionAction::ReleaseRAAChannelMonitorUpdate { channel_funding_outpoint: monitor.get_funding_txo().0, + channel_id: monitor.channel_id(), counterparty_node_id: path.hops[0].pubkey, }; pending_outbounds.claim_htlc(payment_id, preimage, session_priv, @@ -11876,7 +12266,7 @@ where // channel_id -> peer map entry). counterparty_opt.is_none(), counterparty_opt.cloned().or(monitor.get_counterparty_node_id()), - monitor.get_funding_txo().0)) + monitor.get_funding_txo().0, monitor.channel_id())) } else { None } } else { // If it was an outbound payment, we've handled it above - if a preimage @@ -11906,7 +12296,7 @@ where let inbound_pmt_key_material = args.node_signer.get_inbound_payment_key_material(); let expanded_inbound_key = inbound_payment::ExpandedKey::new(&inbound_pmt_key_material); - let mut claimable_payments = HashMap::with_capacity(claimable_htlcs_list.len()); + let mut claimable_payments = hash_map_with_capacity(claimable_htlcs_list.len()); if let Some(purposes) = claimable_htlc_purposes { if purposes.len() != claimable_htlcs_list.len() { return Err(DecodeError::InvalidValue); @@ -11979,7 +12369,7 @@ where } } - let mut outbound_scid_aliases = HashSet::new(); + let mut outbound_scid_aliases = new_hash_set(); 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; @@ -12050,7 +12440,7 @@ where // without the new monitor persisted - we'll end up right back here on // restart. let previous_channel_id = claimable_htlc.prev_hop.channel_id; - if let Some(peer_node_id) = id_to_peer.get(&previous_channel_id){ + if let Some(peer_node_id) = outpoint_to_peer.get(&claimable_htlc.prev_hop.outpoint) { 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; @@ -12082,14 +12472,14 @@ where for action in actions.iter() { if let MonitorUpdateCompletionAction::EmitEventAndFreeOtherChannel { downstream_counterparty_and_funding_outpoint: - Some((blocked_node_id, blocked_channel_outpoint, blocking_action)), .. + Some((blocked_node_id, _blocked_channel_outpoint, blocked_channel_id, blocking_action)), .. } = action { - if let Some(blocked_peer_state) = per_peer_state.get(&blocked_node_id) { + if let Some(blocked_peer_state) = per_peer_state.get(blocked_node_id) { log_trace!(logger, "Holding the next revoke_and_ack from {} until the preimage is durably persisted in the inbound edge's ChannelMonitor", - blocked_channel_outpoint.to_channel_id()); + blocked_channel_id); blocked_peer_state.lock().unwrap().actions_blocking_raa_monitor_updates - .entry(blocked_channel_outpoint.to_channel_id()) + .entry(*blocked_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 @@ -12128,7 +12518,7 @@ where forward_htlcs: Mutex::new(forward_htlcs), claimable_payments: Mutex::new(ClaimablePayments { claimable_payments, pending_claiming_payments: pending_claiming_payments.unwrap() }), outbound_scid_aliases: Mutex::new(outbound_scid_aliases), - id_to_peer: Mutex::new(id_to_peer), + outpoint_to_peer: Mutex::new(outpoint_to_peer), short_to_chan_info: FairRwLock::new(short_to_chan_info), fake_scid_rand_bytes: fake_scid_rand_bytes.unwrap(), @@ -12169,12 +12559,12 @@ where 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 { + for (source, preimage, downstream_value, downstream_closed, downstream_node_id, downstream_funding, downstream_channel_id) 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, true, downstream_node_id, downstream_funding); + channel_manager.claim_funds_internal(source, preimage, Some(downstream_value), None, + downstream_closed, true, downstream_node_id, downstream_funding, downstream_channel_id); } //TODO: Broadcast channel update for closed channels, but only after we've made a @@ -12197,12 +12587,14 @@ mod tests { use crate::events::{Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider, ClosureReason}; use crate::ln::{PaymentPreimage, PaymentHash, PaymentSecret}; use crate::ln::ChannelId; - use crate::ln::channelmanager::{create_recv_pending_htlc_info, inbound_payment, PaymentId, PaymentSendFailure, RecipientOnionFields, InterceptId}; + use crate::ln::channelmanager::{create_recv_pending_htlc_info, HTLCForwardInfo, inbound_payment, PaymentId, PaymentSendFailure, RecipientOnionFields, InterceptId}; use crate::ln::functional_test_utils::*; use crate::ln::msgs::{self, ErrorAction}; use crate::ln::msgs::ChannelMessageHandler; + use crate::prelude::*; use crate::routing::router::{PaymentParameters, RouteParameters, find_route}; use crate::util::errors::APIError; + use crate::util::ser::Writeable; use crate::util::test_utils; use crate::util::config::{ChannelConfig, ChannelConfigUpdate}; use crate::sign::EntropySource; @@ -12749,8 +13141,8 @@ mod tests { } #[test] - fn test_id_to_peer_coverage() { - // Test that the `ChannelManager:id_to_peer` contains channels which have been assigned + fn test_outpoint_to_peer_coverage() { + // Test that the `ChannelManager:outpoint_to_peer` contains channels which have been assigned // a `channel_id` (i.e. have had the funding tx created), and that they are removed once // the channel is successfully closed. let chanmon_cfgs = create_chanmon_cfgs(2); @@ -12764,42 +13156,42 @@ mod tests { 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); - let (temporary_channel_id, tx, _funding_output) = create_funding_transaction(&nodes[0], &nodes[1].node.get_our_node_id(), 1_000_000, 42); + 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 = ChannelId::from_bytes(tx.txid().to_byte_array()); { - // Ensure that the `id_to_peer` map is empty until either party has received the + // Ensure that the `outpoint_to_peer` map is empty until either party has received the // funding transaction, and have the real `channel_id`. - assert_eq!(nodes[0].node.id_to_peer.lock().unwrap().len(), 0); - assert_eq!(nodes[1].node.id_to_peer.lock().unwrap().len(), 0); + assert_eq!(nodes[0].node.outpoint_to_peer.lock().unwrap().len(), 0); + assert_eq!(nodes[1].node.outpoint_to_peer.lock().unwrap().len(), 0); } nodes[0].node.funding_transaction_generated(&temporary_channel_id, &nodes[1].node.get_our_node_id(), tx.clone()).unwrap(); { - // Assert that `nodes[0]`'s `id_to_peer` map is populated with the channel as soon as + // Assert that `nodes[0]`'s `outpoint_to_peer` map is populated with the channel as soon as // as it has the funding transaction. - let nodes_0_lock = nodes[0].node.id_to_peer.lock().unwrap(); + let nodes_0_lock = nodes[0].node.outpoint_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(&funding_output)); } - assert_eq!(nodes[1].node.id_to_peer.lock().unwrap().len(), 0); + assert_eq!(nodes[1].node.outpoint_to_peer.lock().unwrap().len(), 0); let funding_created_msg = 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_msg); { - let nodes_0_lock = nodes[0].node.id_to_peer.lock().unwrap(); + let nodes_0_lock = nodes[0].node.outpoint_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(&funding_output)); } expect_channel_pending_event(&nodes[1], &nodes[0].node.get_our_node_id()); { - // Assert that `nodes[1]`'s `id_to_peer` map is populated with the channel as soon as - // as it has the funding transaction. - let nodes_1_lock = nodes[1].node.id_to_peer.lock().unwrap(); + // Assert that `nodes[1]`'s `outpoint_to_peer` map is populated with the channel as + // soon as it has the funding transaction. + let nodes_1_lock = nodes[1].node.outpoint_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(&funding_output)); } check_added_monitors!(nodes[1], 1); let funding_signed = get_event_msg!(nodes[1], MessageSendEvent::SendFundingSigned, nodes[0].node.get_our_node_id()); @@ -12818,23 +13210,23 @@ mod tests { let closing_signed_node_0 = 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(), &closing_signed_node_0); { - // Assert that the channel is kept in the `id_to_peer` map for both nodes until the + // Assert that the channel is kept in the `outpoint_to_peer` map for both nodes until the // channel can be fully closed by both parties (i.e. no outstanding htlcs exists, the // fee for the closing transaction has been negotiated and the parties has the other // party's signature for the fee negotiated closing transaction.) - let nodes_0_lock = nodes[0].node.id_to_peer.lock().unwrap(); + let nodes_0_lock = nodes[0].node.outpoint_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(&funding_output)); } { // At this stage, `nodes[1]` has proposed a fee for the closing transaction in the // `handle_closing_signed` call above. As `nodes[1]` has not yet received the signature // from `nodes[0]` for the closing transaction with the proposed fee, the channel is - // kept in the `nodes[1]`'s `id_to_peer` map. - let nodes_1_lock = nodes[1].node.id_to_peer.lock().unwrap(); + // kept in the `nodes[1]`'s `outpoint_to_peer` map. + let nodes_1_lock = nodes[1].node.outpoint_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(&funding_output)); } 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())); @@ -12842,29 +13234,29 @@ mod tests { // `nodes[0]` accepts `nodes[1]`'s proposed fee for the closing transaction, and // therefore has all it needs to fully close the channel (both signatures for the // closing transaction). - // Assert that the channel is removed from `nodes[0]`'s `id_to_peer` map as it can be + // Assert that the channel is removed from `nodes[0]`'s `outpoint_to_peer` map as it can be // fully closed by `nodes[0]`. - assert_eq!(nodes[0].node.id_to_peer.lock().unwrap().len(), 0); + assert_eq!(nodes[0].node.outpoint_to_peer.lock().unwrap().len(), 0); - // Assert that the channel is still in `nodes[1]`'s `id_to_peer` map, as `nodes[1]` + // Assert that the channel is still in `nodes[1]`'s `outpoint_to_peer` map, as `nodes[1]` // doesn't have `nodes[0]`'s signature for the closing transaction yet. - let nodes_1_lock = nodes[1].node.id_to_peer.lock().unwrap(); + let nodes_1_lock = nodes[1].node.outpoint_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(&funding_output)); } let (_nodes_0_update, closing_signed_node_0) = 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(), &closing_signed_node_0.unwrap()); { - // Assert that the channel has now been removed from both parties `id_to_peer` map once + // Assert that the channel has now been removed from both parties `outpoint_to_peer` map once // they both have everything required to fully close the channel. - assert_eq!(nodes[1].node.id_to_peer.lock().unwrap().len(), 0); + assert_eq!(nodes[1].node.outpoint_to_peer.lock().unwrap().len(), 0); } 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, [nodes[1].node.get_our_node_id()], 1000000); - check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure, [nodes[0].node.get_our_node_id()], 1000000); + check_closed_event!(nodes[0], 1, ClosureReason::LocallyInitiatedCooperativeClosure, [nodes[1].node.get_our_node_id()], 1000000); + check_closed_event!(nodes[1], 1, ClosureReason::CounterpartyInitiatedCooperativeClosure, [nodes[0].node.get_our_node_id()], 1000000); } fn check_not_connected_to_peer_error(res_err: Result, expected_public_key: PublicKey) { @@ -12996,14 +13388,15 @@ mod tests { 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 = ChannelId::temporary_from_entropy_source(&nodes[0].keys_manager); + open_channel_msg.common_fields.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 = ChannelId::temporary_from_entropy_source(&nodes[0].keys_manager); + open_channel_msg.common_fields.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); + open_channel_msg.common_fields.temporary_channel_id); // Further, because all of our channels with nodes[0] are inbound, and none of them funded, // it doesn't count as a "protected" peer, i.e. it counts towards the MAX_NO_CHANNEL_PEERS @@ -13051,11 +13444,11 @@ mod tests { 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 = ChannelId::temporary_from_entropy_source(&nodes[0].keys_manager); + open_channel_msg.common_fields.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, - open_channel_msg.temporary_channel_id); + open_channel_msg.common_fields.temporary_channel_id); // Of course, however, outbound channels are always allowed nodes[1].node.create_channel(last_random_pk, 100_000, 0, 42, None, None).unwrap(); @@ -13091,14 +13484,14 @@ mod tests { 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 = ChannelId::temporary_from_entropy_source(&nodes[0].keys_manager); + open_channel_msg.common_fields.temporary_channel_id = ChannelId::temporary_from_entropy_source(&nodes[0].keys_manager); } // Once we have MAX_UNFUNDED_CHANS_PER_PEER unfunded channels, new inbound channels will be // rejected. nodes[1].node.handle_open_channel(&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); + open_channel_msg.common_fields.temporary_channel_id); // but we can still open an outbound channel. nodes[1].node.create_channel(nodes[0].node.get_our_node_id(), 100_000, 0, 42, None, None).unwrap(); @@ -13107,7 +13500,7 @@ mod tests { // but even with such an outbound channel, additional inbound channels will still fail. 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); + open_channel_msg.common_fields.temporary_channel_id); } #[test] @@ -13143,7 +13536,7 @@ mod tests { _ => panic!("Unexpected event"), } get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, random_pk); - open_channel_msg.temporary_channel_id = ChannelId::temporary_from_entropy_source(&nodes[0].keys_manager); + open_channel_msg.common_fields.temporary_channel_id = ChannelId::temporary_from_entropy_source(&nodes[0].keys_manager); } // If we try to accept a channel from another peer non-0conf it will fail. @@ -13165,7 +13558,7 @@ mod tests { _ => panic!("Unexpected event"), } assert_eq!(get_err_msg(&nodes[1], &last_random_pk).channel_id, - open_channel_msg.temporary_channel_id); + open_channel_msg.common_fields.temporary_channel_id); // ...however if we accept the same channel 0conf it should work just fine. nodes[1].node.handle_open_channel(&last_random_pk, &open_channel_msg); @@ -13188,8 +13581,8 @@ mod tests { let sender_intended_amt_msat = 100; let extra_fee_msat = 10; let hop_data = msgs::InboundOnionPayload::Receive { - amt_msat: 100, - outgoing_cltv_value: 42, + sender_intended_htlc_amt_msat: 100, + cltv_expiry_height: 42, payment_metadata: None, keysend_preimage: None, payment_data: Some(msgs::FinalOnionHopData { @@ -13199,8 +13592,8 @@ mod tests { }; // Check that if the amount we received + the penultimate hop extra fee is less than the sender // intended amount, we fail the payment. - let current_height: u32 = node[0].node.best_block.read().unwrap().height(); - if let Err(crate::ln::channelmanager::InboundOnionErr { err_code, .. }) = + let current_height: u32 = node[0].node.best_block.read().unwrap().height; + if let Err(crate::ln::channelmanager::InboundHTLCErr { err_code, .. }) = create_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), current_height, node[0].node.default_configuration.accept_mpp_keysend) @@ -13210,8 +13603,8 @@ mod tests { // If amt_received + extra_fee is equal to the sender intended amount, we're fine. 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, + sender_intended_htlc_amt_msat: 100, + cltv_expiry_height: 42, payment_metadata: None, keysend_preimage: None, payment_data: Some(msgs::FinalOnionHopData { @@ -13219,7 +13612,7 @@ mod tests { }), custom_tlvs: Vec::new(), }; - let current_height: u32 = node[0].node.best_block.read().unwrap().height(); + let current_height: u32 = node[0].node.best_block.read().unwrap().height; assert!(create_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), current_height, node[0].node.default_configuration.accept_mpp_keysend).is_ok()); @@ -13232,10 +13625,10 @@ mod tests { let node_chanmgr = create_node_chanmgrs(1, &node_cfg, &[None]); let node = create_network(1, &node_cfg, &node_chanmgr); - let current_height: u32 = node[0].node.best_block.read().unwrap().height(); + let current_height: u32 = node[0].node.best_block.read().unwrap().height; let result = create_recv_pending_htlc_info(msgs::InboundOnionPayload::Receive { - amt_msat: 100, - outgoing_cltv_value: 22, + sender_intended_htlc_amt_msat: 100, + cltv_expiry_height: 22, payment_metadata: None, keysend_preimage: None, payment_data: Some(msgs::FinalOnionHopData { @@ -13310,7 +13703,7 @@ mod tests { nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 100_000, 0, 0, None, None).unwrap(); let open_channel_msg = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, nodes[1].node.get_our_node_id()); - assert!(open_channel_msg.channel_type.as_ref().unwrap().supports_anchors_zero_fee_htlc_tx()); + assert!(open_channel_msg.common_fields.channel_type.as_ref().unwrap().supports_anchors_zero_fee_htlc_tx()); nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &open_channel_msg); let events = nodes[1].node.get_and_clear_pending_events(); @@ -13325,7 +13718,7 @@ mod tests { nodes[0].node.handle_error(&nodes[1].node.get_our_node_id(), &error_msg); 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()); + assert!(!open_channel_msg.common_fields.channel_type.unwrap().supports_anchors_zero_fee_htlc_tx()); // Since nodes[1] should not have accepted the channel, it should // not have generated any events. @@ -13478,6 +13871,63 @@ mod tests { } } + #[test] + fn test_malformed_forward_htlcs_ser() { + // Ensure that `HTLCForwardInfo::FailMalformedHTLC`s are (de)serialized properly. + let chanmon_cfg = create_chanmon_cfgs(1); + let node_cfg = create_node_cfgs(1, &chanmon_cfg); + let persister; + let chain_monitor; + let chanmgrs = create_node_chanmgrs(1, &node_cfg, &[None]); + let deserialized_chanmgr; + let mut nodes = create_network(1, &node_cfg, &chanmgrs); + + let dummy_failed_htlc = |htlc_id| { + HTLCForwardInfo::FailHTLC { htlc_id, err_packet: msgs::OnionErrorPacket { data: vec![42] }, } + }; + let dummy_malformed_htlc = |htlc_id| { + HTLCForwardInfo::FailMalformedHTLC { htlc_id, failure_code: 0x4000, sha256_of_onion: [0; 32] } + }; + + let dummy_htlcs_1: Vec = (1..10).map(|htlc_id| { + if htlc_id % 2 == 0 { + dummy_failed_htlc(htlc_id) + } else { + dummy_malformed_htlc(htlc_id) + } + }).collect(); + + let dummy_htlcs_2: Vec = (1..10).map(|htlc_id| { + if htlc_id % 2 == 1 { + dummy_failed_htlc(htlc_id) + } else { + dummy_malformed_htlc(htlc_id) + } + }).collect(); + + + let (scid_1, scid_2) = (42, 43); + let mut forward_htlcs = new_hash_map(); + forward_htlcs.insert(scid_1, dummy_htlcs_1.clone()); + forward_htlcs.insert(scid_2, dummy_htlcs_2.clone()); + + let mut chanmgr_fwd_htlcs = nodes[0].node.forward_htlcs.lock().unwrap(); + *chanmgr_fwd_htlcs = forward_htlcs.clone(); + core::mem::drop(chanmgr_fwd_htlcs); + + reload_node!(nodes[0], nodes[0].node.encode(), &[], persister, chain_monitor, deserialized_chanmgr); + + let mut deserialized_fwd_htlcs = nodes[0].node.forward_htlcs.lock().unwrap(); + for scid in [scid_1, scid_2].iter() { + let deserialized_htlcs = deserialized_fwd_htlcs.remove(scid).unwrap(); + assert_eq!(forward_htlcs.remove(scid).unwrap(), deserialized_htlcs); + } + assert!(deserialized_fwd_htlcs.is_empty()); + core::mem::drop(deserialized_fwd_htlcs); + + expect_pending_htlcs_forwardable!(nodes[0]); + } + // Dual-funding: V2 Channel Establishment Tests #[test] #[cfg(dual_funding)] @@ -13488,7 +13938,7 @@ mod tests { let nodes = create_network(2, &node_cfgs, &node_chanmgrs); // Create a funding input for the new channel along with its previous transaction. - let funding_inputs = vec![create_custom_dual_funding_input(&nodes[0], 100_000)]; + let funding_inputs = vec![create_dual_funding_utxo_with_prev_tx(&nodes[0], 100_000)]; let funding_satoshis = 50_000; // nodes[0] creates a dual-funded channel as initiator. @@ -13527,13 +13977,13 @@ mod tests { nodes[1].node.handle_tx_add_input(&nodes[0].node.get_our_node_id(), &tx_add_input_msg); let tx_complete_msg = get_event_msg!(nodes[1], MessageSendEvent::SendTxComplete, nodes[0].node.get_our_node_id()); - let input_value = tx_add_input_msg.prevtx.0.output[tx_add_input_msg.prevtx_out as usize].value; + let input_value = tx_add_input_msg.prevtx.as_transaction().output[tx_add_input_msg.prevtx_out as usize].value; assert_eq!(input_value, 100_000); + // We have two outputs being added: the P2WSH funding output, and the P2PKH change output + nodes[0].node.handle_tx_complete(&nodes[1].node.get_our_node_id(), &tx_complete_msg); - // First output we send is the change output. let tx_add_output_msg = get_event_msg!(&nodes[0], MessageSendEvent::SendTxAddOutput, nodes[1].node.get_our_node_id()); - assert!(tx_add_output_msg.script.is_v0_p2wpkh()); nodes[1].node.handle_tx_add_output(&nodes[0].node.get_our_node_id(), &tx_add_output_msg); let tx_complete_msg = get_event_msg!(&nodes[1], MessageSendEvent::SendTxComplete, nodes[0].node.get_our_node_id()); @@ -13541,10 +13991,6 @@ mod tests { nodes[0].node.handle_tx_complete(&nodes[1].node.get_our_node_id(), &tx_complete_msg); let tx_add_output_msg = get_event_msg!(&nodes[0], MessageSendEvent::SendTxAddOutput, nodes[1].node.get_our_node_id()); - // Check we get the channel funding output. - assert!(tx_add_output_msg.script.is_v0_p2wsh()); - assert_eq!(tx_add_output_msg.sats, funding_satoshis); - nodes[1].node.handle_tx_add_output(&nodes[0].node.get_our_node_id(), &tx_add_output_msg); let tx_complete_msg = get_event_msg!(nodes[1], MessageSendEvent::SendTxComplete, nodes[0].node.get_our_node_id()); @@ -13654,7 +14100,7 @@ pub mod bench { use bitcoin::blockdata::locktime::absolute::LockTime; use bitcoin::hashes::Hash; use bitcoin::hashes::sha256::Hash as Sha256; - use bitcoin::{Block, Transaction, TxOut}; + use bitcoin::{Transaction, TxOut}; use crate::sync::{Arc, Mutex, RwLock}; @@ -13694,7 +14140,7 @@ pub mod bench { 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 = RwLock::new(test_utils::TestScorer::new()); - let router = test_utils::TestRouter::new(Arc::new(NetworkGraph::new(network, &logger_a)), &scorer); + let router = test_utils::TestRouter::new(Arc::new(NetworkGraph::new(network, &logger_a)), &logger_a, &scorer); let mut config: UserConfig = Default::default(); config.channel_config.max_dust_htlc_exposure = MaxDustHTLCExposure::FeeRateMultiplier(5_000_000 / 253); @@ -13759,7 +14205,7 @@ pub mod bench { assert_eq!(&tx_broadcaster.txn_broadcasted.lock().unwrap()[..], &[tx.clone()]); - let block = create_dummy_block(BestBlock::from_network(network).block_hash(), 42, vec![tx]); + let block = create_dummy_block(BestBlock::from_network(network).block_hash, 42, vec![tx]); Listen::block_connected(&node_a, &block, 1); Listen::block_connected(&node_b, &block, 1); diff --git a/lightning/src/ln/features.rs b/lightning/src/ln/features.rs index 87b8ea26801..0524d0e64ac 100644 --- a/lightning/src/ln/features.rs +++ b/lightning/src/ln/features.rs @@ -41,6 +41,12 @@ //! (see [BOLT-4](https://github.com/lightning/bolts/blob/master/04-onion-routing.md#basic-multi-part-payments) for more information). //! - `Wumbo` - requires/supports that a node create large channels. Called `option_support_large_channel` in the spec. //! (see [BOLT-2](https://github.com/lightning/bolts/blob/master/02-peer-protocol.md#the-open_channel-message) for more information). +//! - `AnchorsZeroFeeHtlcTx` - requires/supports that commitment transactions include anchor outputs +//! and HTLC transactions are pre-signed with zero fee (see +//! [BOLT-3](https://github.com/lightning/bolts/blob/master/03-transactions.md) for more +//! information). +//! - `RouteBlinding` - requires/supports that a node can relay payments over blinded paths +//! (see [BOLT-4](https://github.com/lightning/bolts/blob/master/04-onion-routing.md#route-blinding) for more information). //! - `ShutdownAnySegwit` - requires/supports that future segwit versions are allowed in `shutdown` //! (see [BOLT-2](https://github.com/lightning/bolts/blob/master/02-peer-protocol.md) for more information). //! - `DualFund` - requires/supports V2 channel establishment @@ -63,10 +69,6 @@ //! for more info). //! - `Keysend` - send funds to a node without an invoice //! (see the [`Keysend` feature assignment proposal](https://github.com/lightning/bolts/issues/605#issuecomment-606679798) for more information). -//! - `AnchorsZeroFeeHtlcTx` - requires/supports that commitment transactions include anchor outputs -//! and HTLC transactions are pre-signed with zero fee (see -//! [BOLT-3](https://github.com/lightning/bolts/blob/master/03-transactions.md) for more -//! information). //! //! LDK knows about the following features, but does not support them: //! - `AnchorsNonzeroFeeHtlcTx` - the initial version of anchor outputs, which was later found to be @@ -146,7 +148,7 @@ mod sealed { // Byte 2 BasicMPP | Wumbo | AnchorsNonzeroFeeHtlcTx | AnchorsZeroFeeHtlcTx, // Byte 3 - ShutdownAnySegwit | DualFund | Taproot, + RouteBlinding | ShutdownAnySegwit | DualFund | Taproot, // Byte 4 OnionMessages, // Byte 5 @@ -162,7 +164,7 @@ mod sealed { // Byte 2 BasicMPP | Wumbo | AnchorsNonzeroFeeHtlcTx | AnchorsZeroFeeHtlcTx, // Byte 3 - ShutdownAnySegwit | DualFund | Taproot, + RouteBlinding | ShutdownAnySegwit | DualFund | Taproot, // Byte 4 OnionMessages, // Byte 5 @@ -394,6 +396,9 @@ mod sealed { define_feature!(23, AnchorsZeroFeeHtlcTx, [InitContext, NodeContext, ChannelTypeContext], "Feature flags for `option_anchors_zero_fee_htlc_tx`.", set_anchors_zero_fee_htlc_tx_optional, set_anchors_zero_fee_htlc_tx_required, supports_anchors_zero_fee_htlc_tx, requires_anchors_zero_fee_htlc_tx); + define_feature!(25, RouteBlinding, [InitContext, NodeContext], + "Feature flags for `option_route_blinding`.", set_route_blinding_optional, + set_route_blinding_required, supports_route_blinding, requires_route_blinding); 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); @@ -470,12 +475,24 @@ impl Clone for Features { } impl Hash for Features { fn hash(&self, hasher: &mut H) { - self.flags.hash(hasher); + let mut nonzero_flags = &self.flags[..]; + while nonzero_flags.last() == Some(&0) { + nonzero_flags = &nonzero_flags[..nonzero_flags.len() - 1]; + } + nonzero_flags.hash(hasher); } } impl PartialEq for Features { fn eq(&self, o: &Self) -> bool { - self.flags.eq(&o.flags) + let mut o_iter = o.flags.iter(); + let mut self_iter = self.flags.iter(); + loop { + match (o_iter.next(), self_iter.next()) { + (Some(o), Some(us)) => if o != us { return false }, + (Some(b), None) | (None, Some(b)) => if *b != 0 { return false }, + (None, None) => return true, + } + } } } impl PartialOrd for Features { @@ -921,6 +938,13 @@ impl Features { } } +impl Features { + #[cfg(test)] + pub(crate) fn clear_route_blinding(&mut self) { + ::clear_bits(&mut self.flags); + } +} + #[cfg(test)] impl Features { pub(crate) fn unknown() -> Self { @@ -1059,6 +1083,7 @@ mod tests { init_features.set_basic_mpp_optional(); init_features.set_wumbo_optional(); init_features.set_anchors_zero_fee_htlc_tx_optional(); + init_features.set_route_blinding_optional(); init_features.set_shutdown_any_segwit_optional(); init_features.set_onion_messages_optional(); init_features.set_channel_type_optional(); @@ -1074,8 +1099,8 @@ mod tests { // Check that the flags are as expected: // - option_data_loss_protect (req) // - var_onion_optin (req) | static_remote_key (req) | payment_secret(req) - // - basic_mpp | wumbo | anchors_zero_fee_htlc_tx - // - opt_shutdown_anysegwit + // - basic_mpp | wumbo | option_anchors_zero_fee_htlc_tx + // - option_route_blinding | opt_shutdown_anysegwit // - onion_messages // - option_channel_type | option_scid_alias // - option_zeroconf @@ -1083,7 +1108,7 @@ mod tests { assert_eq!(node_features.flags[0], 0b00000001); assert_eq!(node_features.flags[1], 0b01010001); assert_eq!(node_features.flags[2], 0b10001010); - assert_eq!(node_features.flags[3], 0b00001000); + assert_eq!(node_features.flags[3], 0b00001010); assert_eq!(node_features.flags[4], 0b10000000); assert_eq!(node_features.flags[5], 0b10100000); assert_eq!(node_features.flags[6], 0b00001000); @@ -1215,4 +1240,26 @@ mod tests { assert!(!converted_features.supports_any_optional_bits()); assert!(converted_features.requires_static_remote_key()); } + + #[test] + #[cfg(feature = "std")] + fn test_excess_zero_bytes_ignored() { + // Checks that `Hash` and `PartialEq` ignore excess zero bytes, which may appear due to + // feature conversion or because a peer serialized their feature poorly. + use std::collections::hash_map::DefaultHasher; + use std::hash::{Hash, Hasher}; + + let mut zerod_features = InitFeatures::empty(); + zerod_features.flags = vec![0]; + let empty_features = InitFeatures::empty(); + assert!(empty_features.flags.is_empty()); + + assert_eq!(zerod_features, empty_features); + + let mut zerod_hash = DefaultHasher::new(); + zerod_features.hash(&mut zerod_hash); + let mut empty_hash = DefaultHasher::new(); + empty_features.hash(&mut empty_hash); + assert_eq!(zerod_hash.finish(), empty_hash.finish()); + } } diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index b2da9241147..7189ccd1b7b 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -11,27 +11,29 @@ //! nodes for functional tests. 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::{ClaimedHTLC, ClosureReason, Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider, PathFailure, PaymentPurpose, PaymentFailureReason}; use crate::events::bump_transaction::{BumpTransactionEvent, 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, RouteParameters}; use crate::ln::features::InitFeatures; use crate::ln::msgs; -use crate::ln::msgs::{ChannelMessageHandler,RoutingMessageHandler}; -use crate::util::test_channel_signer::TestChannelSigner; +use crate::ln::msgs::{ChannelMessageHandler, OnionMessageHandler, RoutingMessageHandler}; +use crate::ln::peer_handler::IgnoringMessageHandler; +use crate::onion_message::messenger::OnionMessenger; +use crate::routing::gossip::{P2PGossipSync, NetworkGraph, NetworkUpdate}; +use crate::routing::router::{self, PaymentParameters, Route, RouteParameters}; +use crate::sign::{EntropySource, RandomBytes}; +use crate::util::config::{UserConfig, MaxDustHTLCExposure}; +use crate::util::errors::APIError; +#[cfg(test)] +use crate::util::logger::Logger; use crate::util::scid_utils; +use crate::util::test_channel_signer::TestChannelSigner; use crate::util::test_utils; use crate::util::test_utils::{panicking, TestChainMonitor, TestScorer, TestKeysInterface}; -use crate::util::errors::APIError; -use crate::util::config::{UserConfig, MaxDustHTLCExposure}; use crate::util::ser::{ReadableArgs, Writeable}; -#[cfg(test)] -use crate::util::logger::Logger; use bitcoin::blockdata::block::{Block, Header, Version}; use bitcoin::blockdata::locktime::absolute::LockTime; @@ -45,13 +47,14 @@ use bitcoin::network::constants::Network; use bitcoin::pow::CompactTarget; use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; +use alloc::rc::Rc; +use core::cell::RefCell; +use core::iter::repeat; +use core::mem; +use core::ops::Deref; use crate::io; use crate::prelude::*; -use core::cell::RefCell; -use alloc::rc::Rc; use crate::sync::{Arc, Mutex, LockTestExt, RwLock}; -use core::mem; -use core::iter::repeat; pub const CHAN_CONFIRM_DEPTH: u32 = 10; @@ -251,7 +254,7 @@ pub fn connect_block<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, block: &Block) fn call_claimable_balances<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>) { // Ensure `get_claimable_balances`' self-tests never panic - for funding_outpoint in node.chain_monitor.chain_monitor.list_monitors() { + for (funding_outpoint, _channel_id) in node.chain_monitor.chain_monitor.list_monitors() { node.chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances(); } } @@ -390,6 +393,7 @@ pub struct NodeCfg<'a> { pub tx_broadcaster: &'a test_utils::TestBroadcaster, pub fee_estimator: &'a test_utils::TestFeeEstimator, pub router: test_utils::TestRouter<'a>, + pub message_router: test_utils::TestMessageRouter<'a>, pub chain_monitor: test_utils::TestChainMonitor<'a>, pub keys_manager: &'a test_utils::TestKeysInterface, pub logger: &'a test_utils::TestLogger, @@ -409,6 +413,26 @@ type TestChannelManager<'node_cfg, 'chan_mon_cfg> = ChannelManager< &'chan_mon_cfg test_utils::TestLogger, >; +type TestOnionMessenger<'chan_man, 'node_cfg, 'chan_mon_cfg> = OnionMessenger< + DedicatedEntropy, + &'node_cfg test_utils::TestKeysInterface, + &'chan_mon_cfg test_utils::TestLogger, + &'node_cfg test_utils::TestMessageRouter<'chan_mon_cfg>, + &'chan_man TestChannelManager<'node_cfg, 'chan_mon_cfg>, + IgnoringMessageHandler, +>; + +/// For use with [`OnionMessenger`] otherwise `test_restored_packages_retry` will fail. This is +/// because that test uses older serialized data produced by calling [`EntropySource`] in a specific +/// manner. Using the same [`EntropySource`] with [`OnionMessenger`] would introduce another call, +/// causing the produced data to no longer match. +pub struct DedicatedEntropy(RandomBytes); + +impl Deref for DedicatedEntropy { + type Target = RandomBytes; + fn deref(&self) -> &Self::Target { &self.0 } +} + 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, @@ -417,6 +441,7 @@ pub struct Node<'chan_man, 'node_cfg: 'chan_man, 'chan_mon_cfg: 'node_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 onion_messenger: TestOnionMessenger<'chan_man, '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], @@ -434,6 +459,14 @@ pub struct Node<'chan_man, 'node_cfg: 'chan_man, 'chan_mon_cfg: 'node_cfg> { &'chan_mon_cfg test_utils::TestLogger, >, } + +impl<'a, 'b, 'c> Node<'a, 'b, 'c> { + pub fn init_features(&self, peer_node_id: &PublicKey) -> InitFeatures { + self.override_init_features.borrow().clone() + .unwrap_or_else(|| self.node.init_features() | self.onion_messenger.provided_init_features(peer_node_id)) + } +} + #[cfg(feature = "std")] impl<'a, 'b, 'c> std::panic::UnwindSafe for Node<'a, 'b, 'c> {} #[cfg(feature = "std")] @@ -456,16 +489,38 @@ impl<'a, 'b, 'c> Node<'a, 'b, 'c> { /// `release_commitment_secret` are affected by this setting. #[cfg(test)] pub fn set_channel_signer_available(&self, peer_id: &PublicKey, chan_id: &ChannelId, available: bool) { + use crate::sign::ChannelSigner; + log_debug!(self.logger, "Setting channel signer for {} as available={}", chan_id, available); + let per_peer_state = self.node.per_peer_state.read().unwrap(); let chan_lock = per_peer_state.get(peer_id).unwrap().lock().unwrap(); - let signer = (|| { - match chan_lock.channel_by_id.get(chan_id) { - Some(phase) => phase.context().get_signer(), - None => panic!("Couldn't find a channel with id {}", chan_id), + + let mut channel_keys_id = None; + if let Some(chan) = chan_lock.channel_by_id.get(chan_id).map(|phase| phase.context()) { + chan.get_signer().as_ecdsa().unwrap().set_available(available); + channel_keys_id = Some(chan.channel_keys_id); + } + + let mut monitor = None; + for (funding_txo, channel_id) in self.chain_monitor.chain_monitor.list_monitors() { + if *chan_id == channel_id { + monitor = self.chain_monitor.chain_monitor.get_monitor(funding_txo).ok(); } - })(); - log_debug!(self.logger, "Setting channel signer for {} as available={}", chan_id, available); - signer.as_ecdsa().unwrap().set_available(available); + } + if let Some(monitor) = monitor { + monitor.do_signer_call(|signer| { + channel_keys_id = channel_keys_id.or(Some(signer.inner.channel_keys_id())); + signer.set_available(available) + }); + } + + if available { + self.keys_manager.unavailable_signers.lock().unwrap() + .remove(channel_keys_id.as_ref().unwrap()); + } else { + self.keys_manager.unavailable_signers.lock().unwrap() + .insert(channel_keys_id.unwrap()); + } } } @@ -570,7 +625,7 @@ impl<'a, 'b, 'c> Drop for Node<'a, 'b, 'c> { let feeest = test_utils::TestFeeEstimator { sat_per_kw: Mutex::new(253) }; let mut deserialized_monitors = Vec::new(); { - for outpoint in self.chain_monitor.chain_monitor.list_monitors() { + for (outpoint, _channel_id) 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)>::read( @@ -587,7 +642,7 @@ impl<'a, 'b, 'c> Drop for Node<'a, 'b, 'c> { // Before using all the new monitors to check the watch outpoints, use the full set of // them to ensure we can write and reload our ChannelManager. { - let mut channel_monitors = HashMap::new(); + let mut channel_monitors = new_hash_map(); for monitor in deserialized_monitors.iter_mut() { channel_monitors.insert(monitor.get_funding_txo().0, monitor); } @@ -601,7 +656,7 @@ impl<'a, 'b, 'c> Drop for Node<'a, 'b, 'c> { node_signer: self.keys_manager, signer_provider: self.keys_manager, fee_estimator: &test_utils::TestFeeEstimator { sat_per_kw: Mutex::new(253) }, - router: &test_utils::TestRouter::new(Arc::new(network_graph), &scorer), + router: &test_utils::TestRouter::new(Arc::new(network_graph), &self.logger, &scorer), chain_monitor: self.chain_monitor, tx_broadcaster: &broadcaster, logger: &self.logger, @@ -613,7 +668,8 @@ impl<'a, 'b, 'c> Drop for Node<'a, 'b, 'c> { 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) != Ok(ChannelMonitorUpdateStatus::Completed) { + let funding_outpoint = deserialized_monitor.get_funding_txo().0; + if chain_monitor.watch_channel(funding_outpoint, deserialized_monitor) != Ok(ChannelMonitorUpdateStatus::Completed) { panic!(); } } @@ -1026,7 +1082,7 @@ pub fn _reload_node<'a, 'b, 'c>(node: &'a Node<'a, 'b, 'c>, default_config: User let mut node_read = &chanman_encoded[..]; let (_, node_deserialized) = { - let mut channel_monitors = HashMap::new(); + let mut channel_monitors = new_hash_map(); for monitor in monitors_read.iter_mut() { assert!(channel_monitors.insert(monitor.get_funding_txo().0, monitor).is_none()); } @@ -1046,7 +1102,8 @@ pub fn _reload_node<'a, 'b, 'c>(node: &'a Node<'a, 'b, 'c>, default_config: User assert!(node_read.is_empty()); for monitor in monitors_read.drain(..) { - assert_eq!(node.chain_monitor.watch_channel(monitor.get_funding_txo().0, monitor), + let funding_outpoint = monitor.get_funding_txo().0; + assert_eq!(node.chain_monitor.watch_channel(funding_outpoint, monitor), Ok(ChannelMonitorUpdateStatus::Completed)); check_added_monitors!(node, 1); } @@ -1065,6 +1122,7 @@ macro_rules! reload_node { $new_channelmanager = _reload_node(&$node, $new_config, &chanman_encoded, $monitors_encoded); $node.node = &$new_channelmanager; + $node.onion_messenger.set_offers_handler(&$new_channelmanager); }; ($node: expr, $chanman_encoded: expr, $monitors_encoded: expr, $persister: ident, $new_chain_monitor: ident, $new_channelmanager: ident) => { reload_node!($node, $crate::util::config::UserConfig::default(), $chanman_encoded, $monitors_encoded, $persister, $new_chain_monitor, $new_channelmanager); @@ -1086,7 +1144,7 @@ pub fn create_coinbase_funding_transaction<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, } /// Create a to-be-contributed custom input for a dual-funded transaction -pub fn create_custom_dual_funding_input<'a, 'b, 'c>( +pub fn create_dual_funding_utxo_with_prev_tx<'a, 'b, 'c>( node: &Node<'a, 'b, 'c>, value_satoshis: u64, ) -> (TxIn, Transaction) { let dummy_secret_key = SecretKey::from_slice(&[2; 32]).unwrap(); @@ -1116,7 +1174,7 @@ pub fn create_custom_dual_funding_input_with_pubkey<'a, 'b, 'c>( (funding_input, tx) } -fn internal_create_funding_transaction<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, +pub 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(); @@ -1234,7 +1292,7 @@ pub fn open_zero_conf_channel<'a, 'b, 'c, 'd>(initiator: &'a Node<'b, 'c, 'd>, r }; let accept_channel = get_event_msg!(receiver, MessageSendEvent::SendAcceptChannel, initiator.node.get_our_node_id()); - assert_eq!(accept_channel.minimum_depth, 0); + assert_eq!(accept_channel.common_fields.minimum_depth, 0); initiator.node.handle_accept_channel(&receiver.node.get_our_node_id(), &accept_channel); let (temporary_channel_id, tx, _) = create_funding_transaction(&initiator, &receiver.node.get_our_node_id(), 100_000, 42); @@ -1285,10 +1343,10 @@ pub fn open_zero_conf_channel<'a, 'b, 'c, 'd>(initiator: &'a Node<'b, 'c, 'd>, r (tx, as_channel_ready.channel_id) } -pub fn create_chan_between_nodes_with_value_init<'a, 'b, 'c>(node_a: &Node<'a, 'b, 'c>, node_b: &Node<'a, 'b, 'c>, channel_value: u64, push_msat: u64) -> Transaction { +pub fn exchange_open_accept_chan<'a, 'b, 'c>(node_a: &Node<'a, 'b, 'c>, node_b: &Node<'a, 'b, 'c>, channel_value: u64, push_msat: u64) -> ChannelId { let create_chan_id = node_a.node.create_channel(node_b.node.get_our_node_id(), channel_value, push_msat, 42, None, None).unwrap(); let open_channel_msg = get_event_msg!(node_a, MessageSendEvent::SendOpenChannel, node_b.node.get_our_node_id()); - assert_eq!(open_channel_msg.temporary_channel_id, create_chan_id); + assert_eq!(open_channel_msg.common_fields.temporary_channel_id, create_chan_id); assert_eq!(node_a.node.list_channels().iter().find(|channel| channel.channel_id == create_chan_id).unwrap().user_channel_id, 42); node_b.node.handle_open_channel(&node_a.node.get_our_node_id(), &open_channel_msg); if node_b.node.get_current_default_configuration().manually_accept_inbound_channels { @@ -1301,10 +1359,15 @@ pub fn create_chan_between_nodes_with_value_init<'a, 'b, 'c>(node_a: &Node<'a, ' }; } let accept_channel_msg = get_event_msg!(node_b, MessageSendEvent::SendAcceptChannel, node_a.node.get_our_node_id()); - assert_eq!(accept_channel_msg.temporary_channel_id, create_chan_id); + assert_eq!(accept_channel_msg.common_fields.temporary_channel_id, create_chan_id); node_a.node.handle_accept_channel(&node_b.node.get_our_node_id(), &accept_channel_msg); assert_ne!(node_b.node.list_channels().iter().find(|channel| channel.channel_id == create_chan_id).unwrap().user_channel_id, 0); + create_chan_id +} + +pub fn create_chan_between_nodes_with_value_init<'a, 'b, 'c>(node_a: &Node<'a, 'b, 'c>, node_b: &Node<'a, 'b, 'c>, channel_value: u64, push_msat: u64) -> Transaction { + let create_chan_id = exchange_open_accept_chan(node_a, node_b, channel_value, push_msat); sign_funding_transaction(node_a, node_b, channel_value, create_chan_id) } @@ -1601,6 +1664,22 @@ pub struct ExpectedCloseEvent { pub counterparty_node_id: Option, pub discard_funding: bool, pub reason: Option, + pub channel_funding_txo: Option, + pub user_channel_id: Option, +} + +impl ExpectedCloseEvent { + pub fn from_id_reason(channel_id: ChannelId, discard_funding: bool, reason: ClosureReason) -> Self { + Self { + channel_capacity_sats: None, + channel_id: Some(channel_id), + counterparty_node_id: None, + discard_funding, + reason: Some(reason), + channel_funding_txo: None, + user_channel_id: None, + } + } } /// Check that multiple channel closing events have been issued. @@ -1617,12 +1696,20 @@ pub fn check_closed_events(node: &Node, expected_close_events: &[ExpectedCloseEv reason, counterparty_node_id, channel_capacity_sats, + channel_funding_txo, + user_channel_id, .. } if ( expected_event.channel_id.map(|expected| *channel_id == expected).unwrap_or(true) && expected_event.reason.as_ref().map(|expected| reason == expected).unwrap_or(true) && - expected_event.counterparty_node_id.map(|expected| *counterparty_node_id == Some(expected)).unwrap_or(true) && - expected_event.channel_capacity_sats.map(|expected| *channel_capacity_sats == Some(expected)).unwrap_or(true) + expected_event. + counterparty_node_id.map(|expected| *counterparty_node_id == Some(expected)).unwrap_or(true) && + expected_event.channel_capacity_sats + .map(|expected| *channel_capacity_sats == Some(expected)).unwrap_or(true) && + expected_event.channel_funding_txo + .map(|expected| *channel_funding_txo == Some(expected)).unwrap_or(true) && + expected_event.user_channel_id + .map(|expected| *user_channel_id == expected).unwrap_or(true) ) ))); } @@ -1647,6 +1734,8 @@ pub fn check_closed_event(node: &Node, events_count: usize, expected_reason: Clo counterparty_node_id: Some(*node_id), discard_funding: is_check_discard_funding, reason: Some(expected_reason.clone()), + channel_funding_txo: None, + user_channel_id: None, }).collect::>(); check_closed_events(node, expected_close_events.as_slice()); } @@ -2038,6 +2127,18 @@ pub fn get_route(send_node: &Node, route_params: &RouteParameters) -> Result Result { + 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::find_route( + &send_node.node.get_our_node_id(), route_params, &send_node.network_graph, + Some(&send_node.node.list_usable_channels().iter().collect::>()), + send_node.logger, &scorer, &Default::default(), &random_seed_bytes + ) +} + /// Gets a route from the given sender to the node described in `payment_params`. /// /// Don't use this, use the identically-named function instead. @@ -2189,16 +2290,30 @@ macro_rules! expect_payment_path_successful { } } +/// Returns the total fee earned by this HTLC forward, in msat. pub fn expect_payment_forwarded>( event: Event, node: &H, prev_node: &H, next_node: &H, expected_fee: Option, - upstream_force_closed: bool, downstream_force_closed: bool -) { + expected_extra_fees_msat: Option, upstream_force_closed: bool, + downstream_force_closed: bool, allow_1_msat_fee_overpay: bool, +) -> Option { match event { Event::PaymentForwarded { - fee_earned_msat, prev_channel_id, claim_from_onchain_tx, next_channel_id, - outbound_amount_forwarded_msat: _ + total_fee_earned_msat, prev_channel_id, claim_from_onchain_tx, next_channel_id, + outbound_amount_forwarded_msat: _, skimmed_fee_msat } => { - assert_eq!(fee_earned_msat, expected_fee); + if allow_1_msat_fee_overpay { + // Aggregating fees for blinded paths may result in a rounding error, causing slight + // overpayment in fees. + let actual_fee = total_fee_earned_msat.unwrap(); + let expected_fee = expected_fee.unwrap(); + assert!(actual_fee == expected_fee || actual_fee == expected_fee + 1); + } else { + assert_eq!(total_fee_earned_msat, expected_fee); + } + + // Check that the (knowingly) withheld amount is always less or equal to the expected + // overpaid amount. + assert!(skimmed_fee_msat == expected_extra_fees_msat); 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())); @@ -2209,18 +2324,21 @@ pub fn expect_payment_forwarded>( 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); + total_fee_earned_msat }, _ => panic!("Unexpected event"), } } +#[macro_export] 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 mut events = $node.node.get_and_clear_pending_events(); assert_eq!(events.len(), 1); $crate::ln::functional_test_utils::expect_payment_forwarded( - events.pop().unwrap(), &$node, &$prev_node, &$next_node, $expected_fee, - $upstream_force_closed, $downstream_force_closed); + events.pop().unwrap(), &$node, &$prev_node, &$next_node, $expected_fee, None, + $upstream_force_closed, $downstream_force_closed, false + ); } } @@ -2431,7 +2549,60 @@ fn fail_payment_along_path<'a, 'b, 'c>(expected_path: &[&Node<'a, 'b, 'c>]) { } } -pub fn do_pass_along_path<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_path: &[&Node<'a, 'b, 'c>], recv_value: u64, our_payment_hash: PaymentHash, our_payment_secret: Option, ev: MessageSendEvent, payment_claimable_expected: bool, clear_recipient_events: bool, expected_preimage: Option, is_probe: bool) -> Option { +pub struct PassAlongPathArgs<'a, 'b, 'c, 'd> { + pub origin_node: &'a Node<'b, 'c, 'd>, + pub expected_path: &'a [&'a Node<'b, 'c, 'd>], + pub recv_value: u64, + pub payment_hash: PaymentHash, + pub payment_secret: Option, + pub event: MessageSendEvent, + pub payment_claimable_expected: bool, + pub clear_recipient_events: bool, + pub expected_preimage: Option, + pub is_probe: bool, +} + +impl<'a, 'b, 'c, 'd> PassAlongPathArgs<'a, 'b, 'c, 'd> { + pub fn new( + origin_node: &'a Node<'b, 'c, 'd>, expected_path: &'a [&'a Node<'b, 'c, 'd>], recv_value: u64, + payment_hash: PaymentHash, event: MessageSendEvent, + ) -> Self { + Self { + origin_node, expected_path, recv_value, payment_hash, payment_secret: None, event, + payment_claimable_expected: true, clear_recipient_events: true, expected_preimage: None, + is_probe: false, + } + } + pub fn without_clearing_recipient_events(mut self) -> Self { + self.clear_recipient_events = false; + self + } + pub fn is_probe(mut self) -> Self { + self.payment_claimable_expected = false; + self.is_probe = true; + self + } + pub fn without_claimable_event(mut self) -> Self { + self.payment_claimable_expected = false; + self + } + pub fn with_payment_secret(mut self, payment_secret: PaymentSecret) -> Self { + self.payment_secret = Some(payment_secret); + self + } + pub fn with_payment_preimage(mut self, payment_preimage: PaymentPreimage) -> Self { + self.expected_preimage = Some(payment_preimage); + self + } +} + +pub fn do_pass_along_path<'a, 'b, 'c>(args: PassAlongPathArgs) -> Option { + let PassAlongPathArgs { + origin_node, expected_path, recv_value, payment_hash: our_payment_hash, + payment_secret: our_payment_secret, event: ev, payment_claimable_expected, + clear_recipient_events, expected_preimage, is_probe + } = args; + let mut payment_event = SendEvent::from_event(ev); let mut prev_node = origin_node; let mut event = None; @@ -2498,7 +2669,17 @@ pub fn do_pass_along_path<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_p } pub fn pass_along_path<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_path: &[&Node<'a, 'b, 'c>], recv_value: u64, our_payment_hash: PaymentHash, our_payment_secret: Option, ev: MessageSendEvent, payment_claimable_expected: bool, expected_preimage: Option) -> Option { - do_pass_along_path(origin_node, expected_path, recv_value, our_payment_hash, our_payment_secret, ev, payment_claimable_expected, true, expected_preimage, false) + let mut args = PassAlongPathArgs::new(origin_node, expected_path, recv_value, our_payment_hash, ev); + if !payment_claimable_expected { + args = args.without_claimable_event(); + } + if let Some(payment_secret) = our_payment_secret { + args = args.with_payment_secret(payment_secret); + } + if let Some(payment_preimage) = expected_preimage { + args = args.with_payment_preimage(payment_preimage); + } + do_pass_along_path(args) } pub fn send_probe_along_route<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_route: &[&[&Node<'a, 'b, 'c>]]) { @@ -2510,7 +2691,10 @@ pub fn send_probe_along_route<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expect for path in expected_route.iter() { let ev = remove_first_msg_event_to_node(&path[0].node.get_our_node_id(), &mut events); - do_pass_along_path(origin_node, path, 0, PaymentHash([0_u8; 32]), None, ev, false, false, None, true); + do_pass_along_path(PassAlongPathArgs::new(origin_node, path, 0, PaymentHash([0_u8; 32]), ev) + .is_probe() + .without_clearing_recipient_events()); + let nodes_to_fail_payment: Vec<_> = vec![origin_node].into_iter().chain(path.iter().cloned()).collect(); fail_payment_along_path(nodes_to_fail_payment.as_slice()); @@ -2540,26 +2724,71 @@ pub fn do_claim_payment_along_route<'a, 'b, 'c>( origin_node: &Node<'a, 'b, 'c>, expected_paths: &[&[&Node<'a, 'b, 'c>]], skip_last: bool, our_payment_preimage: PaymentPreimage ) -> u64 { - let extra_fees = vec![0; expected_paths.len()]; - do_claim_payment_along_route_with_extra_penultimate_hop_fees(origin_node, expected_paths, - &extra_fees[..], skip_last, our_payment_preimage) -} - -pub fn do_claim_payment_along_route_with_extra_penultimate_hop_fees<'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 { - assert_eq!(expected_paths.len(), expected_extra_fees.len()); for path in expected_paths.iter() { 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) + pass_claimed_payment_along_route( + ClaimAlongRouteArgs::new(origin_node, expected_paths, our_payment_preimage) + .skip_last(skip_last) + ) } -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 { +pub struct ClaimAlongRouteArgs<'a, 'b, 'c, 'd> { + pub origin_node: &'a Node<'b, 'c, 'd>, + pub expected_paths: &'a [&'a [&'a Node<'b, 'c, 'd>]], + pub expected_extra_fees: Vec, + pub expected_min_htlc_overpay: Vec, + pub skip_last: bool, + pub payment_preimage: PaymentPreimage, + // Allow forwarding nodes to have taken 1 msat more fee than expected based on the downstream + // fulfill amount. + // + // Necessary because our test utils calculate the expected fee for an intermediate node based on + // the amount was claimed in their downstream peer's fulfill, but blinded intermediate nodes + // calculate their fee based on the inbound amount from their upstream peer, causing a difference + // in rounding. + pub allow_1_msat_fee_overpay: bool, +} + +impl<'a, 'b, 'c, 'd> ClaimAlongRouteArgs<'a, 'b, 'c, 'd> { + pub fn new( + origin_node: &'a Node<'b, 'c, 'd>, expected_paths: &'a [&'a [&'a Node<'b, 'c, 'd>]], + payment_preimage: PaymentPreimage, + ) -> Self { + Self { + origin_node, expected_paths, expected_extra_fees: vec![0; expected_paths.len()], + expected_min_htlc_overpay: vec![0; expected_paths.len()], skip_last: false, payment_preimage, + allow_1_msat_fee_overpay: false, + } + } + pub fn skip_last(mut self, skip_last: bool) -> Self { + self.skip_last = skip_last; + self + } + pub fn with_expected_extra_fees(mut self, extra_fees: Vec) -> Self { + self.expected_extra_fees = extra_fees; + self + } + pub fn with_expected_min_htlc_overpay(mut self, extra_fees: Vec) -> Self { + self.expected_min_htlc_overpay = extra_fees; + self + } + pub fn allow_1_msat_fee_overpay(mut self) -> Self { + self.allow_1_msat_fee_overpay = true; + self + } +} + +pub fn pass_claimed_payment_along_route<'a, 'b, 'c, 'd>(args: ClaimAlongRouteArgs) -> u64 { + let ClaimAlongRouteArgs { + origin_node, expected_paths, expected_extra_fees, expected_min_htlc_overpay, skip_last, + payment_preimage: our_payment_preimage, allow_1_msat_fee_overpay, + } = args; let claim_event = expected_paths[0].last().unwrap().node.get_and_clear_pending_events(); assert_eq!(claim_event.len(), 1); + #[allow(unused)] + let mut fwd_amt_msat = 0; match claim_event[0] { Event::PaymentClaimed { purpose: PaymentPurpose::SpontaneousPayment(preimage), @@ -2576,6 +2805,7 @@ pub fn pass_claimed_payment_along_route<'a, 'b, 'c>(origin_node: &Node<'a, 'b, ' assert_eq!(htlcs.len(), expected_paths.len()); // One per path. assert_eq!(htlcs.iter().map(|h| h.value_msat).sum::(), amount_msat); expected_paths.iter().zip(htlcs).for_each(|(path, htlc)| check_claimed_htlc_channel(origin_node, path, htlc)); + fwd_amt_msat = amount_msat; }, Event::PaymentClaimed { purpose: PaymentPurpose::InvoicePayment { .. }, @@ -2588,6 +2818,7 @@ pub fn pass_claimed_payment_along_route<'a, 'b, 'c>(origin_node: &Node<'a, 'b, ' assert_eq!(htlcs.len(), expected_paths.len()); // One per path. assert_eq!(htlcs.iter().map(|h| h.value_msat).sum::(), amount_msat); expected_paths.iter().zip(htlcs).for_each(|(path, htlc)| check_claimed_htlc_channel(origin_node, path, htlc)); + fwd_amt_msat = amount_msat; } _ => panic!(), } @@ -2619,8 +2850,12 @@ pub fn pass_claimed_payment_along_route<'a, 'b, 'c>(origin_node: &Node<'a, 'b, ' per_path_msgs.push(msgs_from_ev!(&events[0])); } else { for expected_path in expected_paths.iter() { - // For MPP payments, we always want the message to the first node in the path. - let ev = remove_first_msg_event_to_node(&expected_path[0].node.get_our_node_id(), &mut events); + // For MPP payments, we want the fulfill message from the payee to the penultimate hop in the + // path. + let penultimate_hop_node_id = expected_path.iter().rev().skip(1).next() + .map(|n| n.node.get_our_node_id()) + .unwrap_or(origin_node.node.get_our_node_id()); + let ev = remove_first_msg_event_to_node(&penultimate_hop_node_id, &mut events); per_path_msgs.push(msgs_from_ev!(&ev)); } } @@ -2644,19 +2879,34 @@ pub fn pass_claimed_payment_along_route<'a, 'b, 'c>(origin_node: &Node<'a, 'b, ' { $node.node.handle_update_fulfill_htlc(&$prev_node.node.get_our_node_id(), &next_msgs.as_ref().unwrap().0); let mut fee = { - let per_peer_state = $node.node.per_peer_state.read().unwrap(); - 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() { - prev_config.forwarding_fee_base_msat - } else { - channel.context().config().forwarding_fee_base_msat - } + let (base_fee, prop_fee) = { + let per_peer_state = $node.node.per_peer_state.read().unwrap(); + 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() { + (prev_config.forwarding_fee_base_msat as u64, + prev_config.forwarding_fee_proportional_millionths as u64) + } else { + (channel.context().config().forwarding_fee_base_msat as u64, + channel.context().config().forwarding_fee_proportional_millionths as u64) + } + }; + ((fwd_amt_msat * prop_fee / 1_000_000) + base_fee) as u32 }; - if $idx == 1 { fee += expected_extra_fees[i]; } - expect_payment_forwarded!(*$node, $next_node, $prev_node, Some(fee as u64), false, false); - expected_total_fee_msat += fee as u64; + + let mut expected_extra_fee = None; + if $idx == 1 { + fee += expected_extra_fees[i]; + fee += expected_min_htlc_overpay[i]; + expected_extra_fee = if expected_extra_fees[i] > 0 { Some(expected_extra_fees[i] as u64) } else { None }; + } + let mut events = $node.node.get_and_clear_pending_events(); + assert_eq!(events.len(), 1); + let actual_fee = expect_payment_forwarded(events.pop().unwrap(), *$node, $next_node, $prev_node, + Some(fee as u64), expected_extra_fee, false, false, allow_1_msat_fee_overpay); + expected_total_fee_msat += actual_fee.unwrap(); + fwd_amt_msat += actual_fee.unwrap(); check_added_monitors!($node, 1); let new_next_msgs = if $new_msgs { let events = $node.node.get_and_clear_pending_msg_events(); @@ -2921,7 +3171,8 @@ pub fn create_node_cfgs_with_persisters<'a>(node_count: usize, chanmon_cfgs: &'a logger: &chanmon_cfgs[i].logger, tx_broadcaster: &chanmon_cfgs[i].tx_broadcaster, fee_estimator: &chanmon_cfgs[i].fee_estimator, - router: test_utils::TestRouter::new(network_graph.clone(), &chanmon_cfgs[i].scorer), + router: test_utils::TestRouter::new(network_graph.clone(), &chanmon_cfgs[i].logger, &chanmon_cfgs[i].scorer), + message_router: test_utils::TestMessageRouter::new(network_graph.clone(), &chanmon_cfgs[i].keys_manager), chain_monitor, keys_manager: &chanmon_cfgs[i].keys_manager, node_seed: seed, @@ -2975,6 +3226,11 @@ pub fn create_network<'a, 'b: 'a, 'c: 'b>(node_count: usize, cfgs: &'b Vec(node_count: usize, cfgs: &'b Vec(node_count: usize, cfgs: &'b Vec(node: &Node<'a, 'b, 'c>, chan: &(msgs::ChannelUpdate, msgs::ChannelUpdate, ChannelId, Transaction), commitment_tx: Option, has_htlc_tx: HTLCType) -> Vec { let mut node_txn = node.tx_broadcaster.txn_broadcasted.lock().unwrap(); - let mut txn_seen = HashSet::new(); + let mut txn_seen = new_hash_set(); node_txn.retain(|tx| txn_seen.insert(tx.txid())); assert!(node_txn.len() >= if commitment_tx.is_some() { 0 } else { 1 } + if has_htlc_tx == HTLCType::NONE { 0 } else { 1 }); @@ -3097,7 +3361,7 @@ pub fn test_revoked_htlc_claim_txn_broadcast<'a, 'b, 'c>(node: &Node<'a, 'b, 'c> pub fn check_preimage_claim<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, prev_txn: &Vec) -> Vec { let mut node_txn = node.tx_broadcaster.txn_broadcasted.lock().unwrap(); - let mut txn_seen = HashSet::new(); + let mut txn_seen = new_hash_set(); node_txn.retain(|tx| txn_seen.insert(tx.txid())); let mut found_prev = false; @@ -3197,7 +3461,7 @@ macro_rules! get_channel_value_stat { macro_rules! get_chan_reestablish_msgs { ($src_node: expr, $dst_node: expr) => { { - let mut announcements = $crate::prelude::HashSet::new(); + let mut announcements = $crate::prelude::new_hash_set(); let mut res = Vec::with_capacity(1); for msg in $src_node.node.get_and_clear_pending_msg_events() { if let MessageSendEvent::SendChannelReestablish { ref node_id, ref msg } = msg { diff --git a/lightning/src/ln/functional_tests.rs b/lightning/src/ln/functional_tests.rs index 2594068f671..4de8edaaf45 100644 --- a/lightning/src/ln/functional_tests.rs +++ b/lightning/src/ln/functional_tests.rs @@ -106,22 +106,22 @@ fn test_insane_channel_opens() { use crate::ln::channelmanager::MAX_LOCAL_BREAKDOWN_TIMEOUT; // Test all mutations that would make the channel open message insane - insane_open_helper(format!("Per our config, funding must be at most {}. It was {}", TOTAL_BITCOIN_SUPPLY_SATOSHIS + 1, TOTAL_BITCOIN_SUPPLY_SATOSHIS + 2).as_str(), |mut msg| { msg.funding_satoshis = TOTAL_BITCOIN_SUPPLY_SATOSHIS + 2; msg }); - insane_open_helper(format!("Funding must be smaller than the total bitcoin supply. It was {}", TOTAL_BITCOIN_SUPPLY_SATOSHIS).as_str(), |mut msg| { msg.funding_satoshis = TOTAL_BITCOIN_SUPPLY_SATOSHIS; msg }); + insane_open_helper(format!("Per our config, funding must be at most {}. It was {}", TOTAL_BITCOIN_SUPPLY_SATOSHIS + 1, TOTAL_BITCOIN_SUPPLY_SATOSHIS + 2).as_str(), |mut msg| { msg.common_fields.funding_satoshis = TOTAL_BITCOIN_SUPPLY_SATOSHIS + 2; msg }); + insane_open_helper(format!("Funding must be smaller than the total bitcoin supply. It was {}", TOTAL_BITCOIN_SUPPLY_SATOSHIS).as_str(), |mut msg| { msg.common_fields.funding_satoshis = TOTAL_BITCOIN_SUPPLY_SATOSHIS; msg }); - insane_open_helper("Bogus channel_reserve_satoshis", |mut msg| { msg.channel_reserve_satoshis = msg.funding_satoshis + 1; msg }); + insane_open_helper("Bogus channel_reserve_satoshis", |mut msg| { msg.channel_reserve_satoshis = msg.common_fields.funding_satoshis + 1; msg }); - insane_open_helper(r"push_msat \d+ was larger than channel amount minus reserve \(\d+\)", |mut msg| { msg.push_msat = (msg.funding_satoshis - msg.channel_reserve_satoshis) * 1000 + 1; msg }); + insane_open_helper(r"push_msat \d+ was larger than channel amount minus reserve \(\d+\)", |mut msg| { msg.push_msat = (msg.common_fields.funding_satoshis - msg.channel_reserve_satoshis) * 1000 + 1; msg }); - insane_open_helper("Peer never wants payout outputs?", |mut msg| { msg.dust_limit_satoshis = msg.funding_satoshis + 1 ; msg }); + insane_open_helper("Peer never wants payout outputs?", |mut msg| { msg.common_fields.dust_limit_satoshis = msg.common_fields.funding_satoshis + 1 ; msg }); - insane_open_helper(r"Minimum htlc value \(\d+\) was larger than full channel value \(\d+\)", |mut msg| { msg.htlc_minimum_msat = (msg.funding_satoshis - msg.channel_reserve_satoshis) * 1000; msg }); + insane_open_helper(r"Minimum htlc value \(\d+\) was larger than full channel value \(\d+\)", |mut msg| { msg.common_fields.htlc_minimum_msat = (msg.common_fields.funding_satoshis - msg.channel_reserve_satoshis) * 1000; msg }); - insane_open_helper("They wanted our payments to be delayed by a needlessly long period", |mut msg| { msg.to_self_delay = MAX_LOCAL_BREAKDOWN_TIMEOUT + 1; msg }); + insane_open_helper("They wanted our payments to be delayed by a needlessly long period", |mut msg| { msg.common_fields.to_self_delay = MAX_LOCAL_BREAKDOWN_TIMEOUT + 1; msg }); - insane_open_helper("0 max_accepted_htlcs makes for a useless channel", |mut msg| { msg.max_accepted_htlcs = 0; msg }); + insane_open_helper("0 max_accepted_htlcs makes for a useless channel", |mut msg| { msg.common_fields.max_accepted_htlcs = 0; msg }); - insane_open_helper("max_accepted_htlcs was 484. It must not be larger than 483", |mut msg| { msg.max_accepted_htlcs = 484; msg }); + insane_open_helper("max_accepted_htlcs was 484. It must not be larger than 483", |mut msg| { msg.common_fields.max_accepted_htlcs = 484; msg }); } #[test] @@ -165,7 +165,7 @@ fn do_test_counterparty_no_reserve(send_from_initiator: bool) { let mut open_channel_message = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, nodes[1].node.get_our_node_id()); if !send_from_initiator { open_channel_message.channel_reserve_satoshis = 0; - open_channel_message.max_htlc_value_in_flight_msat = 100_000_000; + open_channel_message.common_fields.max_htlc_value_in_flight_msat = 100_000_000; } nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &open_channel_message); @@ -173,7 +173,7 @@ fn do_test_counterparty_no_reserve(send_from_initiator: bool) { let mut accept_channel_message = get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, nodes[0].node.get_our_node_id()); if send_from_initiator { accept_channel_message.channel_reserve_satoshis = 0; - accept_channel_message.max_htlc_value_in_flight_msat = 100_000_000; + accept_channel_message.common_fields.max_htlc_value_in_flight_msat = 100_000_000; } nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), &accept_channel_message); { @@ -767,7 +767,7 @@ fn test_update_fee_that_funder_cannot_afford() { //check to see if the funder, who sent the update_fee request, can afford the new fee (funder_balance >= fee+channel_reserve) //Should produce and error. nodes[1].node.handle_commitment_signed(&nodes[0].node.get_our_node_id(), &commit_signed_msg); - nodes[1].logger.assert_log("lightning::ln::channelmanager", "Funding remote cannot afford proposed new fee".to_string(), 1); + nodes[1].logger.assert_log_contains("lightning::ln::channelmanager", "Funding remote cannot afford proposed new fee", 3); 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") }, @@ -870,8 +870,8 @@ fn test_update_fee_with_fundee_update_add_htlc() { 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, [nodes[1].node.get_our_node_id()], 100000); - check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure, [nodes[0].node.get_our_node_id()], 100000); + check_closed_event!(nodes[0], 1, ClosureReason::CounterpartyInitiatedCooperativeClosure, [nodes[1].node.get_our_node_id()], 100000); + check_closed_event!(nodes[1], 1, ClosureReason::LocallyInitiatedCooperativeClosure, [nodes[0].node.get_our_node_id()], 100000); } #[test] @@ -984,8 +984,8 @@ fn test_update_fee() { 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, [nodes[1].node.get_our_node_id()], 100000); - check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure, [nodes[0].node.get_our_node_id()], 100000); + check_closed_event!(nodes[0], 1, ClosureReason::CounterpartyInitiatedCooperativeClosure, [nodes[1].node.get_our_node_id()], 100000); + check_closed_event!(nodes[1], 1, ClosureReason::LocallyInitiatedCooperativeClosure, [nodes[0].node.get_our_node_id()], 100000); } #[test] @@ -1103,17 +1103,17 @@ fn fake_network_test() { // 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, [nodes[1].node.get_our_node_id()], 100000); - check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure, [nodes[0].node.get_our_node_id()], 100000); + check_closed_event!(nodes[0], 1, ClosureReason::CounterpartyInitiatedCooperativeClosure, [nodes[1].node.get_our_node_id()], 100000); + check_closed_event!(nodes[1], 1, ClosureReason::LocallyInitiatedCooperativeClosure, [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, [nodes[2].node.get_our_node_id()], 100000); - check_closed_event!(nodes[2], 1, ClosureReason::CooperativeClosure, [nodes[1].node.get_our_node_id()], 100000); + check_closed_event!(nodes[1], 1, ClosureReason::LocallyInitiatedCooperativeClosure, [nodes[2].node.get_our_node_id()], 100000); + check_closed_event!(nodes[2], 1, ClosureReason::CounterpartyInitiatedCooperativeClosure, [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, [nodes[3].node.get_our_node_id()], 100000); - check_closed_event!(nodes[3], 1, ClosureReason::CooperativeClosure, [nodes[2].node.get_our_node_id()], 100000); + check_closed_event!(nodes[2], 1, ClosureReason::CounterpartyInitiatedCooperativeClosure, [nodes[3].node.get_our_node_id()], 100000); + check_closed_event!(nodes[3], 1, ClosureReason::LocallyInitiatedCooperativeClosure, [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, [nodes[3].node.get_our_node_id()], 100000); - check_closed_event!(nodes[3], 1, ClosureReason::CooperativeClosure, [nodes[1].node.get_our_node_id()], 100000); + check_closed_event!(nodes[1], 1, ClosureReason::LocallyInitiatedCooperativeClosure, [nodes[3].node.get_our_node_id()], 100000); + check_closed_event!(nodes[3], 1, ClosureReason::CounterpartyInitiatedCooperativeClosure, [nodes[1].node.get_our_node_id()], 100000); } #[test] @@ -1400,7 +1400,7 @@ fn test_fee_spike_violation_fails_htlc() { let secp_ctx = Secp256k1::new(); let session_priv = SecretKey::from_slice(&[42; 32]).expect("RNG is bad!"); - let cur_height = nodes[1].node.best_block.read().unwrap().height() + 1; + let cur_height = nodes[1].node.best_block.read().unwrap().height + 1; let onion_keys = onion_utils::construct_onion_keys(&secp_ctx, &route.paths[0], &session_priv).unwrap(); let (onion_payloads, htlc_msat, htlc_cltv) = onion_utils::build_onion_payloads(&route.paths[0], @@ -1598,7 +1598,7 @@ fn test_chan_reserve_violation_inbound_htlc_outbound_channel() { // Need to manually create the update_add_htlc message to go around the channel reserve check in send_htlc() let secp_ctx = Secp256k1::new(); let session_priv = SecretKey::from_slice(&[42; 32]).unwrap(); - let cur_height = nodes[1].node.best_block.read().unwrap().height() + 1; + let cur_height = nodes[1].node.best_block.read().unwrap().height + 1; let onion_keys = onion_utils::construct_onion_keys(&secp_ctx, &route.paths[0], &session_priv).unwrap(); let (onion_payloads, htlc_msat, htlc_cltv) = onion_utils::build_onion_payloads(&route.paths[0], 700_000, RecipientOnionFields::secret_only(payment_secret), cur_height, &None).unwrap(); @@ -1616,7 +1616,7 @@ fn test_chan_reserve_violation_inbound_htlc_outbound_channel() { nodes[0].node.handle_update_add_htlc(&nodes[1].node.get_our_node_id(), &msg); // Check that the payment failed and the channel is closed in response to the malicious UpdateAdd. - nodes[0].logger.assert_log("lightning::ln::channelmanager", "Cannot accept HTLC that would put our balance under counterparty-announced channel reserve value".to_string(), 1); + nodes[0].logger.assert_log_contains("lightning::ln::channelmanager", "Cannot accept HTLC that would put our balance under counterparty-announced channel reserve value", 3); assert_eq!(nodes[0].node.list_channels().len(), 0); 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"); @@ -1777,7 +1777,7 @@ fn test_chan_reserve_violation_inbound_htlc_inbound_chan() { // Need to manually create the update_add_htlc message to go around the channel reserve check in send_htlc() let secp_ctx = Secp256k1::new(); let session_priv = SecretKey::from_slice(&[42; 32]).unwrap(); - let cur_height = nodes[0].node.best_block.read().unwrap().height() + 1; + let cur_height = nodes[0].node.best_block.read().unwrap().height + 1; let onion_keys = onion_utils::construct_onion_keys(&secp_ctx, &route_2.paths[0], &session_priv).unwrap(); let (onion_payloads, htlc_msat, htlc_cltv) = onion_utils::build_onion_payloads( &route_2.paths[0], recv_value_2, RecipientOnionFields::spontaneous_empty(), cur_height, &None).unwrap(); @@ -1795,7 +1795,7 @@ fn test_chan_reserve_violation_inbound_htlc_inbound_chan() { nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &msg); // Check that the payment failed and the channel is closed in response to the malicious UpdateAdd. - nodes[1].logger.assert_log("lightning::ln::channelmanager", "Remote HTLC add would put them under remote reserve value".to_string(), 1); + nodes[1].logger.assert_log_contains("lightning::ln::channelmanager", "Remote HTLC add would put them under remote reserve value", 3); assert_eq!(nodes[1].node.list_channels().len(), 1); 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"); @@ -2272,9 +2272,15 @@ fn channel_monitor_network_test() { nodes[1].node.force_close_broadcasting_latest_txn(&chan_1.2, &nodes[0].node.get_our_node_id()).unwrap(); check_added_monitors!(nodes[1], 1); check_closed_broadcast!(nodes[1], true); + check_closed_event!(nodes[1], 1, ClosureReason::HolderForceClosed, [nodes[0].node.get_our_node_id()], 100000); { let mut node_txn = test_txn_broadcast(&nodes[1], &chan_1, None, HTLCType::NONE); assert_eq!(node_txn.len(), 1); + mine_transaction(&nodes[1], &node_txn[0]); + if nodes[1].connect_style.borrow().updates_best_block_first() { + let _ = nodes[1].tx_broadcaster.txn_broadcast(); + } + mine_transaction(&nodes[0], &node_txn[0]); check_added_monitors!(nodes[0], 1); test_txn_broadcast(&nodes[0], &chan_1, Some(node_txn[0].clone()), HTLCType::NONE); @@ -2283,7 +2289,6 @@ fn channel_monitor_network_test() { 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, [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); @@ -2883,8 +2888,10 @@ fn test_htlc_on_chain_success() { } let chan_id = Some(chan_1.2); match forwarded_events[1] { - Event::PaymentForwarded { fee_earned_msat, prev_channel_id, claim_from_onchain_tx, next_channel_id, outbound_amount_forwarded_msat } => { - assert_eq!(fee_earned_msat, Some(1000)); + Event::PaymentForwarded { total_fee_earned_msat, prev_channel_id, claim_from_onchain_tx, + next_channel_id, outbound_amount_forwarded_msat, .. + } => { + assert_eq!(total_fee_earned_msat, Some(1000)); assert_eq!(prev_channel_id, chan_id); assert_eq!(claim_from_onchain_tx, true); assert_eq!(next_channel_id, Some(chan_2.2)); @@ -2893,8 +2900,10 @@ fn test_htlc_on_chain_success() { _ => panic!() } match forwarded_events[2] { - Event::PaymentForwarded { fee_earned_msat, prev_channel_id, claim_from_onchain_tx, next_channel_id, outbound_amount_forwarded_msat } => { - assert_eq!(fee_earned_msat, Some(1000)); + Event::PaymentForwarded { total_fee_earned_msat, prev_channel_id, claim_from_onchain_tx, + next_channel_id, outbound_amount_forwarded_msat, .. + } => { + assert_eq!(total_fee_earned_msat, Some(1000)); assert_eq!(prev_channel_id, chan_id); assert_eq!(claim_from_onchain_tx, true); assert_eq!(next_channel_id, Some(chan_2.2)); @@ -3313,7 +3322,7 @@ fn do_test_commitment_revoked_fail_backward_exhaustive(deliver_bs_raa: bool, use // block connection just like the !deliver_bs_raa case } - let mut failed_htlcs = HashSet::new(); + let mut failed_htlcs = new_hash_set(); assert!(nodes[1].node.get_and_clear_pending_events().is_empty()); mine_transaction(&nodes[1], &revoked_local_txn[0]); @@ -3322,22 +3331,18 @@ fn do_test_commitment_revoked_fail_backward_exhaustive(deliver_bs_raa: bool, use let events = nodes[1].node.get_and_clear_pending_events(); assert_eq!(events.len(), if deliver_bs_raa { 3 + nodes.len() - 1 } else { 4 + nodes.len() }); - match events[0] { - Event::ChannelClosed { reason: ClosureReason::CommitmentTxConfirmed, .. } => { }, - _ => panic!("Unexepected event"), - } - match events[1] { - Event::PaymentPathFailed { ref payment_hash, .. } => { - assert_eq!(*payment_hash, fourth_payment_hash); - }, - _ => panic!("Unexpected event"), - } - match events[2] { - Event::PaymentFailed { ref payment_hash, .. } => { - assert_eq!(*payment_hash, fourth_payment_hash); - }, - _ => panic!("Unexpected event"), - } + assert!(events.iter().any(|ev| matches!( + ev, + Event::ChannelClosed { reason: ClosureReason::CommitmentTxConfirmed, .. } + ))); + assert!(events.iter().any(|ev| matches!( + ev, + Event::PaymentPathFailed { ref payment_hash, .. } if *payment_hash == fourth_payment_hash + ))); + assert!(events.iter().any(|ev| matches!( + ev, + Event::PaymentFailed { ref payment_hash, .. } if *payment_hash == fourth_payment_hash + ))); nodes[1].node.process_pending_htlc_forwards(); check_added_monitors!(nodes[1], 1); @@ -3497,7 +3502,7 @@ fn fail_backward_pending_htlc_upon_channel_failure() { let secp_ctx = Secp256k1::new(); let session_priv = SecretKey::from_slice(&[42; 32]).unwrap(); - let current_height = nodes[1].node.best_block.read().unwrap().height() + 1; + let current_height = nodes[1].node.best_block.read().unwrap().height + 1; let (onion_payloads, _amount_msat, cltv_expiry) = onion_utils::build_onion_payloads( &route.paths[0], 50_000, RecipientOnionFields::secret_only(payment_secret), current_height, &None).unwrap(); let onion_keys = onion_utils::construct_onion_keys(&secp_ctx, &route.paths[0], &session_priv).unwrap(); @@ -3555,7 +3560,7 @@ fn test_htlc_ignore_latest_remote_commitment() { // connect_style. return; } - create_announced_chan_between_nodes(&nodes, 0, 1); + let funding_tx = create_announced_chan_between_nodes(&nodes, 0, 1).3; route_payment(&nodes[0], &[&nodes[1]], 10000000); nodes[0].node.force_close_broadcasting_latest_txn(&nodes[0].node.list_channels()[0].channel_id, &nodes[1].node.get_our_node_id()).unwrap(); @@ -3564,11 +3569,12 @@ fn test_htlc_ignore_latest_remote_commitment() { check_added_monitors!(nodes[0], 1); 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); - assert_eq!(node_txn[0].txid(), node_txn[1].txid()); + let node_txn = nodes[0].tx_broadcaster.unique_txn_broadcast(); + assert_eq!(node_txn.len(), 2); + check_spends!(node_txn[0], funding_tx); + check_spends!(node_txn[1], node_txn[0]); - let block = create_dummy_block(nodes[1].best_block_hash(), 42, vec![node_txn[0].clone(), node_txn[1].clone()]); + let block = create_dummy_block(nodes[1].best_block_hash(), 42, vec![node_txn[0].clone()]); connect_block(&nodes[1], &block); check_closed_broadcast!(nodes[1], true); check_added_monitors!(nodes[1], 1); @@ -3625,7 +3631,7 @@ fn test_force_close_fail_back() { check_closed_broadcast!(nodes[2], true); check_added_monitors!(nodes[2], 1); check_closed_event!(nodes[2], 1, ClosureReason::HolderForceClosed, [nodes[1].node.get_our_node_id()], 100000); - let tx = { + let commitment_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 // have a use for it unless nodes[2] learns the preimage somehow, the funds will go @@ -3634,7 +3640,7 @@ fn test_force_close_fail_back() { node_txn.remove(0) }; - mine_transaction(&nodes[1], &tx); + mine_transaction(&nodes[1], &commitment_tx); // Note no UpdateHTLCs event here from nodes[1] to nodes[0]! check_closed_broadcast!(nodes[1], true); @@ -3646,15 +3652,16 @@ fn test_force_close_fail_back() { get_monitor!(nodes[2], payment_event.commitment_msg.channel_id) .provide_payment_preimage(&our_payment_hash, &our_payment_preimage, &node_cfgs[2].tx_broadcaster, &LowerBoundedFeeEstimator::new(node_cfgs[2].fee_estimator), &node_cfgs[2].logger); } - mine_transaction(&nodes[2], &tx); - let node_txn = nodes[2].tx_broadcaster.txn_broadcasted.lock().unwrap(); - assert_eq!(node_txn.len(), 1); - assert_eq!(node_txn[0].input.len(), 1); - assert_eq!(node_txn[0].input[0].previous_output.txid, tx.txid()); - assert_eq!(node_txn[0].lock_time, LockTime::ZERO); // Must be an HTLC-Success - assert_eq!(node_txn[0].input[0].witness.len(), 5); // Must be an HTLC-Success + mine_transaction(&nodes[2], &commitment_tx); + let mut node_txn = nodes[2].tx_broadcaster.txn_broadcast(); + assert_eq!(node_txn.len(), if nodes[2].connect_style.borrow().updates_best_block_first() { 2 } else { 1 }); + let htlc_tx = node_txn.pop().unwrap(); + assert_eq!(htlc_tx.input.len(), 1); + assert_eq!(htlc_tx.input[0].previous_output.txid, commitment_tx.txid()); + assert_eq!(htlc_tx.lock_time, LockTime::ZERO); // Must be an HTLC-Success + assert_eq!(htlc_tx.input[0].witness.len(), 5); // Must be an HTLC-Success - check_spends!(node_txn[0], tx); + check_spends!(htlc_tx, commitment_tx); } #[test] @@ -3691,7 +3698,7 @@ fn test_dup_events_on_peer_disconnect() { #[test] fn test_peer_disconnected_before_funding_broadcasted() { // Test that channels are closed with `ClosureReason::DisconnectedPeer` if the peer disconnects - // before the funding transaction has been broadcasted. + // before the funding transaction has been broadcasted, and doesn't reconnect back within time. 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]); @@ -3720,12 +3727,19 @@ fn test_peer_disconnected_before_funding_broadcasted() { assert_eq!(nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().len(), 0); } - // Ensure that the channel is closed with `ClosureReason::DisconnectedPeer` when the peers are - // disconnected before the funding transaction was broadcasted. + // The peers disconnect before the funding is broadcasted. 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], 2, ClosureReason::DisconnectedPeer, true + // The time for peers to reconnect expires. + for _ in 0..UNFUNDED_CHANNEL_AGE_LIMIT_TICKS { + nodes[0].node.timer_tick_occurred(); + } + + // Ensure that the channel is closed with `ClosureReason::HolderForceClosed` + // when the peers are disconnected and do not reconnect before the funding + // transaction is broadcasted. + check_closed_event!(&nodes[0], 2, ClosureReason::HolderForceClosed, 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); @@ -4908,8 +4922,10 @@ fn test_onchain_to_onchain_claim() { _ => panic!("Unexpected event"), } match events[1] { - Event::PaymentForwarded { fee_earned_msat, prev_channel_id, claim_from_onchain_tx, next_channel_id, outbound_amount_forwarded_msat } => { - assert_eq!(fee_earned_msat, Some(1000)); + Event::PaymentForwarded { total_fee_earned_msat, prev_channel_id, claim_from_onchain_tx, + next_channel_id, outbound_amount_forwarded_msat, .. + } => { + assert_eq!(total_fee_earned_msat, Some(1000)); assert_eq!(prev_channel_id, Some(chan_1.2)); assert_eq!(claim_from_onchain_tx, true); assert_eq!(next_channel_id, Some(chan_2.2)); @@ -5392,11 +5408,11 @@ fn do_test_fail_backwards_unrevoked_remote_announce(deliver_last_raa: bool, anno let as_events = nodes[0].node.get_and_clear_pending_events(); assert_eq!(as_events.len(), if announce_latest { 10 } else { 6 }); - let mut as_failds = HashSet::new(); + let mut as_faileds = new_hash_set(); let mut as_updates = 0; for event in as_events.iter() { if let &Event::PaymentPathFailed { ref payment_hash, ref payment_failed_permanently, ref failure, .. } = event { - assert!(as_failds.insert(*payment_hash)); + assert!(as_faileds.insert(*payment_hash)); if *payment_hash != payment_hash_2 { assert_eq!(*payment_failed_permanently, deliver_last_raa); } else { @@ -5408,21 +5424,21 @@ fn do_test_fail_backwards_unrevoked_remote_announce(deliver_last_raa: bool, anno } else if let &Event::PaymentFailed { .. } = event { } else { panic!("Unexpected event"); } } - assert!(as_failds.contains(&payment_hash_1)); - assert!(as_failds.contains(&payment_hash_2)); + assert!(as_faileds.contains(&payment_hash_1)); + assert!(as_faileds.contains(&payment_hash_2)); if announce_latest { - assert!(as_failds.contains(&payment_hash_3)); - assert!(as_failds.contains(&payment_hash_5)); + assert!(as_faileds.contains(&payment_hash_3)); + assert!(as_faileds.contains(&payment_hash_5)); } - assert!(as_failds.contains(&payment_hash_6)); + assert!(as_faileds.contains(&payment_hash_6)); let bs_events = nodes[1].node.get_and_clear_pending_events(); assert_eq!(bs_events.len(), if announce_latest { 8 } else { 6 }); - let mut bs_failds = HashSet::new(); + let mut bs_faileds = new_hash_set(); let mut bs_updates = 0; for event in bs_events.iter() { if let &Event::PaymentPathFailed { ref payment_hash, ref payment_failed_permanently, ref failure, .. } = event { - assert!(bs_failds.insert(*payment_hash)); + assert!(bs_faileds.insert(*payment_hash)); if *payment_hash != payment_hash_1 && *payment_hash != payment_hash_5 { assert_eq!(*payment_failed_permanently, deliver_last_raa); } else { @@ -5434,12 +5450,12 @@ fn do_test_fail_backwards_unrevoked_remote_announce(deliver_last_raa: bool, anno } else if let &Event::PaymentFailed { .. } = event { } else { panic!("Unexpected event"); } } - assert!(bs_failds.contains(&payment_hash_1)); - assert!(bs_failds.contains(&payment_hash_2)); + assert!(bs_faileds.contains(&payment_hash_1)); + assert!(bs_faileds.contains(&payment_hash_2)); if announce_latest { - assert!(bs_failds.contains(&payment_hash_4)); + assert!(bs_faileds.contains(&payment_hash_4)); } - assert!(bs_failds.contains(&payment_hash_5)); + assert!(bs_faileds.contains(&payment_hash_5)); // For each HTLC which was not failed-back by normal process (ie deliver_last_raa), we should // get a NetworkUpdate. A should have gotten 4 HTLCs which were failed-back due to @@ -5529,8 +5545,9 @@ fn test_key_derivation_params() { 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 = 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 router = test_utils::TestRouter::new(network_graph.clone(), &chanmon_cfgs[0].logger, &scorer); + let message_router = test_utils::TestMessageRouter::new(network_graph.clone(), &keys_manager); + 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, message_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); node_cfgs.remove(0); node_cfgs.insert(0, node); @@ -5614,7 +5631,7 @@ fn test_static_output_closing_tx() { 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, [nodes[1].node.get_our_node_id()], 100000); + check_closed_event!(nodes[0], 1, ClosureReason::CounterpartyInitiatedCooperativeClosure, [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); @@ -5622,7 +5639,7 @@ fn test_static_output_closing_tx() { check_spends!(spend_txn[0], closing_tx); mine_transaction(&nodes[1], &closing_tx); - check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure, [nodes[0].node.get_our_node_id()], 100000); + check_closed_event!(nodes[1], 1, ClosureReason::LocallyInitiatedCooperativeClosure, [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); @@ -5823,26 +5840,26 @@ fn bolt2_open_channel_sending_node_checks_part2() { let push_msat=10001; assert!(nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), channel_value_satoshis, push_msat, 42, None, None).is_ok()); //Create a valid channel let node0_to_1_send_open_channel = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, nodes[1].node.get_our_node_id()); - assert!(node0_to_1_send_open_channel.channel_reserve_satoshis>=node0_to_1_send_open_channel.dust_limit_satoshis); + assert!(node0_to_1_send_open_channel.channel_reserve_satoshis>=node0_to_1_send_open_channel.common_fields.dust_limit_satoshis); // BOLT #2 spec: Sending node must set undefined bits in channel_flags to 0 // Only the least-significant bit of channel_flags is currently defined resulting in channel_flags only having one of two possible states 0 or 1 - assert!(node0_to_1_send_open_channel.channel_flags<=1); + assert!(node0_to_1_send_open_channel.common_fields.channel_flags<=1); // BOLT #2 spec: Sending node should set to_self_delay sufficient to ensure the sender can irreversibly spend a commitment transaction output, in case of misbehaviour by the receiver. assert!(BREAKDOWN_TIMEOUT>0); - assert!(node0_to_1_send_open_channel.to_self_delay==BREAKDOWN_TIMEOUT); + assert!(node0_to_1_send_open_channel.common_fields.to_self_delay==BREAKDOWN_TIMEOUT); // BOLT #2 spec: Sending node must ensure the chain_hash value identifies the chain it wishes to open the channel within. let chain_hash = ChainHash::using_genesis_block(Network::Testnet); - assert_eq!(node0_to_1_send_open_channel.chain_hash, chain_hash); + assert_eq!(node0_to_1_send_open_channel.common_fields.chain_hash, chain_hash); // BOLT #2 spec: Sending node must set funding_pubkey, revocation_basepoint, htlc_basepoint, payment_basepoint, and delayed_payment_basepoint to valid DER-encoded, compressed, secp256k1 pubkeys. - assert!(PublicKey::from_slice(&node0_to_1_send_open_channel.funding_pubkey.serialize()).is_ok()); - assert!(PublicKey::from_slice(&node0_to_1_send_open_channel.revocation_basepoint.serialize()).is_ok()); - assert!(PublicKey::from_slice(&node0_to_1_send_open_channel.htlc_basepoint.serialize()).is_ok()); - assert!(PublicKey::from_slice(&node0_to_1_send_open_channel.payment_point.serialize()).is_ok()); - assert!(PublicKey::from_slice(&node0_to_1_send_open_channel.delayed_payment_basepoint.serialize()).is_ok()); + assert!(PublicKey::from_slice(&node0_to_1_send_open_channel.common_fields.funding_pubkey.serialize()).is_ok()); + assert!(PublicKey::from_slice(&node0_to_1_send_open_channel.common_fields.revocation_basepoint.serialize()).is_ok()); + assert!(PublicKey::from_slice(&node0_to_1_send_open_channel.common_fields.htlc_basepoint.serialize()).is_ok()); + assert!(PublicKey::from_slice(&node0_to_1_send_open_channel.common_fields.payment_basepoint.serialize()).is_ok()); + assert!(PublicKey::from_slice(&node0_to_1_send_open_channel.common_fields.delayed_payment_basepoint.serialize()).is_ok()); } #[test] @@ -5856,7 +5873,7 @@ fn bolt2_open_channel_sane_dust_limit() { let push_msat=10001; nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), channel_value_satoshis, push_msat, 42, None, None).unwrap(); let mut node0_to_1_send_open_channel = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, nodes[1].node.get_our_node_id()); - node0_to_1_send_open_channel.dust_limit_satoshis = 547; + node0_to_1_send_open_channel.common_fields.dust_limit_satoshis = 547; node0_to_1_send_open_channel.channel_reserve_satoshis = 100001; nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &node0_to_1_send_open_channel); @@ -6291,7 +6308,7 @@ fn test_update_add_htlc_bolt2_receiver_zero_value_msat() { updates.update_add_htlcs[0].amount_msat = 0; nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &updates.update_add_htlcs[0]); - nodes[1].logger.assert_log("lightning::ln::channelmanager", "Remote side tried to send a 0-msat HTLC".to_string(), 1); + nodes[1].logger.assert_log_contains("lightning::ln::channelmanager", "Remote side tried to send a 0-msat HTLC", 3); 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() }, @@ -6470,7 +6487,7 @@ fn test_update_add_htlc_bolt2_receiver_check_max_htlc_limit() { get_route_and_payment_hash!(nodes[0], nodes[1], 1000); route.paths[0].hops[0].fee_msat = send_amt; let session_priv = SecretKey::from_slice(&[42; 32]).unwrap(); - let cur_height = nodes[0].node.best_block.read().unwrap().height() + 1; + let cur_height = nodes[0].node.best_block.read().unwrap().height + 1; let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::signing_only(), &route.paths[0], &session_priv).unwrap(); let (onion_payloads, _htlc_msat, htlc_cltv) = onion_utils::build_onion_payloads( &route.paths[0], send_amt, RecipientOnionFields::secret_only(our_payment_secret), cur_height, &None).unwrap(); @@ -7196,7 +7213,7 @@ fn test_user_configurable_csv_delay() { // We test config.our_to_self > BREAKDOWN_TIMEOUT is enforced in InboundV1Channel::new() nodes[1].node.create_channel(nodes[0].node.get_our_node_id(), 1000000, 1000000, 42, None, None).unwrap(); let mut open_channel = get_event_msg!(nodes[1], MessageSendEvent::SendOpenChannel, nodes[0].node.get_our_node_id()); - open_channel.to_self_delay = 200; + open_channel.common_fields.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, /*is_0conf=*/false) @@ -7211,7 +7228,7 @@ fn test_user_configurable_csv_delay() { nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 1000000, 1000000, 42, None, None).unwrap(); nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, nodes[1].node.get_our_node_id())); let mut accept_channel = get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, nodes[0].node.get_our_node_id()); - accept_channel.to_self_delay = 200; + accept_channel.common_fields.to_self_delay = 200; nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), &accept_channel); let reason_msg; if let MessageSendEvent::HandleError { ref action, .. } = nodes[0].node.get_and_clear_pending_msg_events()[0] { @@ -7228,7 +7245,7 @@ fn test_user_configurable_csv_delay() { // 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, None).unwrap(); let mut open_channel = get_event_msg!(nodes[1], MessageSendEvent::SendOpenChannel, nodes[0].node.get_our_node_id()); - open_channel.to_self_delay = 200; + open_channel.common_fields.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, /*is_0conf=*/false) @@ -7329,7 +7346,7 @@ fn test_announce_disable_channels() { } let msg_events = nodes[0].node.get_and_clear_pending_msg_events(); assert_eq!(msg_events.len(), 3); - let mut chans_disabled = HashMap::new(); + let mut chans_disabled = new_hash_map(); for e in msg_events { match e { MessageSendEvent::BroadcastChannelUpdate { ref msg } => { @@ -7913,8 +7930,8 @@ fn test_override_channel_config() { // Assert the channel created by node0 is using the override config. let res = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, nodes[1].node.get_our_node_id()); - assert_eq!(res.channel_flags, 0); - assert_eq!(res.to_self_delay, 200); + assert_eq!(res.common_fields.channel_flags, 0); + assert_eq!(res.common_fields.to_self_delay, 200); } #[test] @@ -7928,11 +7945,11 @@ fn test_override_0msat_htlc_minimum() { nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 16_000_000, 12_000_000, 42, None, Some(zero_config)).unwrap(); let res = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, nodes[1].node.get_our_node_id()); - assert_eq!(res.htlc_minimum_msat, 1); + assert_eq!(res.common_fields.htlc_minimum_msat, 1); nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &res); let res = get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, nodes[0].node.get_our_node_id()); - assert_eq!(res.htlc_minimum_msat, 1); + assert_eq!(res.common_fields.htlc_minimum_msat, 1); } #[test] @@ -8208,8 +8225,10 @@ fn test_onion_value_mpp_set_calculation() { 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 - if let msgs::OutboundOnionPayload::Receive { ref mut amt_msat, .. } = onion_payloads[1] { - *amt_msat = 99_000; + if let msgs::OutboundOnionPayload::Receive { + ref mut sender_intended_htlc_amt_msat, .. + } = onion_payloads[1] { + *sender_intended_htlc_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; @@ -8567,10 +8586,11 @@ fn test_concurrent_monitor_claim() { watchtower_alice.chain_monitor.block_connected(&block, HTLC_TIMEOUT_BROADCAST); // Watchtower Alice should have broadcast a commitment/HTLC-timeout - let alice_state = { + { let mut txn = alice_broadcaster.txn_broadcast(); assert_eq!(txn.len(), 2); - txn.remove(0) + check_spends!(txn[0], chan_1.3); + check_spends!(txn[1], txn[0]); }; // Copy ChainMonitor to simulate watchtower Bob and make it receive a commitment update first. @@ -8639,11 +8659,8 @@ fn test_concurrent_monitor_claim() { check_added_monitors(&nodes[0], 1); { let htlc_txn = alice_broadcaster.txn_broadcast(); - assert_eq!(htlc_txn.len(), 2); + assert_eq!(htlc_txn.len(), 1); check_spends!(htlc_txn[0], bob_state_y); - // Alice doesn't clean up the old HTLC claim since it hasn't seen a conflicting spend for - // it. However, she should, because it now has an invalid parent. - check_spends!(htlc_txn[1], alice_state); } } @@ -8679,7 +8696,7 @@ fn test_pre_lockin_no_chan_closed_update() { check_added_monitors!(nodes[0], 0); let funding_created_msg = get_event_msg!(nodes[0], MessageSendEvent::SendFundingCreated, nodes[1].node.get_our_node_id()); - let channel_id = crate::chain::transaction::OutPoint { txid: funding_created_msg.funding_txid, index: funding_created_msg.funding_output_index }.to_channel_id(); + let channel_id = ChannelId::v1_from_funding_outpoint(crate::chain::transaction::OutPoint { txid: funding_created_msg.funding_txid, index: funding_created_msg.funding_output_index }); 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, @@ -8882,7 +8899,12 @@ fn do_test_onchain_htlc_settlement_after_close(broadcast_alice: bool, go_onchain assert_eq!(bob_txn.len(), 1); check_spends!(bob_txn[0], txn_to_broadcast[0]); } else { - assert_eq!(bob_txn.len(), 2); + if nodes[1].connect_style.borrow().updates_best_block_first() { + assert_eq!(bob_txn.len(), 3); + assert_eq!(bob_txn[0].txid(), bob_txn[1].txid()); + } else { + assert_eq!(bob_txn.len(), 2); + } check_spends!(bob_txn[0], chan_ab.3); } } @@ -8898,15 +8920,16 @@ fn do_test_onchain_htlc_settlement_after_close(broadcast_alice: bool, go_onchain // If Alice force-closed, Bob only broadcasts a HTLC-output-claiming transaction. Otherwise, // Bob force-closed and broadcasts the commitment transaction along with a // HTLC-output-claiming transaction. - let bob_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().clone(); + let mut bob_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().clone(); if broadcast_alice { assert_eq!(bob_txn.len(), 1); check_spends!(bob_txn[0], txn_to_broadcast[0]); assert_eq!(bob_txn[0].input[0].witness.last().unwrap().len(), script_weight); } else { - assert_eq!(bob_txn.len(), 2); - check_spends!(bob_txn[1], txn_to_broadcast[0]); - assert_eq!(bob_txn[1].input[0].witness.last().unwrap().len(), script_weight); + assert_eq!(bob_txn.len(), if nodes[1].connect_style.borrow().updates_best_block_first() { 3 } else { 2 }); + let htlc_tx = bob_txn.pop().unwrap(); + check_spends!(htlc_tx, txn_to_broadcast[0]); + assert_eq!(htlc_tx.input[0].witness.last().unwrap().len(), script_weight); } } } @@ -8938,7 +8961,7 @@ fn test_duplicate_temporary_channel_id_from_different_peers() { // Modify the `OpenChannel` from `nodes[2]` to `nodes[0]` to ensure that it uses the same // `temporary_channel_id` as the `OpenChannel` from nodes[1] to nodes[0]. - open_chan_msg_chan_2_0.temporary_channel_id = open_chan_msg_chan_1_0.temporary_channel_id; + open_chan_msg_chan_2_0.common_fields.temporary_channel_id = open_chan_msg_chan_1_0.common_fields.temporary_channel_id; // Assert that `nodes[0]` can accept both `OpenChannel` requests, even though they use the same // `temporary_channel_id` as they are from different peers. @@ -8949,7 +8972,7 @@ fn test_duplicate_temporary_channel_id_from_different_peers() { match &events[0] { MessageSendEvent::SendAcceptChannel { node_id, msg } => { assert_eq!(node_id, &nodes[1].node.get_our_node_id()); - assert_eq!(msg.temporary_channel_id, open_chan_msg_chan_1_0.temporary_channel_id); + assert_eq!(msg.common_fields.temporary_channel_id, open_chan_msg_chan_1_0.common_fields.temporary_channel_id); }, _ => panic!("Unexpected event"), } @@ -8962,13 +8985,156 @@ fn test_duplicate_temporary_channel_id_from_different_peers() { match &events[0] { MessageSendEvent::SendAcceptChannel { node_id, msg } => { assert_eq!(node_id, &nodes[2].node.get_our_node_id()); - assert_eq!(msg.temporary_channel_id, open_chan_msg_chan_1_0.temporary_channel_id); + assert_eq!(msg.common_fields.temporary_channel_id, open_chan_msg_chan_1_0.common_fields.temporary_channel_id); }, _ => panic!("Unexpected event"), } } } +#[test] +fn test_peer_funding_sidechannel() { + // Test that if a peer somehow learns which txid we'll use for our channel funding before we + // receive `funding_transaction_generated` the peer cannot cause us to crash. We'd previously + // assumed that LDK would receive `funding_transaction_generated` prior to our peer learning + // the txid and panicked if the peer tried to open a redundant channel to us with the same + // funding outpoint. + // + // While this assumption is generally safe, some users may have out-of-band protocols where + // they notify their LSP about a funding outpoint first, or this may be violated in the future + // with collaborative transaction construction protocols, i.e. dual-funding. + let chanmon_cfgs = create_chanmon_cfgs(3); + let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]); + let nodes = create_network(3, &node_cfgs, &node_chanmgrs); + + let temp_chan_id_ab = exchange_open_accept_chan(&nodes[0], &nodes[1], 1_000_000, 0); + let temp_chan_id_ca = exchange_open_accept_chan(&nodes[2], &nodes[0], 1_000_000, 0); + + let (_, tx, funding_output) = + create_funding_transaction(&nodes[0], &nodes[1].node.get_our_node_id(), 1_000_000, 42); + + let cs_funding_events = nodes[2].node.get_and_clear_pending_events(); + assert_eq!(cs_funding_events.len(), 1); + match cs_funding_events[0] { + Event::FundingGenerationReady { .. } => {} + _ => panic!("Unexpected event {:?}", cs_funding_events), + } + + nodes[2].node.funding_transaction_generated_unchecked(&temp_chan_id_ca, &nodes[0].node.get_our_node_id(), tx.clone(), funding_output.index).unwrap(); + let funding_created_msg = get_event_msg!(nodes[2], MessageSendEvent::SendFundingCreated, nodes[0].node.get_our_node_id()); + nodes[0].node.handle_funding_created(&nodes[2].node.get_our_node_id(), &funding_created_msg); + get_event_msg!(nodes[0], MessageSendEvent::SendFundingSigned, nodes[2].node.get_our_node_id()); + expect_channel_pending_event(&nodes[0], &nodes[2].node.get_our_node_id()); + check_added_monitors!(nodes[0], 1); + + let res = nodes[0].node.funding_transaction_generated(&temp_chan_id_ab, &nodes[1].node.get_our_node_id(), tx.clone()); + let err_msg = format!("{:?}", res.unwrap_err()); + assert!(err_msg.contains("An existing channel using outpoint ")); + assert!(err_msg.contains(" is open with peer")); + // Even though the last funding_transaction_generated errored, it still generated a + // SendFundingCreated. However, when the peer responds with a funding_signed it will send the + // appropriate error message. + let as_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(), &as_funding_created); + check_added_monitors!(nodes[1], 1); + expect_channel_pending_event(&nodes[1], &nodes[0].node.get_our_node_id()); + let reason = ClosureReason::ProcessingError { err: format!("An existing channel using outpoint {} is open with peer {}", funding_output, nodes[2].node.get_our_node_id()), }; + check_closed_events(&nodes[0], &[ExpectedCloseEvent::from_id_reason(ChannelId::v1_from_funding_outpoint(funding_output), true, reason)]); + + 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); + get_err_msg(&nodes[0], &nodes[1].node.get_our_node_id()); +} + +#[test] +fn test_duplicate_conflicting_funding_from_second_peer() { + // Test that if a user tries to fund a channel with a funding outpoint they'd previously used + // we don't try to remove the previous ChannelMonitor. This is largely a test to ensure we + // don't regress in the fuzzer, as such funding getting passed our outpoint-matches checks + // implies the user (and our counterparty) has reused cryptographic keys across channels, which + // we require the user not do. + 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 temp_chan_id = exchange_open_accept_chan(&nodes[0], &nodes[1], 1_000_000, 0); + + let (_, tx, funding_output) = + create_funding_transaction(&nodes[0], &nodes[1].node.get_our_node_id(), 1_000_000, 42); + + // Now that we have a funding outpoint, create a dummy `ChannelMonitor` and insert it into + // nodes[0]'s ChainMonitor so that the initial `ChannelMonitor` write fails. + let dummy_chan_id = create_chan_between_nodes(&nodes[2], &nodes[3]).3; + let dummy_monitor = get_monitor!(nodes[2], dummy_chan_id).clone(); + nodes[0].chain_monitor.chain_monitor.watch_channel(funding_output, dummy_monitor).unwrap(); + + nodes[0].node.funding_transaction_generated(&temp_chan_id, &nodes[1].node.get_our_node_id(), tx.clone()).unwrap(); + + let mut funding_created_msg = 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_msg); + let funding_signed_msg = get_event_msg!(nodes[1], MessageSendEvent::SendFundingSigned, nodes[0].node.get_our_node_id()); + check_added_monitors!(nodes[1], 1); + expect_channel_pending_event(&nodes[1], &nodes[0].node.get_our_node_id()); + + nodes[0].node.handle_funding_signed(&nodes[1].node.get_our_node_id(), &funding_signed_msg); + // At this point, the channel should be closed, after having generated one monitor write (the + // watch_channel call which failed), but zero monitor updates. + check_added_monitors!(nodes[0], 1); + get_err_msg(&nodes[0], &nodes[1].node.get_our_node_id()); + let err_reason = ClosureReason::ProcessingError { err: "Channel funding outpoint was a duplicate".to_owned() }; + check_closed_events(&nodes[0], &[ExpectedCloseEvent::from_id_reason(funding_signed_msg.channel_id, true, err_reason)]); +} + +#[test] +fn test_duplicate_funding_err_in_funding() { + // Test that if we have a live channel with one peer, then another peer comes along and tries + // to create a second channel with the same txid we'll fail and not overwrite the + // outpoint_to_peer map in `ChannelManager`. + // + // This was previously broken. + let chanmon_cfgs = create_chanmon_cfgs(3); + let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]); + let nodes = create_network(3, &node_cfgs, &node_chanmgrs); + + let (_, _, _, real_channel_id, funding_tx) = create_chan_between_nodes(&nodes[0], &nodes[1]); + let real_chan_funding_txo = chain::transaction::OutPoint { txid: funding_tx.txid(), index: 0 }; + assert_eq!(ChannelId::v1_from_funding_outpoint(real_chan_funding_txo), real_channel_id); + + nodes[2].node.create_channel(nodes[1].node.get_our_node_id(), 100_000, 0, 42, None, None).unwrap(); + let mut open_chan_msg = get_event_msg!(nodes[2], MessageSendEvent::SendOpenChannel, nodes[1].node.get_our_node_id()); + let node_c_temp_chan_id = open_chan_msg.common_fields.temporary_channel_id; + open_chan_msg.common_fields.temporary_channel_id = real_channel_id; + nodes[1].node.handle_open_channel(&nodes[2].node.get_our_node_id(), &open_chan_msg); + let mut accept_chan_msg = get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, nodes[2].node.get_our_node_id()); + accept_chan_msg.common_fields.temporary_channel_id = node_c_temp_chan_id; + nodes[2].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), &accept_chan_msg); + + // Now that we have a second channel with the same funding txo, send a bogus funding message + // and let nodes[1] remove the inbound channel. + let (_, funding_tx, _) = create_funding_transaction(&nodes[2], &nodes[1].node.get_our_node_id(), 100_000, 42); + + nodes[2].node.funding_transaction_generated(&node_c_temp_chan_id, &nodes[1].node.get_our_node_id(), funding_tx).unwrap(); + + let mut funding_created_msg = get_event_msg!(nodes[2], MessageSendEvent::SendFundingCreated, nodes[1].node.get_our_node_id()); + funding_created_msg.temporary_channel_id = real_channel_id; + // Make the signature invalid by changing the funding output + funding_created_msg.funding_output_index += 10; + nodes[1].node.handle_funding_created(&nodes[2].node.get_our_node_id(), &funding_created_msg); + get_err_msg(&nodes[1], &nodes[2].node.get_our_node_id()); + let err = "Invalid funding_created signature from peer".to_owned(); + let reason = ClosureReason::ProcessingError { err }; + let expected_closing = ExpectedCloseEvent::from_id_reason(real_channel_id, false, reason); + check_closed_events(&nodes[1], &[expected_closing]); + + assert_eq!( + *nodes[1].node.outpoint_to_peer.lock().unwrap().get(&real_chan_funding_txo).unwrap(), + nodes[0].node.get_our_node_id() + ); +} + #[test] fn test_duplicate_chan_id() { // Test that if a given peer tries to open a channel with the same channel_id as one that is @@ -9001,7 +9167,7 @@ fn test_duplicate_chan_id() { // first (valid) and second (invalid) channels are closed, given they both have // the same non-temporary channel_id. However, currently we do not, so we just // move forward with it. - assert_eq!(msg.channel_id, open_chan_msg.temporary_channel_id); + assert_eq!(msg.channel_id, open_chan_msg.common_fields.temporary_channel_id); assert_eq!(node_id, nodes[0].node.get_our_node_id()); }, _ => panic!("Unexpected event"), @@ -9027,7 +9193,7 @@ fn test_duplicate_chan_id() { let funding_signed_msg = get_event_msg!(nodes[1], MessageSendEvent::SendFundingSigned, nodes[0].node.get_our_node_id()); let funding_outpoint = crate::chain::transaction::OutPoint { txid: funding_created_msg.funding_txid, index: funding_created_msg.funding_output_index }; - let channel_id = funding_outpoint.to_channel_id(); + let channel_id = ChannelId::v1_from_funding_outpoint(funding_outpoint); // Now we have the first channel past funding_created (ie it has a txid-based channel_id, not a // temporary one). @@ -9035,7 +9201,7 @@ fn test_duplicate_chan_id() { // First try to open a second channel with a temporary channel id equal to the txid-based one. // Technically this is allowed by the spec, but we don't support it and there's little reason // to. Still, it shouldn't cause any other issues. - open_chan_msg.temporary_channel_id = channel_id; + open_chan_msg.common_fields.temporary_channel_id = channel_id; nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &open_chan_msg); { let events = nodes[1].node.get_and_clear_pending_msg_events(); @@ -9044,7 +9210,7 @@ fn test_duplicate_chan_id() { MessageSendEvent::HandleError { action: ErrorAction::SendErrorMessage { ref msg }, node_id } => { // Technically, at this point, nodes[1] would be justified in thinking both // channels are closed, but currently we do not, so we just move forward with it. - assert_eq!(msg.channel_id, open_chan_msg.temporary_channel_id); + assert_eq!(msg.channel_id, open_chan_msg.common_fields.temporary_channel_id); assert_eq!(node_id, nodes[0].node.get_our_node_id()); }, _ => panic!("Unexpected event"), @@ -9065,20 +9231,26 @@ fn test_duplicate_chan_id() { // 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). - match a_peer_state.channel_by_id.remove(&open_chan_2_msg.temporary_channel_id).unwrap() { + match a_peer_state.channel_by_id.remove(&open_chan_2_msg.common_fields.temporary_channel_id).unwrap() { ChannelPhase::UnfundedOutboundV1(mut chan) => { let logger = test_utils::TestLogger::new(); chan.get_funding_created(tx.clone(), funding_outpoint, false, &&logger).map_err(|_| ()).unwrap() }, _ => panic!("Unexpected ChannelPhase variant"), - } + }.unwrap() }; check_added_monitors!(nodes[0], 0); - nodes[1].node.handle_funding_created(&nodes[0].node.get_our_node_id(), &funding_created.unwrap()); + nodes[1].node.handle_funding_created(&nodes[0].node.get_our_node_id(), &funding_created); // At this point we'll look up if the channel_id is present and immediately fail the channel // without trying to persist the `ChannelMonitor`. check_added_monitors!(nodes[1], 0); + check_closed_events(&nodes[1], &[ + ExpectedCloseEvent::from_id_reason(funding_created.temporary_channel_id, false, ClosureReason::ProcessingError { + err: "Already had channel with the new channel_id".to_owned() + }) + ]); + // ...still, nodes[1] will reject the duplicate channel. { let events = nodes[1].node.get_and_clear_pending_msg_events(); @@ -9382,8 +9554,12 @@ fn do_test_tx_confirmed_skipping_blocks_immediate_broadcast(test_height_before_t // We should broadcast an HTLC transaction spending our funding transaction first let spending_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0); assert_eq!(spending_txn.len(), 2); - assert_eq!(spending_txn[0].txid(), node_txn[0].txid()); - check_spends!(spending_txn[1], node_txn[0]); + let htlc_tx = if spending_txn[0].txid() == node_txn[0].txid() { + &spending_txn[1] + } else { + &spending_txn[0] + }; + check_spends!(htlc_tx, node_txn[0]); // We should also generate a SpendableOutputs event with the to_self output (as its // timelock is up). let descriptor_spend_txn = check_spendable_outputs!(nodes[1], node_cfgs[1].keys_manager); @@ -9393,7 +9569,7 @@ fn do_test_tx_confirmed_skipping_blocks_immediate_broadcast(test_height_before_t // should immediately fail-backwards the HTLC to the previous hop, without waiting for an // additional block built on top of the current chain. nodes[1].chain_monitor.chain_monitor.transactions_confirmed( - &nodes[1].get_block_header(conf_height + 1), &[(0, &spending_txn[1])], conf_height + 1); + &nodes[1].get_block_header(conf_height + 1), &[(0, htlc_tx)], conf_height + 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: channel_id }]); check_added_monitors!(nodes[1], 1); @@ -9722,10 +9898,10 @@ fn do_test_max_dust_htlc_exposure(dust_outbound_balance: bool, exposure_breach_e nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 1_000_000, 500_000_000, 42, None, None).unwrap(); let mut open_channel = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, nodes[1].node.get_our_node_id()); - open_channel.max_htlc_value_in_flight_msat = 50_000_000; - open_channel.max_accepted_htlcs = 60; + open_channel.common_fields.max_htlc_value_in_flight_msat = 50_000_000; + open_channel.common_fields.max_accepted_htlcs = 60; if on_holder_tx { - open_channel.dust_limit_satoshis = 546; + open_channel.common_fields.dust_limit_satoshis = 546; } nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &open_channel); let mut accept_channel = get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, nodes[0].node.get_our_node_id()); @@ -9770,10 +9946,10 @@ fn do_test_max_dust_htlc_exposure(dust_outbound_balance: bool, exposure_breach_e (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_msat: u64 = (dust_buffer_feerate * htlc_timeout_tx_weight(&channel_type_features) / 1000 + open_channel.common_fields.dust_limit_satoshis - 1) * 1000; 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_msat: u64 = (dust_buffer_feerate * htlc_success_tx_weight(&channel_type_features) / 1000 + open_channel.common_fields.dust_limit_satoshis - 1) * 1000; 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; @@ -9908,7 +10084,7 @@ fn test_non_final_funding_tx() { 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 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(); @@ -9953,7 +10129,7 @@ fn test_non_final_funding_tx_within_headroom() { 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 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(); @@ -10342,6 +10518,90 @@ fn test_remove_expired_inbound_unfunded_channels() { check_closed_event(&nodes[1], 1, ClosureReason::HolderForceClosed, false, &[nodes[0].node.get_our_node_id()], 100000); } +#[test] +fn test_channel_close_when_not_timely_accepted() { + // Create network of two nodes + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + // Simulate peer-disconnects mid-handshake + // The channel is initiated from the node 0 side, + // but the nodes disconnect before node 1 could send accept channel + let create_chan_id = nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 100000, 10001, 42, None, None).unwrap(); + let open_channel_msg = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, nodes[1].node.get_our_node_id()); + assert_eq!(open_channel_msg.common_fields.temporary_channel_id, create_chan_id); + + nodes[0].node.peer_disconnected(&nodes[1].node.get_our_node_id()); + nodes[1].node.peer_disconnected(&nodes[0].node.get_our_node_id()); + + // Make sure that we have not removed the OutboundV1Channel from node[0] immediately. + assert_eq!(nodes[0].node.list_channels().len(), 1); + + // Since channel was inbound from node[1] perspective, it should have been dropped immediately. + assert_eq!(nodes[1].node.list_channels().len(), 0); + + // In the meantime, some time passes. + for _ in 0..UNFUNDED_CHANNEL_AGE_LIMIT_TICKS { + nodes[0].node.timer_tick_occurred(); + } + + // Since we disconnected from peer and did not connect back within time, + // we should have forced-closed the channel by now. + check_closed_event!(nodes[0], 1, ClosureReason::HolderForceClosed, [nodes[1].node.get_our_node_id()], 100000); + assert_eq!(nodes[0].node.list_channels().len(), 0); + + { + // Since accept channel message was never received + // The channel should be forced close by now from node 0 side + // and the peer removed from per_peer_state + let node_0_per_peer_state = nodes[0].node.per_peer_state.read().unwrap(); + assert_eq!(node_0_per_peer_state.len(), 0); + } +} + +#[test] +fn test_rebroadcast_open_channel_when_reconnect_mid_handshake() { + // Create network of two nodes + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + // Simulate peer-disconnects mid-handshake + // The channel is initiated from the node 0 side, + // but the nodes disconnect before node 1 could send accept channel + let create_chan_id = nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 100000, 10001, 42, None, None).unwrap(); + let open_channel_msg = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, nodes[1].node.get_our_node_id()); + assert_eq!(open_channel_msg.common_fields.temporary_channel_id, create_chan_id); + + nodes[0].node.peer_disconnected(&nodes[1].node.get_our_node_id()); + nodes[1].node.peer_disconnected(&nodes[0].node.get_our_node_id()); + + // Make sure that we have not removed the OutboundV1Channel from node[0] immediately. + assert_eq!(nodes[0].node.list_channels().len(), 1); + + // Since channel was inbound from node[1] perspective, it should have been immediately dropped. + assert_eq!(nodes[1].node.list_channels().len(), 0); + + // The peers now reconnect + nodes[0].node.peer_connected(&nodes[1].node.get_our_node_id(), &msgs::Init { + features: nodes[1].node.init_features(), networks: None, remote_network_address: None + }, true).unwrap(); + nodes[1].node.peer_connected(&nodes[0].node.get_our_node_id(), &msgs::Init { + features: nodes[0].node.init_features(), networks: None, remote_network_address: None + }, false).unwrap(); + + // Make sure the SendOpenChannel message is added to node_0 pending message events + let msg_events = nodes[0].node.get_and_clear_pending_msg_events(); + assert_eq!(msg_events.len(), 1); + match &msg_events[0] { + MessageSendEvent::SendOpenChannel { msg, .. } => assert_eq!(msg, &open_channel_msg), + _ => panic!("Unexpected message."), + } +} + fn do_test_multi_post_event_actions(do_reload: bool) { // Tests handling multiple post-Event actions at once. // There is specific code in ChannelManager to handle channels where multiple post-Event @@ -10471,7 +10731,7 @@ fn test_batch_channel_open() { // Complete the persistence of the monitor. nodes[0].chain_monitor.complete_sole_pending_chan_update( - &OutPoint { txid: tx.txid(), index: 1 }.to_channel_id() + &ChannelId::v1_from_funding_outpoint(OutPoint { txid: tx.txid(), index: 1 }) ); let events = nodes[0].node.get_and_clear_pending_events(); @@ -10498,7 +10758,9 @@ fn test_batch_channel_open() { } #[test] -fn test_disconnect_in_funding_batch() { +fn test_close_in_funding_batch() { + // This test ensures that if one of the channels + // in the batch closes, the complete batch will close. let chanmon_cfgs = create_chanmon_cfgs(3); let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]); @@ -10522,38 +10784,56 @@ fn test_disconnect_in_funding_batch() { // 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()); + // Force-close the channel for which we've completed the initial monitor. + let funding_txo_1 = OutPoint { txid: tx.txid(), index: 0 }; + let funding_txo_2 = OutPoint { txid: tx.txid(), index: 1 }; + let channel_id_1 = ChannelId::v1_from_funding_outpoint(funding_txo_1); + let channel_id_2 = ChannelId::v1_from_funding_outpoint(funding_txo_2); + + nodes[0].node.force_close_broadcasting_latest_txn(&channel_id_1, &nodes[1].node.get_our_node_id()).unwrap(); - // 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(); + // 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); + } + + 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. check_closed_events(&nodes[0], &[ ExpectedCloseEvent { channel_id: Some(channel_id_1), discard_funding: true, + channel_funding_txo: Some(funding_txo_1), + user_channel_id: Some(42), ..Default::default() }, ExpectedCloseEvent { channel_id: Some(channel_id_2), discard_funding: true, + channel_funding_txo: Some(funding_txo_2), + user_channel_id: Some(43), ..Default::default() }, ]); - // 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()); } @@ -10594,8 +10874,10 @@ fn test_batch_funding_close_after_funding_signed() { 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(); + let funding_txo_1 = OutPoint { txid: tx.txid(), index: 0 }; + let funding_txo_2 = OutPoint { txid: tx.txid(), index: 1 }; + let channel_id_1 = ChannelId::v1_from_funding_outpoint(funding_txo_1); + let channel_id_2 = ChannelId::v1_from_funding_outpoint(funding_txo_2); 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); { @@ -10627,11 +10909,15 @@ fn test_batch_funding_close_after_funding_signed() { ExpectedCloseEvent { channel_id: Some(channel_id_1), discard_funding: true, + channel_funding_txo: Some(funding_txo_1), + user_channel_id: Some(42), ..Default::default() }, ExpectedCloseEvent { channel_id: Some(channel_id_2), discard_funding: true, + channel_funding_txo: Some(funding_txo_2), + user_channel_id: Some(43), ..Default::default() }, ]); @@ -10651,7 +10937,7 @@ fn do_test_funding_and_commitment_tx_confirm_same_block(confirm_remote_commitmen let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs); let funding_tx = create_chan_between_nodes_with_value_init(&nodes[0], &nodes[1], 1_000_000, 0); - let chan_id = chain::transaction::OutPoint { txid: funding_tx.txid(), index: 0 }.to_channel_id(); + let chan_id = ChannelId::v1_from_funding_outpoint(chain::transaction::OutPoint { txid: funding_tx.txid(), index: 0 }); assert_eq!(nodes[0].node.list_channels().len(), 1); assert_eq!(nodes[1].node.list_channels().len(), 1); diff --git a/lightning/src/ln/functional_tests_splice.rs b/lightning/src/ln/functional_tests_splice.rs index 970f3376e31..5d1ab216d5c 100644 --- a/lightning/src/ln/functional_tests_splice.rs +++ b/lightning/src/ln/functional_tests_splice.rs @@ -128,8 +128,8 @@ fn test_channel_open_and_close() { let channel_value_sat = 100000; // same as funding satoshis let push_msat = 0; - let expected_temporary_channel_id = "f9fe5e552aa0fc45020f0505efde432a4e373e5d393863973a6899f8c26d33d1"; - let expected_funded_channel_id = "42056a074feeea62aec646e815197539483a458bf6e9e04d115f72b851265385"; + let expected_temporary_channel_id = "2f64bdc25fb91c69b6f15b6fc10b32eb773471e433681dc956d9267a4dda8c2b"; + let expected_funded_channel_id = "74c52ab4f11296d62b66a6dba9513b04a3e7fb5a09a30cee22fce7294ab55b7e"; // Have node0 initiate a channel to node1 with aforementioned parameters let channel_id_temp1 = initiator_node.node.create_channel(acceptor_node.node.get_our_node_id(), channel_value_sat, push_msat, 42, None, None).unwrap(); @@ -146,7 +146,7 @@ fn test_channel_open_and_close() { let (channel_id_temp2, funding_tx, _funding_output) = create_funding_transaction(&initiator_node, &acceptor_node.node.get_our_node_id(), channel_value_sat, 42); assert_eq!(channel_id_temp2.to_string(), expected_temporary_channel_id); assert_eq!(funding_tx.encode().len(), 55); - let expected_funding_tx = "0000000000010001a086010000000000220020203e7308dfff4f1923d21ca23f45545965d1f6b44ea0e2184c5db4d649a61c3a00000000"; + let expected_funding_tx = "0000000000010001a08601000000000022002034c0cc0ad0dd5fe61dcf7ef58f995e3d34f8dbd24aa2a6fae68fefe102bf025c00000000"; assert_eq!(&funding_tx.encode().as_hex().to_string(), expected_funding_tx); // Funding transation created, provide it @@ -266,8 +266,8 @@ fn test_channel_open_v2_and_close() { // Instantiate channel parameters where we push the maximum msats given our funding satoshis let channel_value_sat = 100000; // same as funding satoshis - let expected_temporary_channel_id = "4cd87e42e1cf2813f604ec16e42297f1723c5dfc100593b83eead1588d30b671"; - let expected_funded_channel_id = "ca537cbb1f386065003906d85719965f67f22687600bc497b29c7d5d4736f0ce"; + let expected_temporary_channel_id = "b1a3942f261316385476c86d7f454062ceb06d2e37675f08c2fac76b8c3ddc5e"; + let expected_funded_channel_id = "0df1425050bb045209e23459ebb5f9c8f6f219dafb85e2ec59d5fe841f1c4463"; // Have node0 initiate a channel to node1 with aforementioned parameters let channel_id_temp1 = initiator_node.node.create_dual_funded_channel(acceptor_node.node.get_our_node_id(), channel_value_sat, None, 42, None).unwrap(); @@ -280,7 +280,7 @@ fn test_channel_open_v2_and_close() { let _res = acceptor_node.node.handle_open_channel_v2(&initiator_node.node.get_our_node_id(), &open_channel2_message.clone()); // Extract the accept channel message from node1 to node0 let accept_channel2_message = get_event_msg!(acceptor_node, MessageSendEvent::SendAcceptChannelV2, initiator_node.node.get_our_node_id()); - assert_eq!(accept_channel2_message.temporary_channel_id.to_string(), expected_temporary_channel_id); + assert_eq!(accept_channel2_message.common_fields.temporary_channel_id.to_string(), expected_temporary_channel_id); let _res = initiator_node.node.handle_accept_channel_v2(&acceptor_node.node.get_our_node_id(), &accept_channel2_message.clone()); @@ -314,9 +314,10 @@ fn test_channel_open_v2_and_close() { let _res = initiator_node.node.handle_tx_complete(&acceptor_node.node.get_our_node_id(), &tx_complete_msg); - // First output we send is the change output. + // First output, the funding tx let tx_add_output_msg = get_event_msg!(&initiator_node, MessageSendEvent::SendTxAddOutput, acceptor_node.node.get_our_node_id()); - assert!(tx_add_output_msg.script.is_v0_p2wpkh()); + // assert!(tx_add_output_msg.script.is_v0_p2wpkh()); // TODO check + assert_eq!(tx_add_output_msg.sats, channel_value_sat); let _res = acceptor_node.node.handle_tx_add_output(&initiator_node.node.get_our_node_id(), &tx_add_output_msg); let tx_complete_msg = get_event_msg!(&acceptor_node, MessageSendEvent::SendTxComplete, initiator_node.node.get_our_node_id()); @@ -324,9 +325,8 @@ fn test_channel_open_v2_and_close() { let _res = initiator_node.node.handle_tx_complete(&acceptor_node.node.get_our_node_id(), &tx_complete_msg); let tx_add_output_msg = get_event_msg!(&initiator_node, MessageSendEvent::SendTxAddOutput, acceptor_node.node.get_our_node_id()); - // Check we get the channel funding output. - assert!(tx_add_output_msg.script.is_v0_p2wsh()); - assert_eq!(tx_add_output_msg.sats, channel_value_sat); + // Second output, change + // assert!(tx_add_output_msg.script.is_v0_p2wsh()); // TODO check let _res = acceptor_node.node.handle_tx_add_output(&initiator_node.node.get_our_node_id(), &tx_add_output_msg); let tx_complete_msg = get_event_msg!(acceptor_node, MessageSendEvent::SendTxComplete, initiator_node.node.get_our_node_id()); @@ -412,7 +412,7 @@ fn test_channel_open_v2_and_close() { // Check that funding transaction has been broadcasted assert_eq!(chanmon_cfgs[initiator_node_index].tx_broadcaster.txn_broadcasted.lock().unwrap().len(), 1); let broadcasted_funding_tx = chanmon_cfgs[initiator_node_index].tx_broadcaster.txn_broadcasted.lock().unwrap()[0].clone(); - let expected_funding_tx = "020000000001019c76affec45612929f824230eacc67dc7b3db1072c39d0e62f4f557a34e141fc000000000000000000023788000000000000160014d5a9aa98b89acc215fc3d23d6fec0ad59ca3665fa086010000000000220020203e7308dfff4f1923d21ca23f45545965d1f6b44ea0e2184c5db4d649a61c3a01010000000000"; + let expected_funding_tx = "020000000001019c76affec45612929f824230eacc67dc7b3db1072c39d0e62f4f557a34e141fc000000000000000000023788000000000000160014d5a9aa98b89acc215fc3d23d6fec0ad59ca3665fa08601000000000022002034c0cc0ad0dd5fe61dcf7ef58f995e3d34f8dbd24aa2a6fae68fefe102bf025c01010000000000"; assert_eq!(broadcasted_funding_tx.encode().len(), 130); assert_eq!(&broadcasted_funding_tx.encode().as_hex().to_string(), expected_funding_tx); // Check that funding transaction has been broadcasted on the acceptor side as well @@ -564,7 +564,7 @@ fn test_v1_splice_in() { let channel_value_sat = 100000; // same as funding satoshis let push_msat = 0; - let expected_funded_channel_id = "e8ef56e12db94c1f62797f65b2093a76d4a198b657547ac75dcc087282930c84"; + let expected_funded_channel_id = "c95d1eb6f3d0c5c74a398a6e9c2c0721afde1e30d23a70867362e9d6d8d04281"; // Have node0 initiate a channel to node1 with aforementioned parameters let channel_id_temp1 = initiator_node.node.create_channel(acceptor_node.node.get_our_node_id(), channel_value_sat, push_msat, 42, None, None).unwrap(); @@ -715,8 +715,8 @@ fn test_v2_splice_in() { // Instantiate channel parameters where we push the maximum msats given our funding satoshis let channel_value_sat = 100000; // same as funding satoshis - let expected_temporary_channel_id = "4cd87e42e1cf2813f604ec16e42297f1723c5dfc100593b83eead1588d30b671"; - let expected_funded_channel_id = "ca537cbb1f386065003906d85719965f67f22687600bc497b29c7d5d4736f0ce"; + let expected_temporary_channel_id = "b1a3942f261316385476c86d7f454062ceb06d2e37675f08c2fac76b8c3ddc5e"; + let expected_funded_channel_id = "0df1425050bb045209e23459ebb5f9c8f6f219dafb85e2ec59d5fe841f1c4463"; // Have node0 initiate a channel to node1 with aforementioned parameters let channel_id_temp1 = initiator_node.node.create_dual_funded_channel(acceptor_node.node.get_our_node_id(), channel_value_sat, None, 42, None).unwrap(); @@ -729,7 +729,7 @@ fn test_v2_splice_in() { let _res = acceptor_node.node.handle_open_channel_v2(&initiator_node.node.get_our_node_id(), &open_channel2_message.clone()); // Extract the accept channel message from node1 to node0 let accept_channel2_message = get_event_msg!(acceptor_node, MessageSendEvent::SendAcceptChannelV2, initiator_node.node.get_our_node_id()); - assert_eq!(accept_channel2_message.temporary_channel_id.to_string(), expected_temporary_channel_id); + assert_eq!(accept_channel2_message.common_fields.temporary_channel_id.to_string(), expected_temporary_channel_id); let _res = initiator_node.node.handle_accept_channel_v2(&acceptor_node.node.get_our_node_id(), &accept_channel2_message.clone()); @@ -759,17 +759,18 @@ fn test_v2_splice_in() { let _res = initiator_node.node.handle_tx_complete(&acceptor_node.node.get_our_node_id(), &tx_complete_msg); - // First output we send is the change output. + // First output, the new funding tx let tx_add_output_msg = get_event_msg!(&initiator_node, MessageSendEvent::SendTxAddOutput, acceptor_node.node.get_our_node_id()); + assert_eq!(tx_add_output_msg.sats, channel_value_sat); let _res = acceptor_node.node.handle_tx_add_output(&initiator_node.node.get_our_node_id(), &tx_add_output_msg); let tx_complete_msg = get_event_msg!(&acceptor_node, MessageSendEvent::SendTxComplete, initiator_node.node.get_our_node_id()); + // Second output, change let _res = initiator_node.node.handle_tx_complete(&acceptor_node.node.get_our_node_id(), &tx_complete_msg); - let tx_add_output_msg = get_event_msg!(&initiator_node, MessageSendEvent::SendTxAddOutput, acceptor_node.node.get_our_node_id()); - assert_eq!(tx_add_output_msg.sats, channel_value_sat); + let tx_add_output2_msg = get_event_msg!(&initiator_node, MessageSendEvent::SendTxAddOutput, acceptor_node.node.get_our_node_id()); - let _res = acceptor_node.node.handle_tx_add_output(&initiator_node.node.get_our_node_id(), &tx_add_output_msg); + let _res = acceptor_node.node.handle_tx_add_output(&initiator_node.node.get_our_node_id(), &tx_add_output2_msg); let tx_complete_msg = get_event_msg!(acceptor_node, MessageSendEvent::SendTxComplete, initiator_node.node.get_our_node_id()); initiator_node.node.handle_tx_complete(&acceptor_node.node.get_our_node_id(), &tx_complete_msg); @@ -829,7 +830,7 @@ fn test_v2_splice_in() { assert_eq!(chanmon_cfgs[initiator_node_index].tx_broadcaster.txn_broadcasted.lock().unwrap().len(), 1); let broadcasted_funding_tx = chanmon_cfgs[initiator_node_index].tx_broadcaster.txn_broadcasted.lock().unwrap()[0].clone(); assert_eq!(broadcasted_funding_tx.encode().len(), 130); - assert_eq!(broadcasted_funding_tx.txid().to_string(), "6d895eaa56b6c8d896e45d9f54744ac5c075f3e961ae376546e167ec75dbd7be"); + assert_eq!(broadcasted_funding_tx.txid().to_string(), "4f2c5194647fa299f0fcac6f4611651fcf2badf58927203398d7cb7cbf8c21e8"); // Simulate confirmation of the funding tx confirm_transaction(&initiator_node, &broadcasted_funding_tx); @@ -943,7 +944,7 @@ fn test_v2_splice_in() { // Initiator_node will generate first TxAddInput message let tx_add_input_msg = get_event_msg!(&initiator_node, MessageSendEvent::SendTxAddInput, acceptor_node.node.get_our_node_id()); - assert_eq!(tx_add_input_msg.prevtx.0.output[tx_add_input_msg.prevtx_out as usize].value, channel_value_sat); + assert_eq!(tx_add_input_msg.prevtx.0.output[tx_add_input_msg.prevtx_out as usize].value, extra_splice_funding_input_sats); let _res = acceptor_node.node.handle_tx_add_input(&initiator_node.node.get_our_node_id(), &tx_add_input_msg); let tx_complete_msg = get_event_msg!(acceptor_node, MessageSendEvent::SendTxComplete, initiator_node.node.get_our_node_id()); @@ -951,7 +952,7 @@ fn test_v2_splice_in() { let _res = initiator_node.node.handle_tx_complete(&acceptor_node.node.get_our_node_id(), &tx_complete_msg); // second TxAddInput message for the second input let tx_add_input2_msg = get_event_msg!(&initiator_node, MessageSendEvent::SendTxAddInput, acceptor_node.node.get_our_node_id()); - assert_eq!(tx_add_input2_msg.prevtx.0.output[tx_add_input2_msg.prevtx_out as usize].value, extra_splice_funding_input_sats); + assert_eq!(tx_add_input2_msg.prevtx.0.output[tx_add_input2_msg.prevtx_out as usize].value, channel_value_sat); let _res = acceptor_node.node.handle_tx_add_input(&initiator_node.node.get_our_node_id(), &tx_add_input2_msg); let tx_complete_msg = get_event_msg!(acceptor_node, MessageSendEvent::SendTxComplete, initiator_node.node.get_our_node_id()); @@ -1016,7 +1017,7 @@ fn test_v2_splice_in() { let _res = initiator_node.node.funding_transaction_signed(&channel_id, &counterparty_node_id, unsigned_transaction).unwrap(); } else { panic!(); } - let expected_splice_funding_txid = "3d43a4cafc53c75e794e7122c120effc03ad96c15904f8e0de134187f2982426"; + let expected_splice_funding_txid = "1fdcee0e53a59a5457b40c8fcf4de29caba871ae7cb4bf8416d81437a2ffa4ed"; // check new funding tx assert_eq!(initiator_node.node.list_channels().len(), 1); { @@ -1100,7 +1101,7 @@ fn test_v2_splice_in() { // Check that funding transaction has been broadcasted assert_eq!(chanmon_cfgs[initiator_node_index].tx_broadcaster.txn_broadcasted.lock().unwrap().len(), 2); let broadcasted_splice_tx = chanmon_cfgs[initiator_node_index].tx_broadcaster.txn_broadcasted.lock().unwrap()[1].clone(); - let expected_funding_tx = "02000000000102bed7db75ec67e1466537ae61e9f375c0c54a74549f5de496d8c8b656aa5e896d010000000000000000a29ca934f2f9e07815e35099881dc8c0de1847ce0f00154de3d66c0133384b7900000000000000000002ea37000000000000160014d5a9aa98b89acc215fc3d23d6fec0ad59ca3665fc0d4010000000000220020203e7308dfff4f1923d21ca23f45545965d1f6b44ea0e2184c5db4d649a61c3a040047304402200900f05f4b09711287cbe827d9bdfe3206c12e6b930d4f68b4a227c011d2f49002205dd7218638af091d32b7afe454a258553b93d57cf681c9e7ca4e5d0ce79bf8cb014730440220717835e6d81afe5a8a955b81b600feb12b1e16f45a7b295a24fd01064918251202201601c199f988bd57b4138c1369ede9b5ed8540ac77fd7b7541d5d837a424365b01475221026a2a07ee436cc6745aed846a76275f66ed16a469706aca3aa581f20b85d85396210307a78def56cba9fc4db22a25928181de538ee59ba1a475ae113af7790acd0db352ae01010000000000"; + let expected_funding_tx = "02000000000102e8218cbf7ccbd79833202789f5ad2bcf1f6511466facfcf099a27f6494512c4f010000000000000000a29ca934f2f9e07815e35099881dc8c0de1847ce0f00154de3d66c0133384b7900000000000000000002c0d401000000000022002034c0cc0ad0dd5fe61dcf7ef58f995e3d34f8dbd24aa2a6fae68fefe102bf025cea37000000000000160014d5a9aa98b89acc215fc3d23d6fec0ad59ca3665f0400473044022071280f2a4768843328412d412b4d7c977604fe0d1c38ef57c6a5aa7cee159d41022000b5ae62d200c8caf1d268b3fa4da2c54a3241b849600215991fc9405a92e42401473044022022158b2e994b8479577f3537964085eebabfef950732566d028236f460827efb02201e94759fd145a7f614d20c7a7d68de2a26d409e250619c73dd84ab149873b5b3014752210307a78def56cba9fc4db22a25928181de538ee59ba1a475ae113af7790acd0db32103c21e841cbc0b48197d060c71e116c185fa0ac281b7d0aa5924f535154437ca3b52ae01010000000000"; assert_eq!(broadcasted_splice_tx.encode().len(), 389); assert_eq!(broadcasted_splice_tx.encode().as_hex().to_string(), expected_funding_tx); let initiator_funding_key = get_funding_key(&initiator_node, &acceptor_node, &channel_id1); @@ -1197,8 +1198,8 @@ fn test_v2_payment_splice_in_payment() { // Instantiate channel parameters where we push the maximum msats given our funding satoshis let channel_value_sat = 100000; // same as funding satoshis - let expected_temporary_channel_id = "4cd87e42e1cf2813f604ec16e42297f1723c5dfc100593b83eead1588d30b671"; - let expected_funded_channel_id = "ca537cbb1f386065003906d85719965f67f22687600bc497b29c7d5d4736f0ce"; + let expected_temporary_channel_id = "b1a3942f261316385476c86d7f454062ceb06d2e37675f08c2fac76b8c3ddc5e"; + let expected_funded_channel_id = "0df1425050bb045209e23459ebb5f9c8f6f219dafb85e2ec59d5fe841f1c4463"; // Have node0 initiate a channel to node1 with aforementioned parameters let channel_id_temp1 = initiator_node.node.create_dual_funded_channel(acceptor_node.node.get_our_node_id(), channel_value_sat, None, 42, None).unwrap(); @@ -1211,7 +1212,7 @@ fn test_v2_payment_splice_in_payment() { let _res = acceptor_node.node.handle_open_channel_v2(&initiator_node.node.get_our_node_id(), &open_channel2_message.clone()); // Extract the accept channel message from node1 to node0 let accept_channel2_message = get_event_msg!(acceptor_node, MessageSendEvent::SendAcceptChannelV2, initiator_node.node.get_our_node_id()); - assert_eq!(accept_channel2_message.temporary_channel_id.to_string(), expected_temporary_channel_id); + assert_eq!(accept_channel2_message.common_fields.temporary_channel_id.to_string(), expected_temporary_channel_id); let _res = initiator_node.node.handle_accept_channel_v2(&acceptor_node.node.get_our_node_id(), &accept_channel2_message.clone()); @@ -1322,11 +1323,11 @@ fn test_v2_payment_splice_in_payment() { let _res = acceptor_node.node.handle_channel_ready(&initiator_node.node.get_our_node_id(), &channel_ready_message1); let _ev = get_event!(acceptor_node, Event::ChannelReady); - let announcement_signatures2 = get_event_msg!(acceptor_node, MessageSendEvent::SendAnnouncementSignatures, initiator_node.node.get_our_node_id()); + let _announcement_signatures2 = get_event_msg!(acceptor_node, MessageSendEvent::SendAnnouncementSignatures, initiator_node.node.get_our_node_id()); let _res = initiator_node.node.handle_channel_ready(&acceptor_node.node.get_our_node_id(), &channel_ready_message2); let _ev = get_event!(initiator_node, Event::ChannelReady); - let announcement_signatures1 = get_event_msg!(initiator_node, MessageSendEvent::SendAnnouncementSignatures, acceptor_node.node.get_our_node_id()); + let _announcement_signatures1 = get_event_msg!(initiator_node, MessageSendEvent::SendAnnouncementSignatures, acceptor_node.node.get_our_node_id()); // let (announcement1, update1, update2) = create_chan_between_nodes_with_value_b(&initiator_node, &acceptor_node, &(channel_ready_message1, announcement_signatures1)); // `update_nodes_with_chan_announce`(&nodes, initiator_node_index, acceptor_node_index, &announcement1, &update1, &update2); @@ -1361,7 +1362,7 @@ fn test_v2_payment_splice_in_payment() { // === Send a payment let payment1_amount_msat = 6001_000; - let payment_res = send_payment(&initiator_node, &[acceptor_node], payment1_amount_msat); + let _payment_res = send_payment(&initiator_node, &[acceptor_node], payment1_amount_msat); assert_eq!(initiator_node.node.list_channels().len(), 1); { diff --git a/lightning/src/ln/inbound_payment.rs b/lightning/src/ln/inbound_payment.rs index 2ee93a6c46e..eeae514fdf3 100644 --- a/lightning/src/ln/inbound_payment.rs +++ b/lightning/src/ln/inbound_payment.rs @@ -18,8 +18,8 @@ use crate::sign::{KeyMaterial, EntropySource}; use crate::ln::{PaymentHash, PaymentPreimage, PaymentSecret}; use crate::ln::msgs; use crate::ln::msgs::MAX_VALUE_MSAT; -use crate::util::chacha20::ChaCha20; -use crate::util::crypto::hkdf_extract_expand_5x; +use crate::crypto::chacha20::ChaCha20; +use crate::crypto::utils::hkdf_extract_expand_5x; use crate::util::errors::APIError; use crate::util::logger::Logger; diff --git a/lightning/src/ln/interactivetxs.rs b/lightning/src/ln/interactivetxs.rs index 0fd1bbb4ab0..f3fa0a79674 100644 --- a/lightning/src/ln/interactivetxs.rs +++ b/lightning/src/ln/interactivetxs.rs @@ -7,23 +7,25 @@ // You may not use this file except in accordance with one or both of these // licenses. -use crate::prelude::*; use crate::io_extras::sink; +use crate::prelude::*; use core::ops::Deref; use bitcoin::blockdata::constants::WITNESS_SCALE_FACTOR; use bitcoin::consensus::Encodable; -use bitcoin::locktime::absolute::LockTime; use bitcoin::policy::MAX_STANDARD_TX_WEIGHT; use bitcoin::secp256k1::PublicKey; use bitcoin::secp256k1::ecdsa::Signature; -use bitcoin::{OutPoint, Sequence, Transaction, TxIn, TxOut, Witness}; +use bitcoin::Witness; +use bitcoin::{ + absolute::LockTime as AbsoluteLockTime, OutPoint, Sequence, Transaction, TxIn, TxOut, +}; use crate::chain::chaininterface::fee_for_weight; use crate::events::bump_transaction::{BASE_INPUT_WEIGHT, EMPTY_SCRIPT_SIG_WEIGHT}; use crate::ln::channel::TOTAL_BITCOIN_SUPPLY_SATOSHIS; -use crate::ln::{ChannelId, msgs}; use crate::ln::msgs::{CommitmentSigned, SerialId, TxSignatures}; +use crate::ln::{msgs, ChannelId}; use crate::sign::EntropySource; use crate::util::ser::TransactionU16LenLimited; @@ -40,11 +42,11 @@ const MAX_RECEIVED_TX_ADD_OUTPUT_COUNT: u16 = 4096; const MAX_INPUTS_OUTPUTS_COUNT: usize = 252; trait SerialIdExt { - fn is_valid_for_initiator(&self) -> bool; + fn is_for_initiator(&self) -> bool; } impl SerialIdExt for SerialId { - fn is_valid_for_initiator(&self) -> bool { + fn is_for_initiator(&self) -> bool { self % 2 == 0 } } @@ -66,6 +68,8 @@ pub enum AbortReason { BelowDustLimit, InvalidOutputScript, InsufficientFees, + OutputsValueExceedsInputsValue, + InvalidTx, } #[derive(Debug, Clone, PartialEq)] @@ -94,7 +98,9 @@ pub struct InteractiveTxSigningSession { } impl InteractiveTxSigningSession { - pub fn received_commitment_signed(&mut self, commitment_signed: CommitmentSigned) -> Option { + pub fn received_commitment_signed( + &mut self, commitment_signed: CommitmentSigned, + ) -> Option { self.received_commitment_signed = Some(commitment_signed); if self.holder_sends_tx_signatures_first { self.holder_tx_signatures.clone() @@ -103,15 +109,23 @@ impl InteractiveTxSigningSession { } } - pub fn received_tx_signatures(&mut self, tx_signatures: TxSignatures) -> (Option, Option) { + pub fn received_tx_signatures( + &mut self, tx_signatures: TxSignatures, + ) -> (Option, Option) { if self.counterparty_tx_signatures.is_some() { return (None, None); }; self.counterparty_tx_signatures = Some(tx_signatures.clone()); - self.inputs.iter_mut().filter_map(|input| match input { - InteractiveTxInput::CounterpartyInput(TxInputWithPrevOutput { input, .. }) => Some(input), - _ => None, - }).zip(tx_signatures.witnesses.into_iter()).for_each(|(input, witness)| input.witness = witness); + self.inputs + .iter_mut() + .filter_map(|input| match input { + InteractiveTxInput::CounterpartyInput(TxInputWithPrevOutput { input, .. }) => { + Some(input) + }, + _ => None, + }) + .zip(tx_signatures.witnesses.into_iter()) + .for_each(|(input, witness)| input.witness = witness); let holder_tx_signatures = if !self.holder_sends_tx_signatures_first { self.holder_tx_signatures.clone() @@ -134,18 +148,23 @@ impl InteractiveTxSigningSession { pub fn provide_holder_witnesses( &mut self, channel_id: ChannelId, witnesses: Vec, shared_signature: Option, ) -> Option { - self.inputs.iter_mut().filter_map(|input| match input { - InteractiveTxInput::HolderInput(TxInputWithPrevOutput { input, .. }) => Some(input), - _ => None, - }).zip(witnesses.iter()).for_each(|(input, witness)| input.witness = witness.clone()); + self.inputs + .iter_mut() + .filter_map(|input| match input { + InteractiveTxInput::HolderInput(TxInputWithPrevOutput { input, .. }) => Some(input), + _ => None, + }) + .zip(witnesses.iter()) + .for_each(|(input, witness)| input.witness = witness.clone()); self.holder_tx_signatures = Some(TxSignatures { channel_id, tx_hash: self.constructed_transaction.txid(), - witnesses, + witnesses: witnesses.into_iter().map(|witness| witness).collect(), tlvs: shared_signature, }); - if self.received_commitment_signed.is_some() && - (self.holder_sends_tx_signatures_first || self.counterparty_tx_signatures.is_some()) { + if self.received_commitment_signed.is_some() + && (self.holder_sends_tx_signatures_first || self.counterparty_tx_signatures.is_some()) + { self.holder_tx_signatures.clone() } else { None @@ -153,24 +172,40 @@ impl InteractiveTxSigningSession { } pub fn counterparty_inputs_count(&self) -> usize { - self.inputs.iter().filter(|input| match input { - InteractiveTxInput::CounterpartyInput(_) => true, _ => false }).count() + self.inputs + .iter() + .filter(|input| match input { + InteractiveTxInput::CounterpartyInput(_) => true, + _ => false, + }) + .count() } pub fn holder_inputs_count(&self) -> usize { - self.inputs.iter().filter(|input| match input { - InteractiveTxInput::HolderInput(_) => true, _ => false }).count() + self.inputs + .iter() + .filter(|input| match input { + InteractiveTxInput::HolderInput(_) => true, + _ => false, + }) + .count() } fn finalize_funding_tx(&mut self) -> Transaction { - self.constructed_transaction.input = self.inputs.iter().map(|interactive_tx_input| match interactive_tx_input { - InteractiveTxInput::HolderInput(input_with_prevout) => input_with_prevout.input.clone(), - InteractiveTxInput::CounterpartyInput(input_with_prevout) => input_with_prevout.input.clone(), - }).collect(); + self.constructed_transaction.input = self + .inputs + .iter() + .map(|interactive_tx_input| match interactive_tx_input { + InteractiveTxInput::HolderInput(input_with_prevout) => { + input_with_prevout.input.clone() + }, + InteractiveTxInput::CounterpartyInput(input_with_prevout) => { + input_with_prevout.input.clone() + }, + }) + .collect(); self.constructed_transaction.clone() } - - pub fn counterparty_tx_signatures(&self) -> Option<&TxSignatures> { self.counterparty_tx_signatures.as_ref() } } #[derive(Debug)] @@ -183,39 +218,47 @@ struct NegotiationContext { inputs: HashMap, prevtx_outpoints: HashSet, outputs: HashMap, - tx_locktime: LockTime, + tx_locktime: AbsoluteLockTime, feerate_sat_per_kw: u32, + to_remote_value: u64, } impl NegotiationContext { fn is_serial_id_valid_for_counterparty(&self, serial_id: &SerialId) -> bool { // A received `SerialId`'s parity must match the role of the counterparty. - self.holder_is_initiator == !serial_id.is_valid_for_initiator() + self.holder_is_initiator == !serial_id.is_for_initiator() } - fn holder_inputs_contributed(&self) -> impl Iterator + Clone { - self.inputs.iter() - .filter_map(move |(_, input)| match input { - InteractiveTxInput::HolderInput(tx_input_with_prev_output) => Some(tx_input_with_prev_output), - _ => None, - }) + fn holder_inputs_contributed(&self) -> impl Iterator + Clone { + self.inputs.iter().filter_map(move |(_, input)| match input { + InteractiveTxInput::HolderInput(tx_input_with_prev_output) => { + Some(tx_input_with_prev_output) + }, + _ => None, + }) } - fn counterparty_inputs_contributed(&self) -> impl Iterator + Clone { - self.inputs.iter() - .filter_map(move |(_, input)| match input { - InteractiveTxInput::CounterpartyInput(tx_input_with_prev_output) => Some(tx_input_with_prev_output), - _ => None, - }) + fn counterparty_inputs_contributed( + &self, + ) -> impl Iterator + Clone { + self.inputs.iter().filter_map(move |(_, input)| match input { + InteractiveTxInput::CounterpartyInput(tx_input_with_prev_output) => { + Some(tx_input_with_prev_output) + }, + _ => None, + }) } - fn counterparty_outputs_contributed(&self) -> impl Iterator + Clone{ - self.outputs.iter() + fn counterparty_outputs_contributed(&self) -> impl Iterator + Clone { + self.outputs + .iter() .filter(move |(serial_id, _)| self.is_serial_id_valid_for_counterparty(serial_id)) - .map(|(_, input_with_prevout)| input_with_prevout) + .map(|(_, output)| output) } - fn should_holder_send_tx_signatures_first(&self, holder_inputs_value: u64, counterparty_inputs_value: u64) -> bool { + fn should_holder_send_tx_signatures_first( + &self, holder_inputs_value: u64, counterparty_inputs_value: u64, + ) -> bool { // There is a strict ordering for `tx_signatures` exchange to prevent deadlocks. if holder_inputs_value == counterparty_inputs_value { // If the amounts are the same then the peer with the lowest pubkey lexicographically sends its @@ -227,7 +270,7 @@ impl NegotiationContext { } } - fn remote_tx_add_input(&mut self, msg: &msgs::TxAddInput) -> Result<(), AbortReason> { + fn received_tx_add_input(&mut self, msg: &msgs::TxAddInput) -> Result<(), AbortReason> { // The interactive-txs spec calls for us to fail negotiation if the `prevtx` we receive is // invalid. However, we would not need to account for this explicit negotiation failure // mode here since `PeerManager` would already disconnect the peer if the `prevtx` is @@ -240,6 +283,14 @@ impl NegotiationContext { return Err(AbortReason::IncorrectSerialIdParity); } + self.received_tx_add_input_count += 1; + if self.received_tx_add_input_count > MAX_RECEIVED_TX_ADD_INPUT_COUNT { + // The receiving node: + // - MUST fail the negotiation if: + // - if has received 4096 `tx_add_input` messages during this negotiation + return Err(AbortReason::ReceivedTooManyTxAddInputs); + } + if msg.sequence >= 0xFFFFFFFE { // The receiving node: // - MUST fail the negotiation if: @@ -247,7 +298,8 @@ impl NegotiationContext { return Err(AbortReason::IncorrectInputSequenceValue); } - let transaction = msg.prevtx.clone().into_transaction(); + let transaction = msg.prevtx.as_transaction(); + let txid = transaction.txid(); if let Some(tx_out) = transaction.output.get(msg.prevtx_out as usize) { if !tx_out.script_pubkey.is_witness_program() { @@ -255,10 +307,9 @@ impl NegotiationContext { // - MUST fail the negotiation if: // - the `scriptPubKey` is not a witness program return Err(AbortReason::PrevTxOutInvalid); - } else if !self.prevtx_outpoints.insert(OutPoint { - txid: transaction.txid(), - vout: msg.prevtx_out, - }) { + } + + if !self.prevtx_outpoints.insert(OutPoint { txid, vout: msg.prevtx_out }) { // The receiving node: // - MUST fail the negotiation if: // - the `prevtx` and `prevtx_vout` are identical to a previously added @@ -272,15 +323,7 @@ impl NegotiationContext { return Err(AbortReason::PrevTxOutInvalid); } - self.received_tx_add_input_count += 1; - if self.received_tx_add_input_count > MAX_RECEIVED_TX_ADD_INPUT_COUNT { - // The receiving node: - // - MUST fail the negotiation if: - // - if has received 4096 `tx_add_input` messages during this negotiation - return Err(AbortReason::ReceivedTooManyTxAddInputs); - } - - let prev_out = if let Some(prev_out) = msg.prevtx.0.output.get(msg.prevtx_out as usize) { + let prev_out = if let Some(prev_out) = transaction.output.get(msg.prevtx_out as usize) { prev_out.clone() } else { return Err(AbortReason::PrevTxOutInvalid); @@ -291,40 +334,37 @@ impl NegotiationContext { // - the `serial_id` is already included in the transaction return Err(AbortReason::DuplicateSerialId); } - let prev_outpoint = OutPoint { - txid: transaction.txid(), - vout: msg.prevtx_out, - }; - self.inputs.insert(msg.serial_id, InteractiveTxInput::CounterpartyInput( - TxInputWithPrevOutput { + let prev_outpoint = OutPoint { txid, vout: msg.prevtx_out }; + self.inputs.entry(msg.serial_id).or_insert_with(|| { + InteractiveTxInput::CounterpartyInput(TxInputWithPrevOutput { input: TxIn { previous_output: prev_outpoint.clone(), sequence: Sequence(msg.sequence), ..Default::default() }, prev_output: prev_out, - })); + }) + }); self.prevtx_outpoints.insert(prev_outpoint); Ok(()) } - fn remote_tx_remove_input(&mut self, msg: &msgs::TxRemoveInput) -> Result<(), AbortReason> { + fn received_tx_remove_input(&mut self, msg: &msgs::TxRemoveInput) -> Result<(), AbortReason> { if !self.is_serial_id_valid_for_counterparty(&msg.serial_id) { return Err(AbortReason::IncorrectSerialIdParity); } - if let Some(_) = self.inputs.remove(&msg.serial_id) { - Ok(()) - } else { + self.inputs + .remove(&msg.serial_id) // The receiving node: // - MUST fail the negotiation if: // - the input or output identified by the `serial_id` was not added by the sender // - the `serial_id` does not correspond to a currently added input - Err(AbortReason::SerialIdUnknown) - } + .ok_or(AbortReason::SerialIdUnknown) + .map(|_| ()) } - fn remote_tx_add_output(&mut self, msg: &msgs::TxAddOutput) -> Result<(), AbortReason> { + fn received_tx_add_output(&mut self, msg: &msgs::TxAddOutput) -> Result<(), AbortReason> { // The receiving node: // - MUST fail the negotiation if: // - the serial_id has the wrong parity @@ -346,9 +386,14 @@ impl NegotiationContext { // - the sats amount is less than the dust_limit return Err(AbortReason::BelowDustLimit); } - // TODO (Spec Clarification https://github.com/lightning/bolts/pull/851/files#r1390457718): - // Check if we should validate cumulative output value so far, instead of just the value of this input. - if msg.sats > TOTAL_BITCOIN_SUPPLY_SATOSHIS { + + // Check that adding this output would not cause the total output value to exceed the total + // bitcoin supply. + let mut outputs_value: u64 = 0; + for output in self.outputs.iter() { + outputs_value = outputs_value.saturating_add(output.1.value); + } + if outputs_value.saturating_add(msg.sats) > TOTAL_BITCOIN_SUPPLY_SATOSHIS { // The receiving node: // - MUST fail the negotiation if: // - the sats amount is greater than 2,100,000,000,000,000 (TOTAL_BITCOIN_SUPPLY_SATOSHIS) @@ -358,7 +403,16 @@ impl NegotiationContext { // The receiving node: // - MUST accept P2WSH, P2WPKH, P2TR scripts // - MAY fail the negotiation if script is non-standard - if !msg.script.is_v0_p2wpkh() && !msg.script.is_v0_p2wsh() && !msg.script.is_v1_p2tr() { + // + // We can actually be a bit looser than the above as only witness version 0 has special + // length-based standardness constraints to match similar consensus rules. All witness scripts + // with witness versions V1 and up are always considered standard. Yes, the scripts can be + // anyone-can-spend-able, but if our counterparty wants to add an output like that then it's none + // of our concern really ĀÆ\_(惄)_/ĀÆ + if !msg.script.is_v0_p2wpkh() + && !msg.script.is_v0_p2wsh() + && msg.script.witness_version().map(|v| v.to_num() < 1).unwrap_or(true) + { return Err(AbortReason::InvalidOutputScript); } @@ -369,15 +423,12 @@ impl NegotiationContext { return Err(AbortReason::DuplicateSerialId); } - let output = TxOut { - value: msg.sats, - script_pubkey: msg.script.clone(), - }; - self.outputs.insert(msg.serial_id, output); + let output = TxOut { value: msg.sats, script_pubkey: msg.script.clone() }; + self.outputs.entry(msg.serial_id).or_insert(output); Ok(()) } - fn remote_tx_remove_output(&mut self, msg: &msgs::TxRemoveOutput) -> Result<(), AbortReason> { + fn received_tx_remove_output(&mut self, msg: &msgs::TxRemoveOutput) -> Result<(), AbortReason> { if !self.is_serial_id_valid_for_counterparty(&msg.serial_id) { return Err(AbortReason::IncorrectSerialIdParity); } @@ -392,140 +443,159 @@ impl NegotiationContext { } } - fn local_tx_add_input(&mut self, msg: &msgs::TxAddInput) { - let tx = msg.prevtx.clone().into_transaction(); + fn sent_tx_add_input(&mut self, msg: &msgs::TxAddInput) { + let tx = msg.prevtx.as_transaction(); let input = TxIn { - previous_output: OutPoint { - txid: tx.txid(), - vout: msg.prevtx_out, - }, + previous_output: OutPoint { txid: tx.txid(), vout: msg.prevtx_out }, sequence: Sequence(msg.sequence), ..Default::default() }; debug_assert!((msg.prevtx_out as usize) < tx.output.len()); let prev_output = &tx.output[msg.prevtx_out as usize]; self.prevtx_outpoints.insert(input.previous_output.clone()); - self.inputs.insert(msg.serial_id, InteractiveTxInput::HolderInput(TxInputWithPrevOutput { - input, - prev_output: prev_output.clone(), - })); + self.inputs.insert( + msg.serial_id, + InteractiveTxInput::HolderInput(TxInputWithPrevOutput { + input, + prev_output: prev_output.clone(), + }), + ); } - fn local_tx_add_output(&mut self, msg: &msgs::TxAddOutput) { - self.outputs.insert(msg.serial_id, TxOut { - value: msg.sats, - script_pubkey: msg.script.clone(), - }); + fn sent_tx_add_output(&mut self, msg: &msgs::TxAddOutput) { + self.outputs + .insert(msg.serial_id, TxOut { value: msg.sats, script_pubkey: msg.script.clone() }); } - fn local_tx_remove_input(&mut self, msg: &msgs::TxRemoveInput) { + fn sent_tx_remove_input(&mut self, msg: &msgs::TxRemoveInput) { self.inputs.remove(&msg.serial_id); } - fn local_tx_remove_output(&mut self, msg: &msgs::TxRemoveOutput) { + fn sent_tx_remove_output(&mut self, msg: &msgs::TxRemoveOutput) { self.outputs.remove(&msg.serial_id); } - fn build_transaction(mut self) -> Result<(Vec, Transaction), AbortReason> { + fn build_transaction(self) -> Result<(Vec, Transaction), AbortReason> { // The receiving node: // MUST fail the negotiation if: // - the peer's total input satoshis is less than their outputs - let counterparty_inputs_contributed = self.counterparty_inputs_contributed(); - let counterparty_inputs_value: u64 = counterparty_inputs_contributed.clone() - .map(|input| input.prev_output.value).sum(); - let counterparty_outputs_contributed = self.counterparty_outputs_contributed(); - let counterparty_outputs_value: u64 = counterparty_outputs_contributed.clone() - .map(|output| output.value).sum(); - if counterparty_inputs_value < counterparty_outputs_value { - return Err(AbortReason::InsufficientFees); + let mut counterparty_inputs_value: u64 = 0; + let mut counterparty_outputs_value: u64 = 0; + for input in self.counterparty_inputs_contributed() { + counterparty_inputs_value = + counterparty_inputs_value.saturating_add(input.prev_output.value); + } + for output in self.counterparty_outputs_contributed() { + counterparty_outputs_value = counterparty_outputs_value.saturating_add(output.value); + } + // ...actually the counterparty might be splicing out, so that their balance also contributes + // to the total input value. + if counterparty_inputs_value.saturating_add(self.to_remote_value) + < counterparty_outputs_value + { + return Err(AbortReason::OutputsValueExceedsInputsValue); } // - there are more than 252 inputs // - there are more than 252 outputs - if self.inputs.len() > MAX_INPUTS_OUTPUTS_COUNT || self.outputs.len() > MAX_INPUTS_OUTPUTS_COUNT { + if self.inputs.len() > MAX_INPUTS_OUTPUTS_COUNT + || self.outputs.len() > MAX_INPUTS_OUTPUTS_COUNT + { return Err(AbortReason::ExceededNumberOfInputsOrOutputs); } + // TODO: How do we enforce their fees cover the witness without knowing its expected length? + const INPUT_WEIGHT: u64 = BASE_INPUT_WEIGHT + EMPTY_SCRIPT_SIG_WEIGHT; + + // - the peer's paid feerate does not meet or exceed the agreed feerate (based on the minimum fee). + let counterparty_output_weight_contributed: u64 = self + .counterparty_outputs_contributed() + .map(|output| { + (8 /* value */ + output.script_pubkey.consensus_encode(&mut sink()).unwrap() as u64) + * WITNESS_SCALE_FACTOR as u64 + }) + .sum(); + let counterparty_weight_contributed = counterparty_output_weight_contributed + + self.counterparty_inputs_contributed().count() as u64 * INPUT_WEIGHT; + let counterparty_fees_contributed = + counterparty_inputs_value.saturating_sub(counterparty_outputs_value); + let mut required_counterparty_contribution_fee = + fee_for_weight(self.feerate_sat_per_kw, counterparty_weight_contributed); + if !self.holder_is_initiator { + // if is the non-initiator: + // - the initiator's fees do not cover the common fields (version, segwit marker + flag, + // input count, output count, locktime) + let tx_common_fields_weight = + (4 /* version */ + 4 /* locktime */ + 1 /* input count */ + 1 /* output count */) * + WITNESS_SCALE_FACTOR as u64 + 2 /* segwit marker + flag */; + let tx_common_fields_fee = + fee_for_weight(self.feerate_sat_per_kw, tx_common_fields_weight); + required_counterparty_contribution_fee += tx_common_fields_fee; + } + if counterparty_fees_contributed < required_counterparty_contribution_fee { + return Err(AbortReason::InsufficientFees); + } + // Inputs and outputs must be sorted by serial_id - let mut inputs = self.inputs.iter().collect::>(); - let mut outputs = self.outputs.iter().collect::>(); + let mut inputs = self.inputs.into_iter().collect::>(); + let mut outputs = self.outputs.into_iter().collect::>(); inputs.sort_unstable_by_key(|(serial_id, _)| *serial_id); outputs.sort_unstable_by_key(|(serial_id, _)| *serial_id); - let tx_input = inputs.iter().filter_map(|(_, input)| match input { - InteractiveTxInput::HolderInput(TxInputWithPrevOutput { input, .. }) => Some(input.clone()), - InteractiveTxInput::CounterpartyInput(TxInputWithPrevOutput { input, .. }) => Some(input.clone()), - }).collect(); + let tx_input = inputs + .iter() + .filter_map(|(_, input)| match input { + InteractiveTxInput::HolderInput(TxInputWithPrevOutput { input, .. }) => { + Some(input.clone()) + }, + InteractiveTxInput::CounterpartyInput(TxInputWithPrevOutput { input, .. }) => { + Some(input.clone()) + }, + }) + .collect(); let tx_to_validate = Transaction { version: 2, lock_time: self.tx_locktime, input: tx_input, - output: outputs.into_iter().map(|(_, output)| output.clone()).collect(), + output: outputs.into_iter().map(|(_, output)| output).collect(), }; if tx_to_validate.weight().to_wu() > MAX_STANDARD_TX_WEIGHT as u64 { return Err(AbortReason::TransactionTooLarge); } - // TODO: How do we enforce their fees cover the witness without knowing its expected length? - const INPUT_WEIGHT: u64 = BASE_INPUT_WEIGHT + EMPTY_SCRIPT_SIG_WEIGHT; - - // - the peer's paid feerate does not meet or exceed the agreed feerate (based on the minimum fee). - let counterparty_output_weight_contributed: u64 = counterparty_outputs_contributed.clone().map(|output| - (8 /* value */ + output.script_pubkey.consensus_encode(&mut sink()).unwrap() as u64) * - WITNESS_SCALE_FACTOR as u64 - ).sum(); - let counterparty_weight_contributed = counterparty_output_weight_contributed + - counterparty_inputs_contributed.clone().count() as u64 * INPUT_WEIGHT; - let counterparty_fees_contributed = - counterparty_inputs_value.saturating_sub(counterparty_outputs_value); - let mut required_counterparty_contribution_fee = fee_for_weight(self.feerate_sat_per_kw, counterparty_weight_contributed); - if !self.holder_is_initiator { - // if is the non-initiator: - // - the initiator's fees do not cover the common fields (version, segwit marker + flag, - // input count, output count, locktime) - let tx_common_fields_weight = - (4 /* version */ + 4 /* locktime */ + 1 /* input count */ + 1 /* output count */) * - WITNESS_SCALE_FACTOR as u64 + 2 /* segwit marker + flag */; - let tx_common_fields_fee = fee_for_weight(self.feerate_sat_per_kw, tx_common_fields_weight); - required_counterparty_contribution_fee += tx_common_fields_fee; - } - if counterparty_fees_contributed < required_counterparty_contribution_fee { - return Err(AbortReason::InsufficientFees); - } - let interactive_tx_inputs = inputs.into_iter().map(|(_, input)| input.clone()).collect(); Ok((interactive_tx_inputs, tx_to_validate)) } } -// Channel states that can receive `(send|receive)_tx_(add|remove)_(input|output)` +/// Channel states that can send & receive `tx_(add|remove)_(input|output)` and `tx_complete` trait State {} /// Category of states where we have sent some message to the counterparty, and we are waiting for /// a response. -trait LocalState: State { +trait MsgSentState: State { fn into_negotiation_context(self) -> NegotiationContext; } /// Category of states that our counterparty has put us in after we receive a message from them. -trait RemoteState: State { +trait MsgReceivedState: State { fn into_negotiation_context(self) -> NegotiationContext; } macro_rules! define_state { - (LOCAL_STATE, $state: ident, $doc: expr) => { + (MSG_SENT_STATE, $state: ident, $doc: expr) => { define_state!($state, NegotiationContext, $doc); - impl LocalState for $state { + impl MsgSentState for $state { fn into_negotiation_context(self) -> NegotiationContext { self.0 } } }; - (REMOTE_STATE, $state: ident, $doc: expr) => { + (MSG_RECEIVED_STATE, $state: ident, $doc: expr) => { define_state!($state, NegotiationContext, $doc); - impl RemoteState for $state { + impl MsgReceivedState for $state { fn into_negotiation_context(self) -> NegotiationContext { self.0 } @@ -539,12 +609,32 @@ macro_rules! define_state { }; } -define_state!(LOCAL_STATE, LocalChange, "We have sent a message to the counterparty that has affected our negotiation state."); -define_state!(LOCAL_STATE, LocalTxComplete, "We have sent a `tx_complete` message and are awaiting the counterparty's."); -define_state!(REMOTE_STATE, RemoteChange, "We have received a message from the counterparty that has affected our negotiation state."); -define_state!(REMOTE_STATE, RemoteTxComplete, "We have received a `tx_complete` message and the counterparty is awaiting ours."); +define_state!( + MSG_SENT_STATE, + MsgSentChange, + "We have sent a message to the counterparty that has affected our negotiation state." +); +define_state!( + MSG_SENT_STATE, + SentTxComplete, + "We have sent a `tx_complete` message and are awaiting the counterparty's." +); +define_state!( + MSG_RECEIVED_STATE, + MsgReceivedChange, + "We have received a message from the counterparty that has affected our negotiation state." +); +define_state!( + MSG_RECEIVED_STATE, + ReceivedTxComplete, + "We have received a `tx_complete` message and the counterparty is awaiting ours." +); define_state!(NegotiationComplete, InteractiveTxSigningSession, "We have exchanged consecutive `tx_complete` messages with the counterparty and the transaction negotiation is complete."); -define_state!(NegotiationAborted, AbortReason, "The negotiation has failed and cannot be continued."); +define_state!( + NegotiationAborted, + AbortReason, + "The negotiation has failed and cannot be continued." +); type StateTransitionResult = Result; @@ -553,37 +643,30 @@ trait StateTransition { } macro_rules! define_state_transitions { - (LOCAL_STATE, [$(DATA $data: ty, TRANSITION $transition: ident),+]) => { + (MSG_SENT_STATE, [$(DATA $data: ty, TRANSITION $transition: ident),+]) => { $( - impl StateTransition for S { - fn transition(self, data: $data) -> StateTransitionResult { + impl StateTransition for S { + fn transition(self, data: $data) -> StateTransitionResult { let mut context = self.into_negotiation_context(); - let _ = context.$transition(data)?; - Ok(RemoteChange(context)) + context.$transition(data)?; + Ok(MsgReceivedChange(context)) } } )* }; - (REMOTE_STATE, [$(DATA $data: ty, TRANSITION $transition: ident),+]) => { + (MSG_RECEIVED_STATE, [$(DATA $data: ty, TRANSITION $transition: ident),+]) => { $( - impl StateTransition for S { - fn transition(self, data: $data) -> StateTransitionResult { + impl StateTransition for S { + fn transition(self, data: $data) -> StateTransitionResult { let mut context = self.into_negotiation_context(); - let _ = context.$transition(data); - Ok(LocalChange(context)) + context.$transition(data); + Ok(MsgSentChange(context)) } } )* }; - (TX_COMPLETE_AS_ACK, $from_state: ident, $to_state: ident) => { - impl StateTransition<$to_state, &msgs::TxComplete> for $from_state { - fn transition(self, _data: &msgs::TxComplete) -> StateTransitionResult<$to_state> { - Ok($to_state(self.into_negotiation_context())) - } - } - }; - (TX_COMPLETE, $from_state: ident) => { - impl StateTransition for $from_state { + (TX_COMPLETE, $from_state: ident, $tx_complete_state: ident) => { + impl StateTransition for $tx_complete_state { fn transition(self, _data: &msgs::TxComplete) -> StateTransitionResult { let context = self.into_negotiation_context(); let holder_inputs_value = context.holder_inputs_contributed() @@ -606,37 +689,41 @@ macro_rules! define_state_transitions { Ok(NegotiationComplete(signing_session)) } } + + impl StateTransition<$tx_complete_state, &msgs::TxComplete> for $from_state { + fn transition(self, _data: &msgs::TxComplete) -> StateTransitionResult<$tx_complete_state> { + Ok($tx_complete_state(self.into_negotiation_context())) + } + } }; } // State transitions when we have sent our counterparty some messages and are waiting for them // to respond. -define_state_transitions!(LOCAL_STATE, [ - DATA &msgs::TxAddInput, TRANSITION remote_tx_add_input, - DATA &msgs::TxRemoveInput, TRANSITION remote_tx_remove_input, - DATA &msgs::TxAddOutput, TRANSITION remote_tx_add_output, - DATA &msgs::TxRemoveOutput, TRANSITION remote_tx_remove_output +define_state_transitions!(MSG_SENT_STATE, [ + DATA &msgs::TxAddInput, TRANSITION received_tx_add_input, + DATA &msgs::TxRemoveInput, TRANSITION received_tx_remove_input, + DATA &msgs::TxAddOutput, TRANSITION received_tx_add_output, + DATA &msgs::TxRemoveOutput, TRANSITION received_tx_remove_output ]); // State transitions when we have received some messages from our counterparty and we should // respond. -define_state_transitions!(REMOTE_STATE, [ - DATA &msgs::TxAddInput, TRANSITION local_tx_add_input, - DATA &msgs::TxRemoveInput, TRANSITION local_tx_remove_input, - DATA &msgs::TxAddOutput, TRANSITION local_tx_add_output, - DATA &msgs::TxRemoveOutput, TRANSITION local_tx_remove_output +define_state_transitions!(MSG_RECEIVED_STATE, [ + DATA &msgs::TxAddInput, TRANSITION sent_tx_add_input, + DATA &msgs::TxRemoveInput, TRANSITION sent_tx_remove_input, + DATA &msgs::TxAddOutput, TRANSITION sent_tx_add_output, + DATA &msgs::TxRemoveOutput, TRANSITION sent_tx_remove_output ]); -define_state_transitions!(TX_COMPLETE_AS_ACK, LocalChange, RemoteTxComplete); -define_state_transitions!(TX_COMPLETE_AS_ACK, RemoteChange, LocalTxComplete); -define_state_transitions!(TX_COMPLETE, LocalTxComplete); -define_state_transitions!(TX_COMPLETE, RemoteTxComplete); +define_state_transitions!(TX_COMPLETE, MsgSentChange, ReceivedTxComplete); +define_state_transitions!(TX_COMPLETE, MsgReceivedChange, SentTxComplete); #[derive(Debug)] enum StateMachine { Indeterminate, - LocalChange(LocalChange), - RemoteChange(RemoteChange), - LocalTxComplete(LocalTxComplete), - RemoteTxComplete(RemoteTxComplete), + MsgSentChange(MsgSentChange), + MsgReceivedChange(MsgReceivedChange), + SentTxComplete(SentTxComplete), + ReceivedTxComplete(ReceivedTxComplete), NegotiationComplete(NegotiationComplete), NegotiationAborted(NegotiationAborted), } @@ -661,21 +748,13 @@ macro_rules! define_state_machine_transitions { } } }; - (LOCAL_OR_REMOTE_CHANGE, $to_local_transition: ident, $to_remote_transition: ident, $msg: ty) => { - define_state_machine_transitions!($to_local_transition, $msg, [ - FROM RemoteChange, TO LocalChange, - FROM RemoteTxComplete, TO LocalChange - ]); - define_state_machine_transitions!($to_remote_transition, $msg, [ - FROM LocalChange, TO RemoteChange, - FROM LocalTxComplete, TO RemoteChange - ]); - }; } impl StateMachine { - fn new(holder_node_id: PublicKey, counterparty_node_id: PublicKey, feerate_sat_per_kw: u32, - is_initiator: bool, tx_locktime: LockTime) -> Self { + fn new( + holder_node_id: PublicKey, counterparty_node_id: PublicKey, feerate_sat_per_kw: u32, + is_initiator: bool, tx_locktime: AbsoluteLockTime, to_remote_value: u64, + ) -> Self { let context = NegotiationContext { tx_locktime, holder_node_id, @@ -683,44 +762,74 @@ impl StateMachine { holder_is_initiator: is_initiator, received_tx_add_input_count: 0, received_tx_add_output_count: 0, - inputs: HashMap::new(), - prevtx_outpoints: HashSet::new(), - outputs: HashMap::new(), + inputs: new_hash_map(), + prevtx_outpoints: new_hash_set(), + outputs: new_hash_map(), feerate_sat_per_kw, + to_remote_value, }; if is_initiator { - Self::RemoteChange(RemoteChange(context)) + Self::MsgReceivedChange(MsgReceivedChange(context)) } else { - Self::LocalChange(LocalChange(context)) + Self::MsgSentChange(MsgSentChange(context)) } } - define_state_machine_transitions!( - LOCAL_OR_REMOTE_CHANGE, local_tx_add_input, remote_tx_add_input, &msgs::TxAddInput - ); - define_state_machine_transitions!( - LOCAL_OR_REMOTE_CHANGE, local_tx_add_output, remote_tx_add_output, &msgs::TxAddOutput - ); - define_state_machine_transitions!( - LOCAL_OR_REMOTE_CHANGE, local_tx_remove_input, remote_tx_remove_input, &msgs::TxRemoveInput - ); - define_state_machine_transitions!( - LOCAL_OR_REMOTE_CHANGE, local_tx_remove_output, remote_tx_remove_output, &msgs::TxRemoveOutput - ); - define_state_machine_transitions!(local_tx_complete, &msgs::TxComplete, [ - FROM RemoteChange, TO LocalTxComplete, - FROM RemoteTxComplete, TO NegotiationComplete + // TxAddInput + define_state_machine_transitions!(sent_tx_add_input, &msgs::TxAddInput, [ + FROM MsgReceivedChange, TO MsgSentChange, + FROM ReceivedTxComplete, TO MsgSentChange + ]); + define_state_machine_transitions!(received_tx_add_input, &msgs::TxAddInput, [ + FROM MsgSentChange, TO MsgReceivedChange, + FROM SentTxComplete, TO MsgReceivedChange + ]); + + // TxAddOutput + define_state_machine_transitions!(sent_tx_add_output, &msgs::TxAddOutput, [ + FROM MsgReceivedChange, TO MsgSentChange, + FROM ReceivedTxComplete, TO MsgSentChange + ]); + define_state_machine_transitions!(received_tx_add_output, &msgs::TxAddOutput, [ + FROM MsgSentChange, TO MsgReceivedChange, + FROM SentTxComplete, TO MsgReceivedChange + ]); + + // TxRemoveInput + define_state_machine_transitions!(sent_tx_remove_input, &msgs::TxRemoveInput, [ + FROM MsgReceivedChange, TO MsgSentChange, + FROM ReceivedTxComplete, TO MsgSentChange + ]); + define_state_machine_transitions!(received_tx_remove_input, &msgs::TxRemoveInput, [ + FROM MsgSentChange, TO MsgReceivedChange, + FROM SentTxComplete, TO MsgReceivedChange + ]); + + // TxRemoveOutput + define_state_machine_transitions!(sent_tx_remove_output, &msgs::TxRemoveOutput, [ + FROM MsgReceivedChange, TO MsgSentChange, + FROM ReceivedTxComplete, TO MsgSentChange ]); - define_state_machine_transitions!(remote_tx_complete, &msgs::TxComplete, [ - FROM LocalChange, TO RemoteTxComplete, - FROM LocalTxComplete, TO NegotiationComplete + define_state_machine_transitions!(received_tx_remove_output, &msgs::TxRemoveOutput, [ + FROM MsgSentChange, TO MsgReceivedChange, + FROM SentTxComplete, TO MsgReceivedChange + ]); + + // TxComplete + define_state_machine_transitions!(sent_tx_complete, &msgs::TxComplete, [ + FROM MsgReceivedChange, TO SentTxComplete, + FROM ReceivedTxComplete, TO NegotiationComplete + ]); + define_state_machine_transitions!(received_tx_complete, &msgs::TxComplete, [ + FROM MsgSentChange, TO ReceivedTxComplete, + FROM SentTxComplete, TO NegotiationComplete ]); } pub struct InteractiveTxConstructor { state_machine: StateMachine, channel_id: ChannelId, - inputs_to_contribute: Vec<(SerialId, TxIn, Transaction)>, + inputs_to_contribute: Vec<(SerialId, TxIn, TransactionU16LenLimited)>, outputs_to_contribute: Vec<(SerialId, TxOut)>, } @@ -741,12 +850,15 @@ macro_rules! do_state_transition { }}; } -fn generate_local_serial_id(entropy_source: &ES, is_initiator: bool) -> SerialId where ES::Target: EntropySource { +fn generate_holder_serial_id(entropy_source: &ES, is_initiator: bool) -> SerialId +where + ES::Target: EntropySource, +{ let rand_bytes = entropy_source.get_secure_random_bytes(); let mut serial_id_bytes = [0u8; 8]; serial_id_bytes.copy_from_slice(&rand_bytes[..8]); let mut serial_id = u64::from_be_bytes(serial_id_bytes); - if serial_id.is_valid_for_initiator() != is_initiator { + if serial_id.is_for_initiator() != is_initiator { serial_id ^= 1; } serial_id @@ -754,35 +866,55 @@ fn generate_local_serial_id(entropy_source: &ES, is_initiator: bool) impl InteractiveTxConstructor { pub fn new( - entropy_source: &ES, channel_id: ChannelId, feerate_sat_per_kw: u32, holder_node_id: PublicKey, - counterparty_node_id: PublicKey, is_initiator: bool, tx_locktime: LockTime, - inputs_to_contribute: Vec<(TxIn, Transaction)>, outputs_to_contribute: Vec, + entropy_source: &ES, channel_id: ChannelId, feerate_sat_per_kw: u32, + holder_node_id: PublicKey, counterparty_node_id: PublicKey, is_initiator: bool, + tx_locktime: AbsoluteLockTime, inputs_to_contribute: Vec<(TxIn, TransactionU16LenLimited)>, + outputs_to_contribute: Vec, to_remote_value: u64, ) -> (Self, Option) where ES::Target: EntropySource, { - let state_machine = StateMachine::new(holder_node_id, counterparty_node_id, feerate_sat_per_kw, is_initiator, tx_locktime); - let inputs_to_contribute = inputs_to_contribute.into_iter().map(|(input, tx)| { - let serial_id = generate_local_serial_id(entropy_source, is_initiator); - (serial_id, input, tx) - }).collect(); - let outputs_to_contribute = outputs_to_contribute.into_iter().map(|output| { - let serial_id = generate_local_serial_id(entropy_source, is_initiator); - (serial_id, output) - }).collect(); - let mut constructor = Self { - state_machine, - channel_id, - inputs_to_contribute, - outputs_to_contribute, - }; + let state_machine = StateMachine::new( + holder_node_id, + counterparty_node_id, + feerate_sat_per_kw, + is_initiator, + tx_locktime, + to_remote_value, + ); + let mut inputs_to_contribute: Vec<(SerialId, TxIn, TransactionU16LenLimited)> = + inputs_to_contribute + .into_iter() + .map(|(input, tx)| { + let serial_id = generate_holder_serial_id(entropy_source, is_initiator); + (serial_id, input, tx) + }) + .collect(); + // We'll sort by the randomly generated serial IDs, effectively shuffling the order of the inputs + // as the user passed them to us to avoid leaking any potential categorization of transactions + // before we pass any of the inputs to the counterparty. + inputs_to_contribute.sort_unstable_by_key(|(serial_id, _, _)| *serial_id); + let mut outputs_to_contribute: Vec<(SerialId, TxOut)> = outputs_to_contribute + .into_iter() + .map(|output| { + let serial_id = generate_holder_serial_id(entropy_source, is_initiator); + (serial_id, output) + }) + .collect(); + // In the same manner and for the same rationale as the inputs above, we'll shuffle the outputs. + outputs_to_contribute.sort_unstable_by_key(|(serial_id, _)| *serial_id); + let mut constructor = + Self { state_machine, channel_id, inputs_to_contribute, outputs_to_contribute }; let message_send = if is_initiator { - match constructor.do_local_state_transition() { + match constructor.do_sent_state_transition() { Ok(msg_send) => Some(msg_send), Err(_) => { - debug_assert!(false, "We should always be able to start our state machine successfully"); + debug_assert!( + false, + "We should always be able to start our state machine successfully" + ); None - } + }, } } else { None @@ -790,18 +922,18 @@ impl InteractiveTxConstructor { (constructor, message_send) } - fn do_local_state_transition(&mut self) -> Result { + fn do_sent_state_transition(&mut self) -> Result { // We first attempt to send inputs we want to add, then outputs. Once we are done sending // them both, then we always send tx_complete. - if let Some((serial_id, input, prev_tx)) = self.inputs_to_contribute.pop() { + if let Some((serial_id, input, prevtx)) = self.inputs_to_contribute.pop() { let msg = msgs::TxAddInput { channel_id: self.channel_id, serial_id, - prevtx: TransactionU16LenLimited(prev_tx), + prevtx, prevtx_out: input.previous_output.vout, sequence: input.sequence.to_consensus_u32(), }; - let _ = do_state_transition!(self, local_tx_add_input, &msg)?; + do_state_transition!(self, sent_tx_add_input, &msg)?; Ok(InteractiveTxMessageSend::TxAddInput(msg)) } else if let Some((serial_id, output)) = self.outputs_to_contribute.pop() { let msg = msgs::TxAddOutput { @@ -810,74 +942,93 @@ impl InteractiveTxConstructor { sats: output.value, script: output.script_pubkey, }; - let _ = do_state_transition!(self, local_tx_add_output, &msg)?; + do_state_transition!(self, sent_tx_add_output, &msg)?; Ok(InteractiveTxMessageSend::TxAddOutput(msg)) } else { let msg = msgs::TxComplete { channel_id: self.channel_id }; - let _ = do_state_transition!(self, local_tx_complete, &msg)?; + do_state_transition!(self, sent_tx_complete, &msg)?; Ok(InteractiveTxMessageSend::TxComplete(msg)) } } - pub fn handle_tx_add_input(&mut self, msg: &msgs::TxAddInput) -> Result { - let _ = do_state_transition!(self, remote_tx_add_input, msg)?; - self.do_local_state_transition() + pub fn handle_tx_add_input( + &mut self, msg: &msgs::TxAddInput, + ) -> Result { + do_state_transition!(self, received_tx_add_input, msg)?; + self.do_sent_state_transition() } - pub fn handle_tx_remove_input(&mut self, msg: &msgs::TxRemoveInput) -> Result { - let _ = do_state_transition!(self, remote_tx_remove_input, msg)?; - self.do_local_state_transition() + pub fn handle_tx_remove_input( + &mut self, msg: &msgs::TxRemoveInput, + ) -> Result { + do_state_transition!(self, received_tx_remove_input, msg)?; + self.do_sent_state_transition() } - pub fn handle_tx_add_output(&mut self, msg: &msgs::TxAddOutput) -> Result { - let _ = do_state_transition!(self, remote_tx_add_output, msg)?; - self.do_local_state_transition() + pub fn handle_tx_add_output( + &mut self, msg: &msgs::TxAddOutput, + ) -> Result { + do_state_transition!(self, received_tx_add_output, msg)?; + self.do_sent_state_transition() } - pub fn handle_tx_remove_output(&mut self, msg: &msgs::TxRemoveOutput) -> Result { - let _ = do_state_transition!(self, remote_tx_remove_output, msg)?; - self.do_local_state_transition() + pub fn handle_tx_remove_output( + &mut self, msg: &msgs::TxRemoveOutput, + ) -> Result { + do_state_transition!(self, received_tx_remove_output, msg)?; + self.do_sent_state_transition() } - pub fn handle_tx_complete(&mut self, msg: &msgs::TxComplete) -> Result<(Option, Option), AbortReason> { - let _ = do_state_transition!(self, remote_tx_complete, msg)?; + pub fn handle_tx_complete( + &mut self, msg: &msgs::TxComplete, + ) -> Result<(Option, Option), AbortReason> + { + let _ = do_state_transition!(self, received_tx_complete, msg)?; match &self.state_machine { - StateMachine::RemoteTxComplete(_) => { - let msg_send = self.do_local_state_transition()?; + StateMachine::ReceivedTxComplete(_) => { + let msg_send = self.do_sent_state_transition()?; let success_result = match &self.state_machine { StateMachine::NegotiationComplete(s) => Some(s.0.clone()), - StateMachine::LocalChange(_) => None, // We either had an input or output to contribute. + StateMachine::MsgSentChange(_) => None, // We either had an input or output to contribute. _ => { debug_assert!(false, "We cannot transition to any other states after receiving `tx_complete` and responding"); return Err(AbortReason::InvalidStateTransition); - } + }, }; Ok((Some(msg_send), success_result)) - } + }, StateMachine::NegotiationComplete(s) => Ok((None, Some(s.0.clone()))), _ => { - debug_assert!(false, "We cannot transition to any other states after receiving `tx_complete`"); + debug_assert!( + false, + "We cannot transition to any other states after receiving `tx_complete`" + ); Err(AbortReason::InvalidStateTransition) - } + }, } } } #[cfg(test)] mod tests { - use core::default::Default; - use std::ops::Deref; use crate::chain::chaininterface::FEERATE_FLOOR_SATS_PER_KW; - use crate::ln::interactivetxs::{AbortReason, generate_local_serial_id, InteractiveTxConstructor, InteractiveTxMessageSend, MAX_INPUTS_OUTPUTS_COUNT, MAX_RECEIVED_TX_ADD_INPUT_COUNT, MAX_RECEIVED_TX_ADD_OUTPUT_COUNT}; - use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; - use bitcoin::{OutPoint, Sequence, Transaction, TxIn, TxOut}; - use bitcoin::blockdata::opcodes; - use bitcoin::blockdata::script::Builder; - use bitcoin::locktime::absolute::LockTime; use crate::ln::channel::TOTAL_BITCOIN_SUPPLY_SATOSHIS; + use crate::ln::interactivetxs::{ + generate_holder_serial_id, AbortReason, InteractiveTxConstructor, InteractiveTxMessageSend, + MAX_INPUTS_OUTPUTS_COUNT, MAX_RECEIVED_TX_ADD_INPUT_COUNT, + MAX_RECEIVED_TX_ADD_OUTPUT_COUNT, + }; use crate::ln::ChannelId; use crate::sign::EntropySource; use crate::util::atomic_counter::AtomicCounter; + use crate::util::ser::TransactionU16LenLimited; + use bitcoin::blockdata::opcodes; + use bitcoin::blockdata::script::Builder; + use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; + use bitcoin::{ + absolute::LockTime as AbsoluteLockTime, OutPoint, Sequence, Transaction, TxIn, TxOut, + }; + use core::ops::Deref; // A simple entropy source that works based on an atomic counter. struct TestEntropySource(AtomicCounter); @@ -910,9 +1061,9 @@ mod tests { } struct TestSession { - inputs_a: Vec<(TxIn, Transaction)>, + inputs_a: Vec<(TxIn, TransactionU16LenLimited)>, outputs_a: Vec, - inputs_b: Vec<(TxIn, Transaction)>, + inputs_b: Vec<(TxIn, TransactionU16LenLimited)>, outputs_b: Vec, expect_error: Option, } @@ -922,49 +1073,83 @@ mod tests { do_test_interactive_tx_constructor_internal(session, &&entropy_source); } - fn do_test_interactive_tx_constructor_with_entropy_source(session: TestSession, entropy_source: ES) where ES::Target: EntropySource { + fn do_test_interactive_tx_constructor_with_entropy_source( + session: TestSession, entropy_source: ES, + ) where + ES::Target: EntropySource, + { do_test_interactive_tx_constructor_internal(session, &entropy_source); } - fn do_test_interactive_tx_constructor_internal(session: TestSession, entropy_source: &ES) where ES::Target: EntropySource { + fn do_test_interactive_tx_constructor_internal( + session: TestSession, entropy_source: &ES, + ) where + ES::Target: EntropySource, + { let channel_id = ChannelId(entropy_source.get_secure_random_bytes()); - let tx_locktime = LockTime::from_height(1337).unwrap(); - let holder_node_id = PublicKey::from_secret_key(&Secp256k1::signing_only(), &SecretKey::from_slice(&[42; 32]).unwrap()); - let counterparty_node_id = PublicKey::from_secret_key(&Secp256k1::signing_only(), &SecretKey::from_slice(&[43; 32]).unwrap()); + let tx_locktime = AbsoluteLockTime::from_height(1337).unwrap(); + let holder_node_id = PublicKey::from_secret_key( + &Secp256k1::signing_only(), + &SecretKey::from_slice(&[42; 32]).unwrap(), + ); + let counterparty_node_id = PublicKey::from_secret_key( + &Secp256k1::signing_only(), + &SecretKey::from_slice(&[43; 32]).unwrap(), + ); let (mut constructor_a, first_message_a) = InteractiveTxConstructor::new( - entropy_source, channel_id, FEERATE_FLOOR_SATS_PER_KW * 10, holder_node_id, counterparty_node_id, true, tx_locktime, session.inputs_a.clone(), session.outputs_a.clone() + entropy_source, + channel_id, + FEERATE_FLOOR_SATS_PER_KW * 10, + holder_node_id, + counterparty_node_id, + true, // is_initiator + tx_locktime, + session.inputs_a, + session.outputs_a, + 0, ); let (mut constructor_b, first_message_b) = InteractiveTxConstructor::new( - entropy_source, channel_id, FEERATE_FLOOR_SATS_PER_KW * 10, holder_node_id, counterparty_node_id, false, tx_locktime, session.inputs_b.clone(), session.outputs_b.clone() + entropy_source, + channel_id, + FEERATE_FLOOR_SATS_PER_KW * 10, + holder_node_id, + counterparty_node_id, + false, // is_initiator + tx_locktime, + session.inputs_b, + session.outputs_b, + 0, ); - let handle_message_send = |msg: InteractiveTxMessageSend, for_constructor: &mut InteractiveTxConstructor| { - match msg { - InteractiveTxMessageSend::TxAddInput(msg) => { - for_constructor.handle_tx_add_input(&msg).map(|msg_send| (Some(msg_send), None)) - }, - InteractiveTxMessageSend::TxAddOutput(msg) => { - for_constructor.handle_tx_add_output(&msg).map(|msg_send| (Some(msg_send), None)) - }, - InteractiveTxMessageSend::TxComplete(msg) => { - for_constructor.handle_tx_complete(&msg) - }, - } - }; + let handle_message_send = + |msg: InteractiveTxMessageSend, for_constructor: &mut InteractiveTxConstructor| { + match msg { + InteractiveTxMessageSend::TxAddInput(msg) => for_constructor + .handle_tx_add_input(&msg) + .map(|msg_send| (Some(msg_send), None)), + InteractiveTxMessageSend::TxAddOutput(msg) => for_constructor + .handle_tx_add_output(&msg) + .map(|msg_send| (Some(msg_send), None)), + InteractiveTxMessageSend::TxComplete(msg) => { + for_constructor.handle_tx_complete(&msg) + }, + } + }; assert!(first_message_b.is_none()); let mut message_send_a = first_message_a; let mut message_send_b = None; let mut final_tx_a = None; let mut final_tx_b = None; - while final_tx_a.is_none() || final_tx_b.is_none() { + while final_tx_a.is_none() || final_tx_b.is_none() { if let Some(message_send_a) = message_send_a.take() { match handle_message_send(message_send_a, &mut constructor_b) { Ok((msg_send, interactive_signing_session)) => { message_send_b = msg_send; - final_tx_b = interactive_signing_session.map(|session| session.constructed_transaction); - } + final_tx_b = interactive_signing_session + .map(|session| session.constructed_transaction); + }, Err(abort_reason) => { assert_eq!(Some(abort_reason), session.expect_error); return; @@ -975,8 +1160,9 @@ mod tests { match handle_message_send(message_send_b, &mut constructor_a) { Ok((msg_send, interactive_signing_session)) => { message_send_a = msg_send; - final_tx_a = interactive_signing_session.map(|session| session.constructed_transaction); - } + final_tx_a = interactive_signing_session + .map(|session| session.constructed_transaction); + }, Err(abort_reason) => { assert_eq!(Some(abort_reason), session.expect_error); return; @@ -991,60 +1177,95 @@ mod tests { } fn generate_tx(values: &[u64]) -> Transaction { + generate_tx_with_locktime(values, 1337) + } + + fn generate_tx_with_locktime(values: &[u64], locktime: u32) -> Transaction { Transaction { version: 2, - lock_time: LockTime::from_height(1337).unwrap(), + lock_time: AbsoluteLockTime::from_height(locktime).unwrap(), input: vec![TxIn { ..Default::default() }], - output: values.iter().map(|value| TxOut { - value: *value, - script_pubkey: Builder::new().push_opcode(opcodes::OP_TRUE).into_script().to_v0_p2wsh(), - }).collect(), + output: values + .iter() + .map(|value| TxOut { + value: *value, + script_pubkey: Builder::new() + .push_opcode(opcodes::OP_TRUE) + .into_script() + .to_v0_p2wsh(), + }) + .collect(), } } - fn generate_inputs(values: &[u64]) -> Vec<(TxIn, Transaction)> { + fn generate_inputs(values: &[u64]) -> Vec<(TxIn, TransactionU16LenLimited)> { let tx = generate_tx(values); let txid = tx.txid(); - tx.output.iter().enumerate().map(|(idx, _)| { - let input = TxIn { - previous_output: OutPoint { - txid: txid, - vout: idx as u32, - }, - script_sig: Default::default(), - sequence: Sequence::ENABLE_RBF_NO_LOCKTIME, - witness: Default::default(), - }; - (input, tx.clone()) - }).collect() + tx.output + .iter() + .enumerate() + .map(|(idx, _)| { + let input = TxIn { + previous_output: OutPoint { txid, vout: idx as u32 }, + script_sig: Default::default(), + sequence: Sequence::ENABLE_RBF_NO_LOCKTIME, + witness: Default::default(), + }; + (input, TransactionU16LenLimited::new(tx.clone()).unwrap()) + }) + .collect() } fn generate_outputs(values: &[u64]) -> Vec { - values.iter().map(|value| { - TxOut { + values + .iter() + .map(|value| TxOut { value: *value, - script_pubkey: Builder::new().push_opcode(opcodes::OP_TRUE).into_script().to_v0_p2wsh() - } - }).collect() + script_pubkey: Builder::new() + .push_opcode(opcodes::OP_TRUE) + .into_script() + .to_v0_p2wsh(), + }) + .collect() } - fn generate_fixed_number_of_inputs(count: u16) -> Vec<(TxIn, Transaction)> { - // Generate a transaction with `count` number of outputs. - let tx = generate_tx(&vec![1_000_000; count as usize]); - let txid = tx.txid(); + fn generate_fixed_number_of_inputs(count: u16) -> Vec<(TxIn, TransactionU16LenLimited)> { + // Generate transactions with a total `count` number of outputs such that no transaction has a + // serialized length greater than u16::MAX. + let max_outputs_per_prevtx = 1_500; + let mut remaining = count; + let mut inputs: Vec<(TxIn, TransactionU16LenLimited)> = Vec::with_capacity(count as usize); + + while remaining > 0 { + let tx_output_count = remaining.min(max_outputs_per_prevtx); + remaining -= tx_output_count; + + // Use unique locktime for each tx so outpoints are different across transactions + let tx = generate_tx_with_locktime( + &vec![1_000_000; tx_output_count as usize], + (1337 + remaining).into(), + ); + let txid = tx.txid(); + + let mut temp: Vec<(TxIn, TransactionU16LenLimited)> = tx + .output + .iter() + .enumerate() + .map(|(idx, _)| { + let input = TxIn { + previous_output: OutPoint { txid, vout: idx as u32 }, + script_sig: Default::default(), + sequence: Sequence::ENABLE_RBF_NO_LOCKTIME, + witness: Default::default(), + }; + (input, TransactionU16LenLimited::new(tx.clone()).unwrap()) + }) + .collect(); + + inputs.append(&mut temp); + } - tx.output.iter().enumerate().map(|(idx, _)| { - let input = TxIn { - previous_output: OutPoint { - txid: txid, - vout: idx as u32, - }, - script_sig: Default::default(), - sequence: Sequence::ENABLE_RBF_NO_LOCKTIME, - witness: Default::default(), - }; - (input, tx.clone()) - }).collect() + inputs } fn generate_fixed_number_of_outputs(count: u16) -> Vec { @@ -1053,7 +1274,10 @@ mod tests { } fn generate_non_witness_output(value: u64) -> TxOut { - TxOut { value, script_pubkey: Builder::new().push_opcode(opcodes::OP_TRUE).into_script().to_p2sh() } + TxOut { + value, + script_pubkey: Builder::new().push_opcode(opcodes::OP_TRUE).into_script().to_p2sh(), + } } #[test] @@ -1072,7 +1296,7 @@ mod tests { outputs_a: generate_outputs(&[1_000_000]), inputs_b: vec![], outputs_b: vec![], - expect_error: Some(AbortReason::InsufficientFees), + expect_error: Some(AbortReason::OutputsValueExceedsInputsValue), }); // Single contribution, no initiator outputs. do_test_interactive_tx_constructor(TestSession { @@ -1111,13 +1335,20 @@ mod tests { let non_segwit_output_tx = { let mut tx = generate_tx(&[1_000_000]); tx.output.push(TxOut { - script_pubkey: Builder::new().push_opcode(opcodes::all::OP_RETURN).into_script().to_p2sh(), + script_pubkey: Builder::new() + .push_opcode(opcodes::all::OP_RETURN) + .into_script() + .to_p2sh(), ..Default::default() }); - tx + + TransactionU16LenLimited::new(tx).unwrap() }; let non_segwit_input = TxIn { - previous_output: OutPoint { txid: non_segwit_output_tx.txid(), vout: 1 }, + previous_output: OutPoint { + txid: non_segwit_output_tx.as_transaction().txid(), + vout: 1, + }, sequence: Sequence::ENABLE_RBF_NO_LOCKTIME, ..Default::default() }; @@ -1130,9 +1361,9 @@ mod tests { }); // Invalid input sequence from initiator. - let tx = generate_tx(&[1_000_000]); + let tx = TransactionU16LenLimited::new(generate_tx(&[1_000_000])).unwrap(); let invalid_sequence_input = TxIn { - previous_output: OutPoint { txid: tx.txid(), vout: 0 }, + previous_output: OutPoint { txid: tx.as_transaction().txid(), vout: 0 }, ..Default::default() }; do_test_interactive_tx_constructor(TestSession { @@ -1144,7 +1375,7 @@ mod tests { }); // Duplicate prevout from initiator. let duplicate_input = TxIn { - previous_output: OutPoint { txid: tx.txid(), vout: 0 }, + previous_output: OutPoint { txid: tx.as_transaction().txid(), vout: 0 }, sequence: Sequence::ENABLE_RBF_NO_LOCKTIME, ..Default::default() }; @@ -1157,7 +1388,7 @@ mod tests { }); // Non-initiator uses same prevout as initiator. let duplicate_input = TxIn { - previous_output: OutPoint { txid: tx.txid(), vout: 0 }, + previous_output: OutPoint { txid: tx.as_transaction().txid(), vout: 0 }, sequence: Sequence::ENABLE_RBF_NO_LOCKTIME, ..Default::default() }; @@ -1178,13 +1409,16 @@ mod tests { }); // Attempt to queue up two inputs with duplicate serial ids. We use a deliberately bad // entropy source, `DuplicateEntropySource` to simulate this. - do_test_interactive_tx_constructor_with_entropy_source(TestSession { - inputs_a: generate_fixed_number_of_inputs(2), - outputs_a: vec![], - inputs_b: vec![], - outputs_b: vec![], - expect_error: Some(AbortReason::DuplicateSerialId), - }, &DuplicateEntropySource); + do_test_interactive_tx_constructor_with_entropy_source( + TestSession { + inputs_a: generate_fixed_number_of_inputs(2), + outputs_a: vec![], + inputs_b: vec![], + outputs_b: vec![], + expect_error: Some(AbortReason::DuplicateSerialId), + }, + &DuplicateEntropySource, + ); // Initiator sends too many TxAddOutputs. do_test_interactive_tx_constructor(TestSession { inputs_a: vec![], @@ -1199,7 +1433,7 @@ mod tests { outputs_a: generate_outputs(&[1]), inputs_b: vec![], outputs_b: vec![], - expect_error: Some(AbortReason::BelowDustLimit) + expect_error: Some(AbortReason::BelowDustLimit), }); // Initiator sends an output above maximum sats allowed. do_test_interactive_tx_constructor(TestSession { @@ -1207,7 +1441,7 @@ mod tests { outputs_a: generate_outputs(&[TOTAL_BITCOIN_SUPPLY_SATOSHIS + 1]), inputs_b: vec![], outputs_b: vec![], - expect_error: Some(AbortReason::ExceededMaximumSatsAllowed) + expect_error: Some(AbortReason::ExceededMaximumSatsAllowed), }); // Initiator sends an output without a witness program. do_test_interactive_tx_constructor(TestSession { @@ -1215,17 +1449,20 @@ mod tests { outputs_a: vec![generate_non_witness_output(1_000_000)], inputs_b: vec![], outputs_b: vec![], - expect_error: Some(AbortReason::InvalidOutputScript) + expect_error: Some(AbortReason::InvalidOutputScript), }); // Attempt to queue up two outputs with duplicate serial ids. We use a deliberately bad // entropy source, `DuplicateEntropySource` to simulate this. - do_test_interactive_tx_constructor_with_entropy_source(TestSession { - inputs_a: vec![], - outputs_a: generate_fixed_number_of_outputs(2), - inputs_b: vec![], - outputs_b: vec![], - expect_error: Some(AbortReason::DuplicateSerialId) - }, &DuplicateEntropySource); + do_test_interactive_tx_constructor_with_entropy_source( + TestSession { + inputs_a: vec![], + outputs_a: generate_fixed_number_of_outputs(2), + inputs_b: vec![], + outputs_b: vec![], + expect_error: Some(AbortReason::DuplicateSerialId), + }, + &DuplicateEntropySource, + ); // Peer contributed more output value than inputs do_test_interactive_tx_constructor(TestSession { @@ -1233,7 +1470,7 @@ mod tests { outputs_a: generate_outputs(&[1_000_000]), inputs_b: vec![], outputs_b: vec![], - expect_error: Some(AbortReason::InsufficientFees) + expect_error: Some(AbortReason::OutputsValueExceedsInputsValue), }); // Peer contributed more than allowed number of inputs. @@ -1242,7 +1479,7 @@ mod tests { outputs_a: vec![], inputs_b: vec![], outputs_b: vec![], - expect_error: Some(AbortReason::ExceededNumberOfInputsOrOutputs) + expect_error: Some(AbortReason::ExceededNumberOfInputsOrOutputs), }); // Peer contributed more than allowed number of outputs. do_test_interactive_tx_constructor(TestSession { @@ -1250,7 +1487,7 @@ mod tests { outputs_a: generate_fixed_number_of_outputs(MAX_INPUTS_OUTPUTS_COUNT as u16 + 1), inputs_b: vec![], outputs_b: vec![], - expect_error: Some(AbortReason::ExceededNumberOfInputsOrOutputs) + expect_error: Some(AbortReason::ExceededNumberOfInputsOrOutputs), }); } @@ -1259,7 +1496,7 @@ mod tests { let entropy_source = TestEntropySource(AtomicCounter::new()); // Initiators should have even serial id, non-initiators should have odd serial id. - assert_eq!(generate_local_serial_id(&&entropy_source, true) % 2, 0); - assert_eq!(generate_local_serial_id(&&entropy_source, false) % 2, 1) + assert_eq!(generate_holder_serial_id(&&entropy_source, true) % 2, 0); + assert_eq!(generate_holder_serial_id(&&entropy_source, false) % 2, 1) } } diff --git a/lightning/src/ln/mod.rs b/lightning/src/ln/mod.rs index d97ccab54e8..2d920289c23 100644 --- a/lightning/src/ln/mod.rs +++ b/lightning/src/ln/mod.rs @@ -80,14 +80,19 @@ mod monitor_tests; #[cfg(test)] #[allow(unused_mut)] mod shutdown_tests; -#[cfg(test)] +#[cfg(all(test, async_signing))] #[allow(unused_mut)] mod async_signer_tests; -#[allow(unused_mut)] // TODO +#[cfg(test)] +#[allow(unused_mut)] +mod offers_tests; +#[allow(dead_code)] // TODO(dual_funding): Exchange for dual_funding cfg pub(crate) mod interactivetxs; pub use self::peer_channel_encryptor::LN_MAX_MSG_LEN; +use bitcoin::hashes::{sha256::Hash as Sha256, Hash}; + /// payment_hash type, use to cross-lock hop /// /// This is not exported to bindings users as we just use [u8; 32] directly @@ -112,6 +117,13 @@ impl core::fmt::Display for PaymentPreimage { } } +/// Converts a `PaymentPreimage` into a `PaymentHash` by hashing the preimage with SHA256. +impl From for PaymentHash { + fn from(value: PaymentPreimage) -> Self { + PaymentHash(Sha256::hash(&value.0).to_byte_array()) + } +} + /// 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 diff --git a/lightning/src/ln/monitor_tests.rs b/lightning/src/ln/monitor_tests.rs index f7b9c3ab0c2..dbd0c326e89 100644 --- a/lightning/src/ln/monitor_tests.rs +++ b/lightning/src/ln/monitor_tests.rs @@ -15,11 +15,11 @@ use crate::chain::transaction::OutPoint; 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::{channel, ChannelId}; use crate::ln::channelmanager::{BREAKDOWN_TIMEOUT, PaymentId, RecipientOnionFields}; use crate::ln::msgs::ChannelMessageHandler; use crate::util::config::UserConfig; -use crate::util::crypto::sign; +use crate::crypto::utils::sign; use crate::util::ser::Writeable; use crate::util::scid_utils::block_from_scid; use crate::util::test_utils; @@ -176,7 +176,7 @@ fn do_chanmon_claim_value_coop_close(anchors: bool) { let (_, _, chan_id, funding_tx) = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 1_000_000); let funding_outpoint = OutPoint { txid: funding_tx.txid(), index: 0 }; - assert_eq!(funding_outpoint.to_channel_id(), chan_id); + assert_eq!(ChannelId::v1_from_funding_outpoint(funding_outpoint), chan_id); let chan_feerate = get_feerate!(nodes[0], nodes[1], chan_id) as u64; let channel_type_features = get_channel_type_features!(nodes[0], nodes[1], chan_id); @@ -209,8 +209,8 @@ fn do_chanmon_claim_value_coop_close(anchors: bool) { assert_eq!(shutdown_tx, nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0)); assert_eq!(shutdown_tx.len(), 1); - let shutdown_tx_conf_height_a = block_from_scid(&mine_transaction(&nodes[0], &shutdown_tx[0])); - let shutdown_tx_conf_height_b = block_from_scid(&mine_transaction(&nodes[1], &shutdown_tx[0])); + let shutdown_tx_conf_height_a = block_from_scid(mine_transaction(&nodes[0], &shutdown_tx[0])); + let shutdown_tx_conf_height_b = block_from_scid(mine_transaction(&nodes[1], &shutdown_tx[0])); assert!(nodes[0].node.list_channels().is_empty()); assert!(nodes[1].node.list_channels().is_empty()); @@ -257,8 +257,8 @@ fn do_chanmon_claim_value_coop_close(anchors: bool) { spendable_outputs_b ); - 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); + check_closed_event!(nodes[0], 1, ClosureReason::LocallyInitiatedCooperativeClosure, [nodes[1].node.get_our_node_id()], 1000000); + check_closed_event!(nodes[1], 1, ClosureReason::CounterpartyInitiatedCooperativeClosure, [nodes[0].node.get_our_node_id()], 1000000); } #[test] @@ -327,7 +327,7 @@ fn do_test_claim_value_force_close(anchors: bool, prev_commitment_tx: bool) { let (_, _, chan_id, funding_tx) = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 1_000_000); let funding_outpoint = OutPoint { txid: funding_tx.txid(), index: 0 }; - assert_eq!(funding_outpoint.to_channel_id(), chan_id); + assert_eq!(ChannelId::v1_from_funding_outpoint(funding_outpoint), 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); @@ -736,8 +736,8 @@ fn do_test_balances_on_local_commitment_htlcs(anchors: bool) { check_spends!(commitment_tx, funding_tx); commitment_tx }; - let commitment_tx_conf_height_a = block_from_scid(&mine_transaction(&nodes[0], &commitment_tx)); - if anchors && nodes[0].connect_style.borrow().updates_best_block_first() { + let commitment_tx_conf_height_a = block_from_scid(mine_transaction(&nodes[0], &commitment_tx)); + if nodes[0].connect_style.borrow().updates_best_block_first() { let mut txn = nodes[0].tx_broadcaster.txn_broadcast(); assert_eq!(txn.len(), 1); assert_eq!(txn[0].txid(), commitment_tx.txid()); @@ -1121,7 +1121,7 @@ fn do_test_revoked_counterparty_commitment_balances(anchors: bool, confirm_htlc_ let (_, _, chan_id, funding_tx) = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 100_000_000); let funding_outpoint = OutPoint { txid: funding_tx.txid(), index: 0 }; - assert_eq!(funding_outpoint.to_channel_id(), chan_id); + assert_eq!(ChannelId::v1_from_funding_outpoint(funding_outpoint), chan_id); // We create five HTLCs for B to claim against A's revoked commitment transaction: // @@ -1403,7 +1403,7 @@ fn do_test_revoked_counterparty_htlc_tx_balances(anchors: bool) { let (_, _, chan_id, funding_tx) = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 12_000_000); let funding_outpoint = OutPoint { txid: funding_tx.txid(), index: 0 }; - assert_eq!(funding_outpoint.to_channel_id(), chan_id); + assert_eq!(ChannelId::v1_from_funding_outpoint(funding_outpoint), chan_id); let payment_preimage = route_payment(&nodes[0], &[&nodes[1]], 3_000_000).0; let failed_payment_hash = route_payment(&nodes[1], &[&nodes[0]], 1_000_000).1; @@ -1705,7 +1705,7 @@ fn do_test_revoked_counterparty_aggregated_claims(anchors: bool) { let (_, _, chan_id, funding_tx) = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 100_000_000); let funding_outpoint = OutPoint { txid: funding_tx.txid(), index: 0 }; - assert_eq!(funding_outpoint.to_channel_id(), chan_id); + assert_eq!(ChannelId::v1_from_funding_outpoint(funding_outpoint), chan_id); // We create two HTLCs, one which we will give A the preimage to to generate an HTLC-Success // transaction, and one which we will not, allowing B to claim the HTLC output in an aggregated @@ -1998,6 +1998,11 @@ fn do_test_restored_packages_retry(check_old_monitor_retries_after_upgrade: bool }; mine_transaction(&nodes[0], &commitment_tx); + if nodes[0].connect_style.borrow().updates_best_block_first() { + let txn = nodes[0].tx_broadcaster.txn_broadcast(); + assert_eq!(txn.len(), 1); + assert_eq!(txn[0].txid(), commitment_tx.txid()); + } // Connect blocks until the HTLC's expiration is met, expecting a transaction broadcast. connect_blocks(&nodes[0], TEST_FINAL_CLTV); @@ -2403,26 +2408,12 @@ fn test_anchors_aggregated_revoked_htlc_tx() { nodes[1].node.timer_tick_occurred(); check_added_monitors(&nodes[1], 2); 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.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 - if txn[0].input[0].previous_output.txid == chan_a.3.txid() { - check_spends!(&txn[0], &chan_a.3); - check_spends!(&txn[1], &chan_b.3); - (txn[0].clone(), txn[1].clone()) - } else { - check_spends!(&txn[1], &chan_a.3); - check_spends!(&txn[0], &chan_b.3); - (txn[1].clone(), txn[0].clone()) - } - }; // Bob should now receive two events to bump his revoked commitment transaction fees. 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 mut revoked_commitment_txs = Vec::with_capacity(events.len()); 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; @@ -2442,13 +2433,21 @@ fn test_anchors_aggregated_revoked_htlc_tx() { }; let txn = nodes[1].tx_broadcaster.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 + if txn[0].input[0].previous_output.txid == chan_a.3.txid() { + check_spends!(&txn[0], &chan_a.3); + } else { + check_spends!(&txn[0], &chan_b.3); + } let (commitment_tx, anchor_tx) = (&txn[0], &txn[1]); check_spends!(anchor_tx, coinbase_tx, commitment_tx); + + revoked_commitment_txs.push(commitment_tx.clone()); anchor_txs.push(anchor_tx.clone()); }; for node in &nodes { - mine_transactions(node, &[&revoked_commitment_a, &anchor_txs[0], &revoked_commitment_b, &anchor_txs[1]]); + mine_transactions(node, &[&revoked_commitment_txs[0], &anchor_txs[0], &revoked_commitment_txs[1], &anchor_txs[1]]); } check_added_monitors!(&nodes[0], 2); check_closed_broadcast(&nodes[0], 2, true); @@ -2460,7 +2459,7 @@ fn test_anchors_aggregated_revoked_htlc_tx() { let txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0); assert_eq!(txn.len(), 4); - let (revoked_htlc_claim_a, revoked_htlc_claim_b) = if txn[0].input[0].previous_output.txid == revoked_commitment_a.txid() { + let (revoked_htlc_claim_a, revoked_htlc_claim_b) = if txn[0].input[0].previous_output.txid == revoked_commitment_txs[0].txid() { (if txn[0].input.len() == 2 { &txn[0] } else { &txn[1] }, if txn[2].input.len() == 2 { &txn[2] } else { &txn[3] }) } else { (if txn[2].input.len() == 2 { &txn[2] } else { &txn[3] }, if txn[0].input.len() == 2 { &txn[0] } else { &txn[1] }) @@ -2468,10 +2467,10 @@ fn test_anchors_aggregated_revoked_htlc_tx() { assert_eq!(revoked_htlc_claim_a.input.len(), 2); // Spends both HTLC outputs assert_eq!(revoked_htlc_claim_a.output.len(), 1); - check_spends!(revoked_htlc_claim_a, revoked_commitment_a); + check_spends!(revoked_htlc_claim_a, revoked_commitment_txs[0]); assert_eq!(revoked_htlc_claim_b.input.len(), 2); // Spends both HTLC outputs assert_eq!(revoked_htlc_claim_b.output.len(), 1); - check_spends!(revoked_htlc_claim_b, revoked_commitment_b); + check_spends!(revoked_htlc_claim_b, revoked_commitment_txs[1]); } // Since Bob was able to confirm his revoked commitment, he'll now try to claim the HTLCs @@ -2551,7 +2550,7 @@ fn test_anchors_aggregated_revoked_htlc_tx() { sig }; htlc_tx.input[0].witness = Witness::from_slice(&[fee_utxo_sig, public_key.to_bytes()]); - check_spends!(htlc_tx, coinbase_tx, revoked_commitment_a, revoked_commitment_b); + check_spends!(htlc_tx, coinbase_tx, revoked_commitment_txs[0], revoked_commitment_txs[1]); htlc_tx }; @@ -2575,7 +2574,7 @@ fn test_anchors_aggregated_revoked_htlc_tx() { check_spends!(revoked_htlc_claim, htlc_tx); } - let mut revoked_claim_transaction_map = HashMap::new(); + let mut revoked_claim_transaction_map = new_hash_map(); for current_tx in txn.into_iter() { revoked_claim_transaction_map.insert(current_tx.txid(), current_tx); } @@ -2610,7 +2609,7 @@ fn test_anchors_aggregated_revoked_htlc_tx() { ).unwrap(); if let SpendableOutputDescriptor::StaticPaymentOutput(_) = &outputs[0] { - check_spends!(spend_tx, &revoked_commitment_a, &revoked_commitment_b); + check_spends!(spend_tx, &revoked_commitment_txs[0], &revoked_commitment_txs[1]); } else { check_spends!(spend_tx, revoked_claim_transactions.get(&spend_tx.input[0].previous_output.txid).unwrap()); } @@ -2677,14 +2676,14 @@ fn do_test_anchors_monitor_fixes_counterparty_payment_script_on_reload(confirm_c // We should expect our round trip serialization check to fail as we're writing the monitor // with the incorrect P2WPKH script but reading it with the correct P2WSH script. *nodes[1].chain_monitor.expect_monitor_round_trip_fail.lock().unwrap() = Some(chan_id); - let commitment_tx_conf_height = block_from_scid(&mine_transaction(&nodes[1], &commitment_tx)); + let commitment_tx_conf_height = block_from_scid(mine_transaction(&nodes[1], &commitment_tx)); let serialized_monitor = get_monitor!(nodes[1], chan_id).encode(); reload_node!(nodes[1], user_config, &nodes[1].node.encode(), &[&serialized_monitor], persister, chain_monitor, node_deserialized); commitment_tx_conf_height } else { let serialized_monitor = get_monitor!(nodes[1], chan_id).encode(); reload_node!(nodes[1], user_config, &nodes[1].node.encode(), &[&serialized_monitor], persister, chain_monitor, node_deserialized); - let commitment_tx_conf_height = block_from_scid(&mine_transaction(&nodes[1], &commitment_tx)); + let commitment_tx_conf_height = block_from_scid(mine_transaction(&nodes[1], &commitment_tx)); check_added_monitors(&nodes[1], 1); check_closed_broadcast(&nodes[1], 1, true); commitment_tx_conf_height @@ -2758,7 +2757,9 @@ fn do_test_monitor_claims_with_random_signatures(anchors: bool, confirm_counterp (&nodes[0], &nodes[1]) }; - closing_node.node.force_close_broadcasting_latest_txn(&chan_id, &other_node.node.get_our_node_id()).unwrap(); + get_monitor!(closing_node, chan_id).broadcast_latest_holder_commitment_txn( + &closing_node.tx_broadcaster, &closing_node.fee_estimator, &closing_node.logger + ); // The commitment transaction comes first. let commitment_tx = { @@ -2771,7 +2772,7 @@ fn do_test_monitor_claims_with_random_signatures(anchors: bool, confirm_counterp mine_transaction(closing_node, &commitment_tx); check_added_monitors!(closing_node, 1); check_closed_broadcast!(closing_node, true); - check_closed_event!(closing_node, 1, ClosureReason::HolderForceClosed, [other_node.node.get_our_node_id()], 1_000_000); + check_closed_event!(closing_node, 1, ClosureReason::CommitmentTxConfirmed, [other_node.node.get_our_node_id()], 1_000_000); mine_transaction(other_node, &commitment_tx); check_added_monitors!(other_node, 1); @@ -2780,7 +2781,7 @@ fn do_test_monitor_claims_with_random_signatures(anchors: bool, confirm_counterp // If we update the best block to the new height before providing the confirmed transactions, // we'll see another broadcast of the commitment transaction. - if anchors && !confirm_counterparty_commitment && nodes[0].connect_style.borrow().updates_best_block_first() { + if !confirm_counterparty_commitment && nodes[0].connect_style.borrow().updates_best_block_first() { let _ = nodes[0].tx_broadcaster.txn_broadcast(); } @@ -2798,11 +2799,7 @@ fn do_test_monitor_claims_with_random_signatures(anchors: bool, confirm_counterp let htlc_timeout_tx = { let mut txn = nodes[0].tx_broadcaster.txn_broadcast(); assert_eq!(txn.len(), 1); - let tx = if txn[0].input[0].previous_output.txid == commitment_tx.txid() { - txn[0].clone() - } else { - txn[1].clone() - }; + let tx = txn.pop().unwrap(); check_spends!(tx, commitment_tx, coinbase_tx); tx }; diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index ac5cbf09c65..b80abb5e492 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -53,7 +53,7 @@ use crate::io::{self, Cursor, Read}; use crate::io_extras::read_to_end; use crate::events::{EventsProvider, MessageSendEventsProvider}; -use crate::util::chacha20poly1305rfc::ChaChaPolyReadAdapter; +use crate::crypto::streams::ChaChaPolyReadAdapter; use crate::util::logger; use crate::util::ser::{LengthReadable, LengthReadableArgs, Readable, ReadableArgs, Writeable, Writer, WithoutLength, FixedLengthReader, HighZeroBytesDroppedBigSize, Hostname, TransactionU16LenLimited, BigSize}; use crate::util::base32; @@ -172,76 +172,20 @@ pub struct Pong { pub byteslen: u16, } -/// An [`open_channel`] message to be sent to or received from a peer. -/// -/// Used in V1 channel establishment +/// Contains fields that are both common to [`open_channel`] and `open_channel2` messages. /// /// [`open_channel`]: https://github.com/lightning/bolts/blob/master/02-peer-protocol.md#the-open_channel-message -#[derive(Clone, Debug, Hash, PartialEq, Eq)] -pub struct OpenChannel { - /// The genesis hash of the blockchain where the channel is to be opened - pub chain_hash: ChainHash, - /// A temporary channel ID, until the funding outpoint is announced - 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 - pub push_msat: u64, - /// 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 - pub max_htlc_value_in_flight_msat: u64, - /// The minimum value unencumbered by HTLCs for the counterparty to keep in the channel - pub channel_reserve_satoshis: u64, - /// The minimum HTLC size incoming to sender, in milli-satoshi - pub htlc_minimum_msat: u64, - /// The feerate per 1000-weight of sender generated transactions, until updated by - /// [`UpdateFee`] - pub feerate_per_kw: u32, - /// The number of blocks which the counterparty will have to wait to claim on-chain funds if - /// they broadcast a commitment transaction - pub to_self_delay: u16, - /// The maximum number of inbound HTLCs towards sender - pub max_accepted_htlcs: u16, - /// The sender's key controlling the funding transaction - pub funding_pubkey: PublicKey, - /// Used to derive a revocation key for transactions broadcast by counterparty - pub revocation_basepoint: PublicKey, - /// A payment key to sender for transactions broadcast by counterparty - pub payment_point: PublicKey, - /// Used to derive a payment key to sender for transactions broadcast by sender - pub delayed_payment_basepoint: PublicKey, - /// Used to derive an HTLC payment key to sender - pub htlc_basepoint: PublicKey, - /// The first to-be-broadcast-by-sender transaction's per commitment point - pub first_per_commitment_point: PublicKey, - /// The channel flags to be used - pub channel_flags: u8, - /// A request to pre-set the to-sender output's `scriptPubkey` for when we collaboratively close - pub shutdown_scriptpubkey: Option, - /// The channel type that this channel will represent - /// - /// If this is `None`, we derive the channel type from the intersection of our - /// feature bits with our counterparty's feature bits from the [`Init`] message. - pub channel_type: Option, -} - -/// An open_channel2 message to be sent by or received from the channel initiator. -/// -/// Used in V2 channel establishment -/// // TODO(dual_funding): Add spec link for `open_channel2`. #[derive(Clone, Debug, Hash, PartialEq, Eq)] -pub struct OpenChannelV2 { +pub struct CommonOpenChannelFields { /// The genesis hash of the blockchain where the channel is to be opened pub chain_hash: ChainHash, - /// A temporary channel ID derived using a zeroed out value for the channel acceptor's revocation basepoint + /// A temporary channel ID + /// For V2 channels: derived using a zeroed out value for the channel acceptor's revocation basepoint + /// For V1 channels: a temporary channel ID, until the funding outpoint is announced 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 - pub commitment_feerate_sat_per_1000_weight: u32, - /// Part of the channel value contributed by the channel initiator + /// For V1 channels: The channel value + /// For V2 channels: Part of the channel value contributed by the channel initiator pub funding_satoshis: u64, /// The threshold below which outputs on transactions broadcast by the channel initiator will be /// omitted @@ -250,13 +194,14 @@ pub struct OpenChannelV2 { pub max_htlc_value_in_flight_msat: u64, /// The minimum HTLC size incoming to channel initiator, in milli-satoshi pub htlc_minimum_msat: u64, + /// The feerate for the commitment transaction set by the channel initiator until updated by + /// [`UpdateFee`] + pub commitment_feerate_sat_per_1000_weight: u32, /// The number of blocks which the counterparty will have to wait to claim on-chain funds if they /// broadcast a commitment transaction pub to_self_delay: u16, /// The maximum number of inbound HTLCs towards channel initiator pub max_accepted_htlcs: u16, - /// The locktime for the funding transaction - pub locktime: u32, /// The channel initiator's key controlling the funding transaction pub funding_pubkey: PublicKey, /// Used to derive a revocation key for transactions broadcast by counterparty @@ -270,84 +215,64 @@ pub struct OpenChannelV2 { pub htlc_basepoint: PublicKey, /// The first to-be-broadcast-by-channel-initiator transaction's per commitment point pub first_per_commitment_point: PublicKey, - /// The second to-be-broadcast-by-channel-initiator transaction's per commitment point - pub second_per_commitment_point: PublicKey, - /// Channel flags + /// The channel flags to be used pub channel_flags: u8, /// Optionally, a request to pre-set the to-channel-initiator output's scriptPubkey for when we /// collaboratively close pub shutdown_scriptpubkey: Option, - /// The channel type that this channel will represent. If none is set, we derive the channel - /// type from the intersection of our feature bits with our counterparty's feature bits from - /// the Init message. + /// The channel type that this channel will represent + /// + /// If this is `None`, we derive the channel type from the intersection of our + /// feature bits with our counterparty's feature bits from the [`Init`] message. pub channel_type: Option, - /// Optionally, a requirement that only confirmed inputs can be added - pub require_confirmed_inputs: Option<()>, } -/// An [`accept_channel`] message to be sent to or received from a peer. +/// An [`open_channel`] message to be sent to or received from a peer. /// /// Used in V1 channel establishment /// -/// [`accept_channel`]: https://github.com/lightning/bolts/blob/master/02-peer-protocol.md#the-accept_channel-message +/// [`open_channel`]: https://github.com/lightning/bolts/blob/master/02-peer-protocol.md#the-open_channel-message #[derive(Clone, Debug, Hash, PartialEq, Eq)] -pub struct AcceptChannel { - /// A temporary channel ID, until the funding outpoint is announced - 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 - pub max_htlc_value_in_flight_msat: u64, +pub struct OpenChannel { + /// Common fields of `open_channel(2)`-like messages + pub common_fields: CommonOpenChannelFields, + /// The amount to push to the counterparty as part of the open, in milli-satoshi + pub push_msat: u64, /// The minimum value unencumbered by HTLCs for the counterparty to keep in the channel pub channel_reserve_satoshis: u64, - /// The minimum HTLC size incoming to sender, in milli-satoshi - pub htlc_minimum_msat: u64, - /// Minimum depth of the funding transaction before the channel is considered open - pub minimum_depth: u32, - /// The number of blocks which the counterparty will have to wait to claim on-chain funds if they broadcast a commitment transaction - pub to_self_delay: u16, - /// The maximum number of inbound HTLCs towards sender - pub max_accepted_htlcs: u16, - /// The sender's key controlling the funding transaction - pub funding_pubkey: PublicKey, - /// Used to derive a revocation key for transactions broadcast by counterparty - pub revocation_basepoint: PublicKey, - /// A payment key to sender for transactions broadcast by counterparty - pub payment_point: PublicKey, - /// Used to derive a payment key to sender for transactions broadcast by sender - pub delayed_payment_basepoint: PublicKey, - /// Used to derive an HTLC payment key to sender for transactions broadcast by counterparty - pub htlc_basepoint: PublicKey, - /// The first to-be-broadcast-by-sender transaction's per commitment point - pub first_per_commitment_point: PublicKey, - /// A request to pre-set the to-sender output's scriptPubkey for when we collaboratively close - pub shutdown_scriptpubkey: Option, - /// The channel type that this channel will represent. - /// - /// If this is `None`, we derive the channel type from the intersection of - /// our feature bits with our counterparty's feature bits from the [`Init`] message. - /// This is required to match the equivalent field in [`OpenChannel::channel_type`]. - pub channel_type: Option, - #[cfg(taproot)] - /// Next nonce the channel initiator should use to create a funding output signature against - pub next_local_nonce: Option, } -/// An accept_channel2 message to be sent by or received from the channel accepter. +/// An open_channel2 message to be sent by or received from the channel initiator. /// /// Used in V2 channel establishment /// +// TODO(dual_funding): Add spec link for `open_channel2`. +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +pub struct OpenChannelV2 { + /// Common fields of `open_channel(2)`-like messages + pub common_fields: CommonOpenChannelFields, + /// The feerate for the funding transaction set by the channel initiator + pub funding_feerate_sat_per_1000_weight: u32, + /// The locktime for the funding transaction + pub locktime: u32, + /// The second to-be-broadcast-by-channel-initiator transaction's per commitment point + pub second_per_commitment_point: PublicKey, + /// Optionally, a requirement that only confirmed inputs can be added + pub require_confirmed_inputs: Option<()>, +} + +/// Contains fields that are both common to [`accept_channel`] and `accept_channel2` messages. +/// +/// [`accept_channel`]: https://github.com/lightning/bolts/blob/master/02-peer-protocol.md#the-accept_channel-message // TODO(dual_funding): Add spec link for `accept_channel2`. #[derive(Clone, Debug, Hash, PartialEq, Eq)] -pub struct AcceptChannelV2 { - /// The same `temporary_channel_id` received from the initiator's `open_channel2` message. +pub struct CommonAcceptChannelFields { + /// The same `temporary_channel_id` received from the initiator's `open_channel2` or `open_channel` message. 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 /// omitted pub dust_limit_satoshis: u64, - /// The maximum inbound HTLC value in flight towards channel acceptor, in milli-satoshi + /// The maximum inbound HTLC value in flight towards sender, in milli-satoshi pub max_htlc_value_in_flight_msat: u64, /// The minimum HTLC size incoming to channel acceptor, in milli-satoshi pub htlc_minimum_msat: u64, @@ -371,8 +296,6 @@ pub struct AcceptChannelV2 { pub htlc_basepoint: PublicKey, /// The first to-be-broadcast-by-channel-acceptor transaction's per commitment point pub first_per_commitment_point: PublicKey, - /// The second to-be-broadcast-by-channel-acceptor transaction's per commitment point - pub second_per_commitment_point: PublicKey, /// Optionally, a request to pre-set the to-channel-acceptor output's scriptPubkey for when we /// collaboratively close pub shutdown_scriptpubkey: Option, @@ -380,8 +303,40 @@ pub struct AcceptChannelV2 { /// type from the intersection of our feature bits with our counterparty's feature bits from /// the Init message. /// - /// This is required to match the equivalent field in [`OpenChannelV2::channel_type`]. + /// This is required to match the equivalent field in [`OpenChannel`] or [`OpenChannelV2`]'s + /// [`CommonOpenChannelFields::channel_type`]. pub channel_type: Option, +} + +/// An [`accept_channel`] message to be sent to or received from a peer. +/// +/// Used in V1 channel establishment +/// +/// [`accept_channel`]: https://github.com/lightning/bolts/blob/master/02-peer-protocol.md#the-accept_channel-message +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +pub struct AcceptChannel { + /// Common fields of `accept_channel(2)`-like messages + pub common_fields: CommonAcceptChannelFields, + /// The minimum value unencumbered by HTLCs for the counterparty to keep in the channel + pub channel_reserve_satoshis: u64, + #[cfg(taproot)] + /// Next nonce the channel initiator should use to create a funding output signature against + pub next_local_nonce: Option, +} + +/// An accept_channel2 message to be sent by or received from the channel accepter. +/// +/// Used in V2 channel establishment +/// +// TODO(dual_funding): Add spec link for `accept_channel2`. +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +pub struct AcceptChannelV2 { + /// Common fields of `accept_channel(2)`-like messages + pub common_fields: CommonAcceptChannelFields, + /// Part of the channel value contributed by the channel acceptor + pub funding_satoshis: u64, + /// The second to-be-broadcast-by-channel-acceptor transaction's per commitment point + pub second_per_commitment_point: PublicKey, /// Optionally, a requirement that only confirmed inputs can be added pub require_confirmed_inputs: Option<()>, } @@ -441,6 +396,10 @@ pub struct ChannelReady { pub short_channel_id_alias: Option, } +/// A randomly chosen number that is used to identify inputs within an interactive transaction +/// construction. +pub type SerialId = u64; + /// An stfu (quiescence) message to be sent by or received from the stfu initiator. // TODO(splicing): Add spec link for `stfu`; still in draft, using from https://github.com/lightning/bolts/pull/863 #[derive(Clone, Debug, PartialEq, Eq)] @@ -495,10 +454,6 @@ pub struct SpliceLocked { pub channel_id: ChannelId, } -/// A randomly chosen number that is used to identify inputs within an interactive transaction -/// construction. -pub type SerialId = u64; - /// A tx_add_input message for adding an input during interactive transaction construction /// // TODO(dual_funding): Add spec link for `tx_add_input`. @@ -701,7 +656,7 @@ pub struct OnionMessage { /// Used in decrypting the onion packet's payload. pub blinding_point: PublicKey, /// The full onion packet including hop data, pubkey, and hmac - pub onion_routing_packet: onion_message::Packet, + pub onion_routing_packet: onion_message::packet::Packet, } /// An [`update_fulfill_htlc`] message to be sent to or received from a peer. @@ -894,6 +849,16 @@ impl SocketAddress { /// This maximum length is reached by a hostname address descriptor: /// a hostname with a maximum length of 255, its 1-byte length and a 2-byte port. pub(crate) const MAX_LEN: u16 = 258; + + pub(crate) fn is_tor(&self) -> bool { + match self { + &SocketAddress::TcpIpV4 {..} => false, + &SocketAddress::TcpIpV6 {..} => false, + &SocketAddress::OnionV2(_) => true, + &SocketAddress::OnionV3 {..} => true, + &SocketAddress::Hostname {..} => false, + } + } } impl Writeable for SocketAddress { @@ -1689,22 +1654,31 @@ pub trait OnionMessageHandler: EventsProvider { fn provided_init_features(&self, their_node_id: &PublicKey) -> InitFeatures; } +#[derive(Clone)] +#[cfg_attr(test, derive(Debug, PartialEq))] +/// Information communicated in the onion to the recipient for multi-part tracking and proof that +/// the payment is associated with an invoice. +pub struct FinalOnionHopData { + /// When sending a multi-part payment, this secret is used to identify a payment across HTLCs. + /// Because it is generated by the recipient and included in the invoice, it also provides + /// proof to the recipient that the payment was sent by someone with the generated invoice. + pub payment_secret: PaymentSecret, + /// The intended total amount that this payment is for. + /// + /// Message serialization may panic if this value is more than 21 million Bitcoin. + pub total_msat: u64, +} + mod fuzzy_internal_msgs { use bitcoin::secp256k1::PublicKey; use crate::blinded_path::payment::{PaymentConstraints, PaymentRelay}; use crate::prelude::*; use crate::ln::{PaymentPreimage, PaymentSecret}; use crate::ln::features::BlindedHopFeatures; + use super::FinalOnionHopData; // These types aren't intended to be pub, but are exposed for direct fuzzing (as we deserialize // them from untrusted input): - #[derive(Clone)] - 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 total_msat: u64, - } pub enum InboundOnionPayload { Forward { @@ -1718,23 +1692,23 @@ mod fuzzy_internal_msgs { payment_metadata: Option>, keysend_preimage: Option, custom_tlvs: Vec<(u64, Vec)>, - amt_msat: u64, - outgoing_cltv_value: u32, + sender_intended_htlc_amt_msat: u64, + cltv_expiry_height: u32, }, BlindedForward { short_channel_id: u64, payment_relay: PaymentRelay, payment_constraints: PaymentConstraints, features: BlindedHopFeatures, - intro_node_blinding_point: PublicKey, + intro_node_blinding_point: Option, }, BlindedReceive { - amt_msat: u64, + sender_intended_htlc_amt_msat: u64, total_msat: u64, - outgoing_cltv_value: u32, + cltv_expiry_height: u32, payment_secret: PaymentSecret, payment_constraints: PaymentConstraints, - intro_node_blinding_point: PublicKey, + intro_node_blinding_point: Option, } } @@ -1750,17 +1724,17 @@ mod fuzzy_internal_msgs { payment_metadata: Option>, keysend_preimage: Option, custom_tlvs: Vec<(u64, Vec)>, - amt_msat: u64, - outgoing_cltv_value: u32, + sender_intended_htlc_amt_msat: u64, + cltv_expiry_height: u32, }, BlindedForward { encrypted_tlvs: Vec, intro_node_blinding_point: Option, }, BlindedReceive { - amt_msat: u64, + sender_intended_htlc_amt_msat: u64, total_msat: u64, - outgoing_cltv_value: u32, + cltv_expiry_height: u32, encrypted_tlvs: Vec, intro_node_blinding_point: Option, // Set if the introduction node of the blinded path is the final node } @@ -1843,70 +1817,173 @@ impl From for DecodeError { } } -#[cfg(not(taproot))] -impl_writeable_msg!(AcceptChannel, { - temporary_channel_id, - dust_limit_satoshis, - max_htlc_value_in_flight_msat, - channel_reserve_satoshis, - htlc_minimum_msat, - minimum_depth, - to_self_delay, - max_accepted_htlcs, - funding_pubkey, - revocation_basepoint, - payment_point, - delayed_payment_basepoint, - htlc_basepoint, - first_per_commitment_point, -}, { - (0, shutdown_scriptpubkey, (option, encoding: (ScriptBuf, WithoutLength))), // Don't encode length twice. - (1, channel_type, option), -}); +impl Writeable for AcceptChannel { + fn write(&self, w: &mut W) -> Result<(), io::Error> { + self.common_fields.temporary_channel_id.write(w)?; + self.common_fields.dust_limit_satoshis.write(w)?; + self.common_fields.max_htlc_value_in_flight_msat.write(w)?; + self.channel_reserve_satoshis.write(w)?; + self.common_fields.htlc_minimum_msat.write(w)?; + self.common_fields.minimum_depth.write(w)?; + self.common_fields.to_self_delay.write(w)?; + self.common_fields.max_accepted_htlcs.write(w)?; + self.common_fields.funding_pubkey.write(w)?; + self.common_fields.revocation_basepoint.write(w)?; + self.common_fields.payment_basepoint.write(w)?; + self.common_fields.delayed_payment_basepoint.write(w)?; + self.common_fields.htlc_basepoint.write(w)?; + self.common_fields.first_per_commitment_point.write(w)?; + #[cfg(not(taproot))] + encode_tlv_stream!(w, { + (0, self.common_fields.shutdown_scriptpubkey.as_ref().map(|s| WithoutLength(s)), option), // Don't encode length twice. + (1, self.common_fields.channel_type, option), + }); + #[cfg(taproot)] + encode_tlv_stream!(w, { + (0, self.common_fields.shutdown_scriptpubkey.as_ref().map(|s| WithoutLength(s)), option), // Don't encode length twice. + (1, self.common_fields.channel_type, option), + (4, self.next_local_nonce, option), + }); + Ok(()) + } +} -#[cfg(taproot)] -impl_writeable_msg!(AcceptChannel, { - temporary_channel_id, - dust_limit_satoshis, - max_htlc_value_in_flight_msat, - channel_reserve_satoshis, - htlc_minimum_msat, - minimum_depth, - to_self_delay, - max_accepted_htlcs, - funding_pubkey, - revocation_basepoint, - payment_point, - delayed_payment_basepoint, - htlc_basepoint, - first_per_commitment_point, -}, { - (0, shutdown_scriptpubkey, (option, encoding: (ScriptBuf, WithoutLength))), // Don't encode length twice. - (1, channel_type, option), - (4, next_local_nonce, option), -}); +impl Readable for AcceptChannel { + fn read(r: &mut R) -> Result { + let temporary_channel_id: ChannelId = Readable::read(r)?; + let dust_limit_satoshis: u64 = Readable::read(r)?; + let max_htlc_value_in_flight_msat: u64 = Readable::read(r)?; + let channel_reserve_satoshis: u64 = Readable::read(r)?; + let htlc_minimum_msat: u64 = Readable::read(r)?; + let minimum_depth: u32 = Readable::read(r)?; + let to_self_delay: u16 = Readable::read(r)?; + let max_accepted_htlcs: u16 = Readable::read(r)?; + let funding_pubkey: PublicKey = Readable::read(r)?; + let revocation_basepoint: PublicKey = Readable::read(r)?; + let payment_basepoint: PublicKey = Readable::read(r)?; + let delayed_payment_basepoint: PublicKey = Readable::read(r)?; + let htlc_basepoint: PublicKey = Readable::read(r)?; + let first_per_commitment_point: PublicKey = Readable::read(r)?; + + let mut shutdown_scriptpubkey: Option = None; + let mut channel_type: Option = None; + #[cfg(not(taproot))] + decode_tlv_stream!(r, { + (0, shutdown_scriptpubkey, (option, encoding: (ScriptBuf, WithoutLength))), + (1, channel_type, option), + }); + #[cfg(taproot)] + let mut next_local_nonce: Option = None; + #[cfg(taproot)] + decode_tlv_stream!(r, { + (0, shutdown_scriptpubkey, (option, encoding: (ScriptBuf, WithoutLength))), + (1, channel_type, option), + (4, next_local_nonce, option), + }); -impl_writeable_msg!(AcceptChannelV2, { - temporary_channel_id, - funding_satoshis, - dust_limit_satoshis, - max_htlc_value_in_flight_msat, - htlc_minimum_msat, - minimum_depth, - to_self_delay, - max_accepted_htlcs, - funding_pubkey, - revocation_basepoint, - payment_basepoint, - delayed_payment_basepoint, - htlc_basepoint, - first_per_commitment_point, - second_per_commitment_point, -}, { - (0, shutdown_scriptpubkey, option), - (1, channel_type, option), - (2, require_confirmed_inputs, option), -}); + Ok(AcceptChannel { + common_fields: CommonAcceptChannelFields { + temporary_channel_id, + dust_limit_satoshis, + max_htlc_value_in_flight_msat, + htlc_minimum_msat, + minimum_depth, + to_self_delay, + max_accepted_htlcs, + funding_pubkey, + revocation_basepoint, + payment_basepoint, + delayed_payment_basepoint, + htlc_basepoint, + first_per_commitment_point, + shutdown_scriptpubkey, + channel_type, + }, + channel_reserve_satoshis, + #[cfg(taproot)] + next_local_nonce, + }) + } +} + +impl Writeable for AcceptChannelV2 { + fn write(&self, w: &mut W) -> Result<(), io::Error> { + self.common_fields.temporary_channel_id.write(w)?; + self.funding_satoshis.write(w)?; + self.common_fields.dust_limit_satoshis.write(w)?; + self.common_fields.max_htlc_value_in_flight_msat.write(w)?; + self.common_fields.htlc_minimum_msat.write(w)?; + self.common_fields.minimum_depth.write(w)?; + self.common_fields.to_self_delay.write(w)?; + self.common_fields.max_accepted_htlcs.write(w)?; + self.common_fields.funding_pubkey.write(w)?; + self.common_fields.revocation_basepoint.write(w)?; + self.common_fields.payment_basepoint.write(w)?; + self.common_fields.delayed_payment_basepoint.write(w)?; + self.common_fields.htlc_basepoint.write(w)?; + self.common_fields.first_per_commitment_point.write(w)?; + self.second_per_commitment_point.write(w)?; + + encode_tlv_stream!(w, { + (0, self.common_fields.shutdown_scriptpubkey.as_ref().map(|s| WithoutLength(s)), option), // Don't encode length twice. + (1, self.common_fields.channel_type, option), + (2, self.require_confirmed_inputs, option), + }); + Ok(()) + } +} + +impl Readable for AcceptChannelV2 { + fn read(r: &mut R) -> Result { + let temporary_channel_id: ChannelId = Readable::read(r)?; + let funding_satoshis: u64 = Readable::read(r)?; + let dust_limit_satoshis: u64 = Readable::read(r)?; + let max_htlc_value_in_flight_msat: u64 = Readable::read(r)?; + let htlc_minimum_msat: u64 = Readable::read(r)?; + let minimum_depth: u32 = Readable::read(r)?; + let to_self_delay: u16 = Readable::read(r)?; + let max_accepted_htlcs: u16 = Readable::read(r)?; + let funding_pubkey: PublicKey = Readable::read(r)?; + let revocation_basepoint: PublicKey = Readable::read(r)?; + let payment_basepoint: PublicKey = Readable::read(r)?; + let delayed_payment_basepoint: PublicKey = Readable::read(r)?; + let htlc_basepoint: PublicKey = Readable::read(r)?; + let first_per_commitment_point: PublicKey = Readable::read(r)?; + let second_per_commitment_point: PublicKey = Readable::read(r)?; + + let mut shutdown_scriptpubkey: Option = None; + let mut channel_type: Option = None; + let mut require_confirmed_inputs: Option<()> = None; + decode_tlv_stream!(r, { + (0, shutdown_scriptpubkey, (option, encoding: (ScriptBuf, WithoutLength))), + (1, channel_type, option), + (2, require_confirmed_inputs, option), + }); + + Ok(AcceptChannelV2 { + common_fields: CommonAcceptChannelFields { + temporary_channel_id, + dust_limit_satoshis, + max_htlc_value_in_flight_msat, + htlc_minimum_msat, + minimum_depth, + to_self_delay, + max_accepted_htlcs, + funding_pubkey, + revocation_basepoint, + payment_basepoint, + delayed_payment_basepoint, + htlc_basepoint, + first_per_commitment_point, + shutdown_scriptpubkey, + channel_type, + }, + funding_satoshis, + second_per_commitment_point, + require_confirmed_inputs, + }) + } +} impl_writeable_msg!(Stfu, { channel_id, @@ -2109,55 +2186,176 @@ impl Readable for Init { } } -impl_writeable_msg!(OpenChannel, { - chain_hash, - temporary_channel_id, - funding_satoshis, - push_msat, - dust_limit_satoshis, - max_htlc_value_in_flight_msat, - channel_reserve_satoshis, - htlc_minimum_msat, - feerate_per_kw, - to_self_delay, - max_accepted_htlcs, - funding_pubkey, - revocation_basepoint, - payment_point, - delayed_payment_basepoint, - htlc_basepoint, - first_per_commitment_point, - channel_flags, -}, { - (0, shutdown_scriptpubkey, (option, encoding: (ScriptBuf, WithoutLength))), // Don't encode length twice. - (1, channel_type, option), -}); +impl Writeable for OpenChannel { + fn write(&self, w: &mut W) -> Result<(), io::Error> { + self.common_fields.chain_hash.write(w)?; + self.common_fields.temporary_channel_id.write(w)?; + self.common_fields.funding_satoshis.write(w)?; + self.push_msat.write(w)?; + self.common_fields.dust_limit_satoshis.write(w)?; + self.common_fields.max_htlc_value_in_flight_msat.write(w)?; + self.channel_reserve_satoshis.write(w)?; + self.common_fields.htlc_minimum_msat.write(w)?; + self.common_fields.commitment_feerate_sat_per_1000_weight.write(w)?; + self.common_fields.to_self_delay.write(w)?; + self.common_fields.max_accepted_htlcs.write(w)?; + self.common_fields.funding_pubkey.write(w)?; + self.common_fields.revocation_basepoint.write(w)?; + self.common_fields.payment_basepoint.write(w)?; + self.common_fields.delayed_payment_basepoint.write(w)?; + self.common_fields.htlc_basepoint.write(w)?; + self.common_fields.first_per_commitment_point.write(w)?; + self.common_fields.channel_flags.write(w)?; + encode_tlv_stream!(w, { + (0, self.common_fields.shutdown_scriptpubkey.as_ref().map(|s| WithoutLength(s)), option), // Don't encode length twice. + (1, self.common_fields.channel_type, option), + }); + Ok(()) + } +} -impl_writeable_msg!(OpenChannelV2, { - chain_hash, - temporary_channel_id, - funding_feerate_sat_per_1000_weight, - commitment_feerate_sat_per_1000_weight, - funding_satoshis, - dust_limit_satoshis, - max_htlc_value_in_flight_msat, - htlc_minimum_msat, - to_self_delay, - max_accepted_htlcs, - locktime, - funding_pubkey, - revocation_basepoint, - payment_basepoint, - delayed_payment_basepoint, - htlc_basepoint, - first_per_commitment_point, - second_per_commitment_point, - channel_flags, -}, { - (0, shutdown_scriptpubkey, option), - (1, channel_type, option), - (2, require_confirmed_inputs, option), -}); +impl Readable for OpenChannel { + fn read(r: &mut R) -> Result { + let chain_hash: ChainHash = Readable::read(r)?; + let temporary_channel_id: ChannelId = Readable::read(r)?; + let funding_satoshis: u64 = Readable::read(r)?; + let push_msat: u64 = Readable::read(r)?; + let dust_limit_satoshis: u64 = Readable::read(r)?; + let max_htlc_value_in_flight_msat: u64 = Readable::read(r)?; + let channel_reserve_satoshis: u64 = Readable::read(r)?; + let htlc_minimum_msat: u64 = Readable::read(r)?; + let commitment_feerate_sat_per_1000_weight: u32 = Readable::read(r)?; + let to_self_delay: u16 = Readable::read(r)?; + let max_accepted_htlcs: u16 = Readable::read(r)?; + let funding_pubkey: PublicKey = Readable::read(r)?; + let revocation_basepoint: PublicKey = Readable::read(r)?; + let payment_basepoint: PublicKey = Readable::read(r)?; + let delayed_payment_basepoint: PublicKey = Readable::read(r)?; + let htlc_basepoint: PublicKey = Readable::read(r)?; + let first_per_commitment_point: PublicKey = Readable::read(r)?; + let channel_flags: u8 = Readable::read(r)?; + + let mut shutdown_scriptpubkey: Option = None; + let mut channel_type: Option = None; + decode_tlv_stream!(r, { + (0, shutdown_scriptpubkey, (option, encoding: (ScriptBuf, WithoutLength))), + (1, channel_type, option), + }); + Ok(OpenChannel { + common_fields: CommonOpenChannelFields { + chain_hash, + temporary_channel_id, + funding_satoshis, + dust_limit_satoshis, + max_htlc_value_in_flight_msat, + htlc_minimum_msat, + commitment_feerate_sat_per_1000_weight, + to_self_delay, + max_accepted_htlcs, + funding_pubkey, + revocation_basepoint, + payment_basepoint, + delayed_payment_basepoint, + htlc_basepoint, + first_per_commitment_point, + channel_flags, + shutdown_scriptpubkey, + channel_type, + }, + push_msat, + channel_reserve_satoshis, + }) + } +} + +impl Writeable for OpenChannelV2 { + fn write(&self, w: &mut W) -> Result<(), io::Error> { + self.common_fields.chain_hash.write(w)?; + self.common_fields.temporary_channel_id.write(w)?; + self.funding_feerate_sat_per_1000_weight.write(w)?; + self.common_fields.commitment_feerate_sat_per_1000_weight.write(w)?; + self.common_fields.funding_satoshis.write(w)?; + self.common_fields.dust_limit_satoshis.write(w)?; + self.common_fields.max_htlc_value_in_flight_msat.write(w)?; + self.common_fields.htlc_minimum_msat.write(w)?; + self.common_fields.to_self_delay.write(w)?; + self.common_fields.max_accepted_htlcs.write(w)?; + self.locktime.write(w)?; + self.common_fields.funding_pubkey.write(w)?; + self.common_fields.revocation_basepoint.write(w)?; + self.common_fields.payment_basepoint.write(w)?; + self.common_fields.delayed_payment_basepoint.write(w)?; + self.common_fields.htlc_basepoint.write(w)?; + self.common_fields.first_per_commitment_point.write(w)?; + self.second_per_commitment_point.write(w)?; + self.common_fields.channel_flags.write(w)?; + encode_tlv_stream!(w, { + (0, self.common_fields.shutdown_scriptpubkey.as_ref().map(|s| WithoutLength(s)), option), // Don't encode length twice. + (1, self.common_fields.channel_type, option), + (2, self.require_confirmed_inputs, option), + }); + Ok(()) + } +} + +impl Readable for OpenChannelV2 { + fn read(r: &mut R) -> Result { + let chain_hash: ChainHash = Readable::read(r)?; + let temporary_channel_id: ChannelId = Readable::read(r)?; + let funding_feerate_sat_per_1000_weight: u32 = Readable::read(r)?; + let commitment_feerate_sat_per_1000_weight: u32 = Readable::read(r)?; + let funding_satoshis: u64 = Readable::read(r)?; + let dust_limit_satoshis: u64 = Readable::read(r)?; + let max_htlc_value_in_flight_msat: u64 = Readable::read(r)?; + let htlc_minimum_msat: u64 = Readable::read(r)?; + let to_self_delay: u16 = Readable::read(r)?; + let max_accepted_htlcs: u16 = Readable::read(r)?; + let locktime: u32 = Readable::read(r)?; + let funding_pubkey: PublicKey = Readable::read(r)?; + let revocation_basepoint: PublicKey = Readable::read(r)?; + let payment_basepoint: PublicKey = Readable::read(r)?; + let delayed_payment_basepoint: PublicKey = Readable::read(r)?; + let htlc_basepoint: PublicKey = Readable::read(r)?; + let first_per_commitment_point: PublicKey = Readable::read(r)?; + let second_per_commitment_point: PublicKey = Readable::read(r)?; + let channel_flags: u8 = Readable::read(r)?; + + let mut shutdown_scriptpubkey: Option = None; + let mut channel_type: Option = None; + let mut require_confirmed_inputs: Option<()> = None; + decode_tlv_stream!(r, { + (0, shutdown_scriptpubkey, (option, encoding: (ScriptBuf, WithoutLength))), + (1, channel_type, option), + (2, require_confirmed_inputs, option), + }); + Ok(OpenChannelV2 { + common_fields: CommonOpenChannelFields { + chain_hash, + temporary_channel_id, + funding_satoshis, + dust_limit_satoshis, + max_htlc_value_in_flight_msat, + htlc_minimum_msat, + commitment_feerate_sat_per_1000_weight, + to_self_delay, + max_accepted_htlcs, + funding_pubkey, + revocation_basepoint, + payment_basepoint, + delayed_payment_basepoint, + htlc_basepoint, + first_per_commitment_point, + channel_flags, + shutdown_scriptpubkey, + channel_type, + }, + funding_feerate_sat_per_1000_weight, + locktime, + second_per_commitment_point, + require_confirmed_inputs, + }) + } +} #[cfg(not(taproot))] impl_writeable_msg!(RevokeAndACK, { @@ -2259,7 +2457,8 @@ impl Readable for OnionMessage { let blinding_point: PublicKey = Readable::read(r)?; let len: u16 = Readable::read(r)?; let mut packet_reader = FixedLengthReader::new(r, len as u64); - let onion_routing_packet: onion_message::Packet = ::read(&mut packet_reader)?; + let onion_routing_packet: onion_message::packet::Packet = + ::read(&mut packet_reader)?; Ok(Self { blinding_point, onion_routing_packet, @@ -2303,8 +2502,8 @@ impl Writeable for OutboundOnionPayload { }); }, Self::Receive { - ref payment_data, ref payment_metadata, ref keysend_preimage, amt_msat, - outgoing_cltv_value, ref custom_tlvs, + ref payment_data, ref payment_metadata, ref keysend_preimage, sender_intended_htlc_amt_msat, + cltv_expiry_height, 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 @@ -2313,8 +2512,8 @@ impl Writeable for OutboundOnionPayload { let mut custom_tlvs: Vec<&(u64, Vec)> = custom_tlvs.iter().chain(keysend_tlv.iter()).collect(); custom_tlvs.sort_unstable_by_key(|(typ, _)| *typ); _encode_varint_length_prefixed_tlv!(w, { - (2, HighZeroBytesDroppedBigSize(*amt_msat), required), - (4, HighZeroBytesDroppedBigSize(*outgoing_cltv_value), required), + (2, HighZeroBytesDroppedBigSize(*sender_intended_htlc_amt_msat), required), + (4, HighZeroBytesDroppedBigSize(*cltv_expiry_height), required), (8, payment_data, option), (16, payment_metadata.as_ref().map(|m| WithoutLength(m)), option) }, custom_tlvs.iter()); @@ -2326,12 +2525,12 @@ impl Writeable for OutboundOnionPayload { }); }, Self::BlindedReceive { - amt_msat, total_msat, outgoing_cltv_value, encrypted_tlvs, + sender_intended_htlc_amt_msat, total_msat, cltv_expiry_height, encrypted_tlvs, intro_node_blinding_point, } => { _encode_varint_length_prefixed_tlv!(w, { - (2, HighZeroBytesDroppedBigSize(*amt_msat), required), - (4, HighZeroBytesDroppedBigSize(*outgoing_cltv_value), required), + (2, HighZeroBytesDroppedBigSize(*sender_intended_htlc_amt_msat), required), + (4, HighZeroBytesDroppedBigSize(*cltv_expiry_height), required), (10, *encrypted_tlvs, required_vec), (12, intro_node_blinding_point, option), (18, HighZeroBytesDroppedBigSize(*total_msat), required) @@ -2342,8 +2541,10 @@ impl Writeable for OutboundOnionPayload { } } -impl ReadableArgs<&NS> for InboundOnionPayload where NS::Target: NodeSigner { - fn read(r: &mut R, node_signer: &NS) -> Result { +impl ReadableArgs<(Option, &NS)> for InboundOnionPayload where NS::Target: NodeSigner { + fn read(r: &mut R, args: (Option, &NS)) -> Result { + let (update_add_blinding_point, node_signer) = args; + let mut amt = None; let mut cltv_value = None; let mut short_id: Option = None; @@ -2377,9 +2578,14 @@ impl ReadableArgs<&NS> for InboundOnionPayload where NS::Target: Node }); if amt.unwrap_or(0) > MAX_VALUE_MSAT { return Err(DecodeError::InvalidValue) } + if intro_node_blinding_point.is_some() && update_add_blinding_point.is_some() { + 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() { + if let Some(blinding_point) = intro_node_blinding_point.or(update_add_blinding_point) { + if short_id.is_some() || payment_data.is_some() || payment_metadata.is_some() || + keysend_preimage.is_some() + { return Err(DecodeError::InvalidValue) } let enc_tlvs = encrypted_tlvs_opt.ok_or(DecodeError::InvalidValue)?.0; @@ -2400,7 +2606,7 @@ impl ReadableArgs<&NS> for InboundOnionPayload where NS::Target: Node payment_relay, payment_constraints, features, - intro_node_blinding_point: blinding_point, + intro_node_blinding_point, }) }, ChaChaPolyReadAdapter { readable: BlindedPaymentTlvs::Receive(ReceiveTlvs { @@ -2408,12 +2614,12 @@ impl ReadableArgs<&NS> for InboundOnionPayload where NS::Target: Node })} => { if total_msat.unwrap_or(0) > MAX_VALUE_MSAT { return Err(DecodeError::InvalidValue) } Ok(Self::BlindedReceive { - amt_msat: amt.ok_or(DecodeError::InvalidValue)?, + sender_intended_htlc_amt_msat: amt.ok_or(DecodeError::InvalidValue)?, total_msat: total_msat.ok_or(DecodeError::InvalidValue)?, - outgoing_cltv_value: cltv_value.ok_or(DecodeError::InvalidValue)?, + cltv_expiry_height: cltv_value.ok_or(DecodeError::InvalidValue)?, payment_secret, payment_constraints, - intro_node_blinding_point: blinding_point, + intro_node_blinding_point, }) }, } @@ -2439,8 +2645,8 @@ impl ReadableArgs<&NS> for InboundOnionPayload where NS::Target: Node payment_data, payment_metadata: payment_metadata.map(|w| w.0), keysend_preimage, - amt_msat: amt.ok_or(DecodeError::InvalidValue)?, - outgoing_cltv_value: cltv_value.ok_or(DecodeError::InvalidValue)?, + sender_intended_htlc_amt_msat: amt.ok_or(DecodeError::InvalidValue)?, + cltv_expiry_height: cltv_value.ok_or(DecodeError::InvalidValue)?, custom_tlvs, }) } @@ -2860,7 +3066,7 @@ mod tests { 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}; + use crate::ln::msgs::{self, FinalOnionHopData, OnionErrorPacket, CommonOpenChannelFields, CommonAcceptChannelFields}; use crate::ln::msgs::SocketAddress; use crate::routing::gossip::{NodeAlias, NodeId}; use crate::util::ser::{Writeable, Readable, ReadableArgs, Hostname, TransactionU16LenLimited}; @@ -3212,26 +3418,28 @@ mod tests { let (_, pubkey_5) = get_keys_from!("0505050505050505050505050505050505050505050505050505050505050505", secp_ctx); let (_, pubkey_6) = get_keys_from!("0606060606060606060606060606060606060606060606060606060606060606", secp_ctx); let open_channel = msgs::OpenChannel { - chain_hash: ChainHash::using_genesis_block(Network::Bitcoin), - temporary_channel_id: ChannelId::from_bytes([2; 32]), - funding_satoshis: 1311768467284833366, + common_fields: CommonOpenChannelFields { + chain_hash: ChainHash::using_genesis_block(Network::Bitcoin), + temporary_channel_id: ChannelId::from_bytes([2; 32]), + funding_satoshis: 1311768467284833366, + dust_limit_satoshis: 3608586615801332854, + max_htlc_value_in_flight_msat: 8517154655701053848, + htlc_minimum_msat: 2316138423780173, + commitment_feerate_sat_per_1000_weight: 821716, + to_self_delay: 49340, + max_accepted_htlcs: 49340, + funding_pubkey: pubkey_1, + revocation_basepoint: pubkey_2, + payment_basepoint: pubkey_3, + delayed_payment_basepoint: pubkey_4, + htlc_basepoint: pubkey_5, + first_per_commitment_point: pubkey_6, + channel_flags: if random_bit { 1 << 5 } else { 0 }, + shutdown_scriptpubkey: if shutdown { Some(Address::p2pkh(&::bitcoin::PublicKey{compressed: true, inner: pubkey_1}, Network::Testnet).script_pubkey()) } else { None }, + channel_type: if incl_chan_type { Some(ChannelTypeFeatures::empty()) } else { None }, + }, push_msat: 2536655962884945560, - dust_limit_satoshis: 3608586615801332854, - max_htlc_value_in_flight_msat: 8517154655701053848, channel_reserve_satoshis: 8665828695742877976, - htlc_minimum_msat: 2316138423780173, - feerate_per_kw: 821716, - to_self_delay: 49340, - max_accepted_htlcs: 49340, - funding_pubkey: pubkey_1, - revocation_basepoint: pubkey_2, - payment_point: pubkey_3, - delayed_payment_basepoint: pubkey_4, - htlc_basepoint: pubkey_5, - first_per_commitment_point: pubkey_6, - channel_flags: if random_bit { 1 << 5 } else { 0 }, - shutdown_scriptpubkey: if shutdown { Some(Address::p2pkh(&::bitcoin::PublicKey{compressed: true, inner: pubkey_1}, Network::Testnet).script_pubkey()) } else { None }, - channel_type: if incl_chan_type { Some(ChannelTypeFeatures::empty()) } else { None }, }; let encoded_value = open_channel.encode(); let mut target_value = Vec::new(); @@ -3273,27 +3481,29 @@ mod tests { let (_, pubkey_6) = get_keys_from!("0606060606060606060606060606060606060606060606060606060606060606", secp_ctx); let (_, pubkey_7) = get_keys_from!("0707070707070707070707070707070707070707070707070707070707070707", secp_ctx); let open_channelv2 = msgs::OpenChannelV2 { - chain_hash: ChainHash::using_genesis_block(Network::Bitcoin), - temporary_channel_id: ChannelId::from_bytes([2; 32]), + common_fields: CommonOpenChannelFields { + chain_hash: ChainHash::using_genesis_block(Network::Bitcoin), + temporary_channel_id: ChannelId::from_bytes([2; 32]), + commitment_feerate_sat_per_1000_weight: 821716, + funding_satoshis: 1311768467284833366, + dust_limit_satoshis: 3608586615801332854, + max_htlc_value_in_flight_msat: 8517154655701053848, + htlc_minimum_msat: 2316138423780173, + to_self_delay: 49340, + max_accepted_htlcs: 49340, + funding_pubkey: pubkey_1, + revocation_basepoint: pubkey_2, + payment_basepoint: pubkey_3, + delayed_payment_basepoint: pubkey_4, + htlc_basepoint: pubkey_5, + first_per_commitment_point: pubkey_6, + channel_flags: if random_bit { 1 << 5 } else { 0 }, + shutdown_scriptpubkey: if shutdown { Some(Address::p2pkh(&::bitcoin::PublicKey{compressed: true, inner: pubkey_1}, Network::Testnet).script_pubkey()) } else { None }, + channel_type: if incl_chan_type { Some(ChannelTypeFeatures::empty()) } else { None }, + }, funding_feerate_sat_per_1000_weight: 821716, - commitment_feerate_sat_per_1000_weight: 821716, - funding_satoshis: 1311768467284833366, - dust_limit_satoshis: 3608586615801332854, - max_htlc_value_in_flight_msat: 8517154655701053848, - htlc_minimum_msat: 2316138423780173, - to_self_delay: 49340, - max_accepted_htlcs: 49340, locktime: 305419896, - funding_pubkey: pubkey_1, - revocation_basepoint: pubkey_2, - payment_basepoint: pubkey_3, - delayed_payment_basepoint: pubkey_4, - htlc_basepoint: pubkey_5, - first_per_commitment_point: pubkey_6, second_per_commitment_point: pubkey_7, - channel_flags: if random_bit { 1 << 5 } else { 0 }, - shutdown_scriptpubkey: if shutdown { Some(Address::p2pkh(&::bitcoin::PublicKey{compressed: true, inner: pubkey_1}, Network::Testnet).script_pubkey()) } else { None }, - channel_type: if incl_chan_type { Some(ChannelTypeFeatures::empty()) } else { None }, require_confirmed_inputs: if require_confirmed_inputs { Some(()) } else { None }, }; let encoded_value = open_channelv2.encode(); @@ -3323,7 +3533,6 @@ mod tests { target_value.append(&mut >::from_hex("00").unwrap()); } if shutdown { - target_value.append(&mut >::from_hex("001b").unwrap()); // Type 0 + Length 27 target_value.append(&mut >::from_hex("001976a91479b000887626b294a914501a4cd226b58b23598388ac").unwrap()); } if incl_chan_type { @@ -3364,22 +3573,24 @@ mod tests { 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: ChannelId::from_bytes([2; 32]), - dust_limit_satoshis: 1311768467284833366, - max_htlc_value_in_flight_msat: 2536655962884945560, + common_fields: CommonAcceptChannelFields { + temporary_channel_id: ChannelId::from_bytes([2; 32]), + dust_limit_satoshis: 1311768467284833366, + max_htlc_value_in_flight_msat: 2536655962884945560, + htlc_minimum_msat: 2316138423780173, + minimum_depth: 821716, + to_self_delay: 49340, + max_accepted_htlcs: 49340, + funding_pubkey: pubkey_1, + revocation_basepoint: pubkey_2, + payment_basepoint: pubkey_3, + delayed_payment_basepoint: pubkey_4, + htlc_basepoint: pubkey_5, + first_per_commitment_point: pubkey_6, + shutdown_scriptpubkey: if shutdown { Some(Address::p2pkh(&::bitcoin::PublicKey{compressed: true, inner: pubkey_1}, Network::Testnet).script_pubkey()) } else { None }, + channel_type: None, + }, channel_reserve_satoshis: 3608586615801332854, - htlc_minimum_msat: 2316138423780173, - minimum_depth: 821716, - to_self_delay: 49340, - max_accepted_htlcs: 49340, - funding_pubkey: pubkey_1, - revocation_basepoint: pubkey_2, - payment_point: pubkey_3, - delayed_payment_basepoint: pubkey_4, - htlc_basepoint: pubkey_5, - first_per_commitment_point: pubkey_6, - shutdown_scriptpubkey: if shutdown { Some(Address::p2pkh(&::bitcoin::PublicKey{compressed: true, inner: pubkey_1}, Network::Testnet).script_pubkey()) } else { None }, - channel_type: None, #[cfg(taproot)] next_local_nonce: None, }; @@ -3407,23 +3618,25 @@ mod tests { 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: ChannelId::from_bytes([2; 32]), + common_fields: CommonAcceptChannelFields { + temporary_channel_id: ChannelId::from_bytes([2; 32]), + dust_limit_satoshis: 1311768467284833366, + max_htlc_value_in_flight_msat: 2536655962884945560, + htlc_minimum_msat: 2316138423780173, + minimum_depth: 821716, + to_self_delay: 49340, + max_accepted_htlcs: 49340, + funding_pubkey: pubkey_1, + revocation_basepoint: pubkey_2, + payment_basepoint: pubkey_3, + delayed_payment_basepoint: pubkey_4, + htlc_basepoint: pubkey_5, + first_per_commitment_point: pubkey_6, + shutdown_scriptpubkey: if shutdown { Some(Address::p2pkh(&::bitcoin::PublicKey{compressed: true, inner: pubkey_1}, Network::Testnet).script_pubkey()) } else { None }, + channel_type: None, + }, funding_satoshis: 1311768467284833366, - dust_limit_satoshis: 1311768467284833366, - max_htlc_value_in_flight_msat: 2536655962884945560, - htlc_minimum_msat: 2316138423780173, - minimum_depth: 821716, - to_self_delay: 49340, - max_accepted_htlcs: 49340, - funding_pubkey: pubkey_1, - revocation_basepoint: pubkey_2, - payment_basepoint: pubkey_3, - delayed_payment_basepoint: pubkey_4, - htlc_basepoint: pubkey_5, - first_per_commitment_point: pubkey_6, second_per_commitment_point: pubkey_7, - shutdown_scriptpubkey: if shutdown { Some(Address::p2pkh(&::bitcoin::PublicKey{compressed: true, inner: pubkey_1}, Network::Testnet).script_pubkey()) } else { None }, - channel_type: None, require_confirmed_inputs: None, }; let encoded_value = accept_channelv2.encode(); @@ -3443,7 +3656,6 @@ mod tests { target_value.append(&mut >::from_hex("03f006a18d5653c4edf5391ff23a61f03ff83d237e880ee61187fa9f379a028e0a").unwrap()); // first_per_commitment_point target_value.append(&mut >::from_hex("02989c0b76cb563971fdc9bef31ec06c3560f3249d6ee9e5d83c57625596e05f6f").unwrap()); // second_per_commitment_point if shutdown { - target_value.append(&mut >::from_hex("001b").unwrap()); // Type 0 + Length 27 target_value.append(&mut >::from_hex("001976a91479b000887626b294a914501a4cd226b58b23598388ac").unwrap()); } assert_eq!(encoded_value, target_value); @@ -4017,7 +4229,7 @@ mod tests { assert_eq!(encoded_value, target_value); let node_signer = test_utils::TestKeysInterface::new(&[42; 32], Network::Testnet); - let inbound_msg = ReadableArgs::read(&mut Cursor::new(&target_value[..]), &&node_signer).unwrap(); + let inbound_msg = ReadableArgs::read(&mut Cursor::new(&target_value[..]), (None, &&node_signer)).unwrap(); if let msgs::InboundOnionPayload::Forward { short_channel_id, amt_to_forward, outgoing_cltv_value } = inbound_msg { @@ -4033,8 +4245,8 @@ mod tests { payment_data: None, payment_metadata: None, keysend_preimage: None, - amt_msat: 0x0badf00d01020304, - outgoing_cltv_value: 0xffffffff, + sender_intended_htlc_amt_msat: 0x0badf00d01020304, + cltv_expiry_height: 0xffffffff, custom_tlvs: vec![], }; let encoded_value = outbound_msg.encode(); @@ -4042,12 +4254,12 @@ mod tests { assert_eq!(encoded_value, target_value); let node_signer = test_utils::TestKeysInterface::new(&[42; 32], Network::Testnet); - let inbound_msg = ReadableArgs::read(&mut Cursor::new(&target_value[..]), &&node_signer).unwrap(); + let inbound_msg = ReadableArgs::read(&mut Cursor::new(&target_value[..]), (None, &&node_signer)).unwrap(); if let msgs::InboundOnionPayload::Receive { - payment_data: None, amt_msat, outgoing_cltv_value, .. + payment_data: None, sender_intended_htlc_amt_msat, cltv_expiry_height, .. } = inbound_msg { - assert_eq!(amt_msat, 0x0badf00d01020304); - assert_eq!(outgoing_cltv_value, 0xffffffff); + assert_eq!(sender_intended_htlc_amt_msat, 0x0badf00d01020304); + assert_eq!(cltv_expiry_height, 0xffffffff); } else { panic!(); } } @@ -4061,8 +4273,8 @@ mod tests { }), payment_metadata: None, keysend_preimage: None, - amt_msat: 0x0badf00d01020304, - outgoing_cltv_value: 0xffffffff, + sender_intended_htlc_amt_msat: 0x0badf00d01020304, + cltv_expiry_height: 0xffffffff, custom_tlvs: vec![], }; let encoded_value = outbound_msg.encode(); @@ -4070,20 +4282,20 @@ mod tests { assert_eq!(encoded_value, target_value); let node_signer = test_utils::TestKeysInterface::new(&[42; 32], Network::Testnet); - let inbound_msg = ReadableArgs::read(&mut Cursor::new(&target_value[..]), &&node_signer).unwrap(); + let inbound_msg = ReadableArgs::read(&mut Cursor::new(&target_value[..]), (None, &&node_signer)).unwrap(); if let msgs::InboundOnionPayload::Receive { payment_data: Some(FinalOnionHopData { payment_secret, total_msat: 0x1badca1f }), - amt_msat, outgoing_cltv_value, + sender_intended_htlc_amt_msat, cltv_expiry_height, payment_metadata: None, keysend_preimage: None, custom_tlvs, } = inbound_msg { assert_eq!(payment_secret, expected_payment_secret); - assert_eq!(amt_msat, 0x0badf00d01020304); - assert_eq!(outgoing_cltv_value, 0xffffffff); + assert_eq!(sender_intended_htlc_amt_msat, 0x0badf00d01020304); + assert_eq!(cltv_expiry_height, 0xffffffff); assert_eq!(custom_tlvs, vec![]); } else { panic!(); } } @@ -4101,12 +4313,12 @@ mod tests { payment_metadata: None, keysend_preimage: None, custom_tlvs: bad_type_range_tlvs, - amt_msat: 0x0badf00d01020304, - outgoing_cltv_value: 0xffffffff, + sender_intended_htlc_amt_msat: 0x0badf00d01020304, + cltv_expiry_height: 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()); + assert!(msgs::InboundOnionPayload::read(&mut Cursor::new(&encoded_value[..]), (None, &&node_signer)).is_err()); let good_type_range_tlvs = vec![ ((1 << 16) - 3, vec![42]), ((1 << 16) - 1, vec![42; 32]), @@ -4115,7 +4327,7 @@ mod tests { *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(); + let inbound_msg = ReadableArgs::read(&mut Cursor::new(&encoded_value[..]), (None, &&node_signer)).unwrap(); match inbound_msg { msgs::InboundOnionPayload::Receive { custom_tlvs, .. } => assert!(custom_tlvs.is_empty()), _ => panic!(), @@ -4133,25 +4345,25 @@ mod tests { payment_metadata: None, keysend_preimage: None, custom_tlvs: expected_custom_tlvs.clone(), - amt_msat: 0x0badf00d01020304, - outgoing_cltv_value: 0xffffffff, + sender_intended_htlc_amt_msat: 0x0badf00d01020304, + cltv_expiry_height: 0xffffffff, }; let encoded_value = msg.encode(); let target_value = >::from_hex("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(); + let inbound_msg: msgs::InboundOnionPayload = ReadableArgs::read(&mut Cursor::new(&target_value[..]), (None, &&node_signer)).unwrap(); if let msgs::InboundOnionPayload::Receive { payment_data: None, payment_metadata: None, keysend_preimage: None, custom_tlvs, - amt_msat, - outgoing_cltv_value, + sender_intended_htlc_amt_msat, + cltv_expiry_height: outgoing_cltv_value, .. } = inbound_msg { assert_eq!(custom_tlvs, expected_custom_tlvs); - assert_eq!(amt_msat, 0x0badf00d01020304); + assert_eq!(sender_intended_htlc_amt_msat, 0x0badf00d01020304); assert_eq!(outgoing_cltv_value, 0xffffffff); } else { panic!(); } } @@ -4305,8 +4517,8 @@ mod tests { let mut rd = Cursor::new(&big_payload[..]); let node_signer = test_utils::TestKeysInterface::new(&[42; 32], Network::Testnet); - > - ::read(&mut rd, &&node_signer).unwrap(); + , &&test_utils::TestKeysInterface)>> + ::read(&mut rd, (None, &&node_signer)).unwrap(); } // see above test, needs to be a separate method for use of the serialization macros. fn encode_big_payload() -> Result, io::Error> { diff --git a/lightning/src/ln/offers_tests.rs b/lightning/src/ln/offers_tests.rs new file mode 100644 index 00000000000..f5e8bc2afdb --- /dev/null +++ b/lightning/src/ln/offers_tests.rs @@ -0,0 +1,1044 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +//! Functional tests for the BOLT 12 Offers payment flow. +//! +//! [`ChannelManager`] provides utilities to create [`Offer`]s and [`Refund`]s along with utilities +//! to initiate and request payment for them, respectively. It also manages the payment flow via +//! implementing [`OffersMessageHandler`]. This module tests that functionality, including the +//! resulting [`Event`] generation. +//! +//! Two-node success tests use an announced channel: +//! +//! Alice --- Bob +//! +//! While two-node failure tests use an unannounced channel: +//! +//! Alice ... Bob +//! +//! Six-node tests use unannounced channels for the sender and recipient and announced channels for +//! the rest of the network. +//! +//! nodes[4] +//! / \ +//! / \ +//! / \ +//! Alice ... Bob -------- Charlie ... David +//! \ / +//! \ / +//! \ / +//! nodes[5] +//! +//! Unnamed nodes are needed to ensure unannounced nodes can create two-hop blinded paths. +//! +//! Nodes without channels are disconnected and connected as needed to ensure that deterministic +//! blinded paths are used. + +use core::time::Duration; +use crate::blinded_path::BlindedPath; +use crate::events::{Event, MessageSendEventsProvider, PaymentPurpose}; +use crate::ln::channelmanager::{PaymentId, RecentPaymentDetails, Retry, self}; +use crate::ln::functional_test_utils::*; +use crate::ln::msgs::{ChannelMessageHandler, Init, NodeAnnouncement, OnionMessage, OnionMessageHandler, RoutingMessageHandler, SocketAddress, UnsignedGossipMessage, UnsignedNodeAnnouncement}; +use crate::offers::invoice::Bolt12Invoice; +use crate::offers::invoice_error::InvoiceError; +use crate::offers::invoice_request::InvoiceRequest; +use crate::offers::parse::Bolt12SemanticError; +use crate::onion_message::messenger::PeeledOnion; +use crate::onion_message::offers::OffersMessage; +use crate::onion_message::packet::ParsedOnionMessageContents; +use crate::routing::gossip::{NodeAlias, NodeId}; +use crate::sign::{NodeSigner, Recipient}; + +use crate::prelude::*; + +macro_rules! expect_recent_payment { + ($node: expr, $payment_state: path, $payment_id: expr) => { + match $node.node.list_recent_payments().first() { + Some(&$payment_state { payment_id: actual_payment_id, .. }) => { + assert_eq!($payment_id, actual_payment_id); + }, + Some(_) => panic!("Unexpected recent payment state"), + None => panic!("No recent payments"), + } + } +} + +fn connect_peers<'a, 'b, 'c>(node_a: &Node<'a, 'b, 'c>, node_b: &Node<'a, 'b, 'c>) { + let node_id_a = node_a.node.get_our_node_id(); + let node_id_b = node_b.node.get_our_node_id(); + + let init_a = Init { + features: node_a.init_features(&node_id_b), + networks: None, + remote_network_address: None, + }; + let init_b = Init { + features: node_b.init_features(&node_id_a), + networks: None, + remote_network_address: None, + }; + + node_a.node.peer_connected(&node_id_b, &init_b, true).unwrap(); + node_b.node.peer_connected(&node_id_a, &init_a, false).unwrap(); + node_a.onion_messenger.peer_connected(&node_id_b, &init_b, true).unwrap(); + node_b.onion_messenger.peer_connected(&node_id_a, &init_a, false).unwrap(); +} + +fn disconnect_peers<'a, 'b, 'c>(node_a: &Node<'a, 'b, 'c>, peers: &[&Node<'a, 'b, 'c>]) { + for node_b in peers { + node_a.node.peer_disconnected(&node_b.node.get_our_node_id()); + node_b.node.peer_disconnected(&node_a.node.get_our_node_id()); + node_a.onion_messenger.peer_disconnected(&node_b.node.get_our_node_id()); + node_b.onion_messenger.peer_disconnected(&node_a.node.get_our_node_id()); + } +} + +fn announce_node_address<'a, 'b, 'c>( + node: &Node<'a, 'b, 'c>, peers: &[&Node<'a, 'b, 'c>], address: SocketAddress, +) { + let features = node.onion_messenger.provided_node_features() + | node.gossip_sync.provided_node_features(); + let rgb = [0u8; 3]; + let announcement = UnsignedNodeAnnouncement { + features, + timestamp: 1000, + node_id: NodeId::from_pubkey(&node.keys_manager.get_node_id(Recipient::Node).unwrap()), + rgb, + alias: NodeAlias([0u8; 32]), + addresses: vec![address], + excess_address_data: Vec::new(), + excess_data: Vec::new(), + }; + let signature = node.keys_manager.sign_gossip_message( + UnsignedGossipMessage::NodeAnnouncement(&announcement) + ).unwrap(); + + let msg = NodeAnnouncement { + signature, + contents: announcement + }; + + node.gossip_sync.handle_node_announcement(&msg).unwrap(); + for peer in peers { + peer.gossip_sync.handle_node_announcement(&msg).unwrap(); + } +} + +fn route_bolt12_payment<'a, 'b, 'c>( + node: &Node<'a, 'b, 'c>, path: &[&Node<'a, 'b, 'c>], invoice: &Bolt12Invoice +) { + // Monitor added when handling the invoice onion message. + check_added_monitors(node, 1); + + let mut events = node.node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + let ev = remove_first_msg_event_to_node(&path[0].node.get_our_node_id(), &mut events); + + // Use a fake payment_hash and bypass checking for the PaymentClaimable event since the + // invoice contains the payment_hash but it was encrypted inside an onion message. + let amount_msats = invoice.amount_msats(); + let payment_hash = invoice.payment_hash(); + let args = PassAlongPathArgs::new(node, path, amount_msats, payment_hash, ev) + .without_clearing_recipient_events(); + do_pass_along_path(args); +} + +fn claim_bolt12_payment<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, path: &[&Node<'a, 'b, 'c>]) { + let recipient = &path[path.len() - 1]; + match get_event!(recipient, Event::PaymentClaimable) { + Event::PaymentClaimable { + purpose: PaymentPurpose::InvoicePayment { + payment_preimage: Some(payment_preimage), .. + }, .. + } => claim_payment(node, path, payment_preimage), + _ => panic!(), + }; +} + +fn extract_invoice_request<'a, 'b, 'c>( + node: &Node<'a, 'b, 'c>, message: &OnionMessage +) -> (InvoiceRequest, Option) { + match node.onion_messenger.peel_onion_message(message) { + Ok(PeeledOnion::Receive(message, _, reply_path)) => match message { + ParsedOnionMessageContents::Offers(offers_message) => match offers_message { + OffersMessage::InvoiceRequest(invoice_request) => (invoice_request, reply_path), + OffersMessage::Invoice(invoice) => panic!("Unexpected invoice: {:?}", invoice), + OffersMessage::InvoiceError(error) => panic!("Unexpected invoice_error: {:?}", error), + }, + ParsedOnionMessageContents::Custom(message) => panic!("Unexpected custom message: {:?}", message), + }, + Ok(PeeledOnion::Forward(_, _)) => panic!("Unexpected onion message forward"), + Err(e) => panic!("Failed to process onion message {:?}", e), + } +} + +fn extract_invoice<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, message: &OnionMessage) -> Bolt12Invoice { + match node.onion_messenger.peel_onion_message(message) { + Ok(PeeledOnion::Receive(message, _, _)) => match message { + ParsedOnionMessageContents::Offers(offers_message) => match offers_message { + OffersMessage::InvoiceRequest(invoice_request) => panic!("Unexpected invoice_request: {:?}", invoice_request), + OffersMessage::Invoice(invoice) => invoice, + OffersMessage::InvoiceError(error) => panic!("Unexpected invoice_error: {:?}", error), + }, + ParsedOnionMessageContents::Custom(message) => panic!("Unexpected custom message: {:?}", message), + }, + Ok(PeeledOnion::Forward(_, _)) => panic!("Unexpected onion message forward"), + Err(e) => panic!("Failed to process onion message {:?}", e), + } +} + +fn extract_invoice_error<'a, 'b, 'c>( + node: &Node<'a, 'b, 'c>, message: &OnionMessage +) -> InvoiceError { + match node.onion_messenger.peel_onion_message(message) { + Ok(PeeledOnion::Receive(message, _, _)) => match message { + ParsedOnionMessageContents::Offers(offers_message) => match offers_message { + OffersMessage::InvoiceRequest(invoice_request) => panic!("Unexpected invoice_request: {:?}", invoice_request), + OffersMessage::Invoice(invoice) => panic!("Unexpected invoice: {:?}", invoice), + OffersMessage::InvoiceError(error) => error, + }, + ParsedOnionMessageContents::Custom(message) => panic!("Unexpected custom message: {:?}", message), + }, + Ok(PeeledOnion::Forward(_, _)) => panic!("Unexpected onion message forward"), + Err(e) => panic!("Failed to process onion message {:?}", e), + } +} + +/// Checks that blinded paths without Tor-only nodes are preferred when constructing an offer. +#[test] +fn prefers_non_tor_nodes_in_blinded_paths() { + let mut accept_forward_cfg = test_default_channel_config(); + accept_forward_cfg.accept_forwards_to_priv_channels = true; + + let mut features = channelmanager::provided_init_features(&accept_forward_cfg); + features.set_onion_messages_optional(); + features.set_route_blinding_optional(); + + let chanmon_cfgs = create_chanmon_cfgs(6); + let node_cfgs = create_node_cfgs(6, &chanmon_cfgs); + + *node_cfgs[1].override_init_features.borrow_mut() = Some(features); + + let node_chanmgrs = create_node_chanmgrs( + 6, &node_cfgs, &[None, Some(accept_forward_cfg), None, None, None, None] + ); + let nodes = create_network(6, &node_cfgs, &node_chanmgrs); + + create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000); + create_unannounced_chan_between_nodes_with_value(&nodes, 2, 3, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 1, 4, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 1, 5, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 2, 4, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 2, 5, 10_000_000, 1_000_000_000); + + // Add an extra channel so that more than one of Bob's peers have MIN_PEER_CHANNELS. + create_announced_chan_between_nodes_with_value(&nodes, 4, 5, 10_000_000, 1_000_000_000); + + let (alice, bob, charlie, david) = (&nodes[0], &nodes[1], &nodes[2], &nodes[3]); + let bob_id = bob.node.get_our_node_id(); + let charlie_id = charlie.node.get_our_node_id(); + + disconnect_peers(alice, &[charlie, david, &nodes[4], &nodes[5]]); + disconnect_peers(david, &[bob, &nodes[4], &nodes[5]]); + + let tor = SocketAddress::OnionV2([255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 38, 7]); + announce_node_address(charlie, &[alice, bob, david, &nodes[4], &nodes[5]], tor.clone()); + + let offer = bob.node + .create_offer_builder("coffee".to_string()).unwrap() + .amount_msats(10_000_000) + .build().unwrap(); + assert_ne!(offer.signing_pubkey(), bob_id); + assert!(!offer.paths().is_empty()); + for path in offer.paths() { + assert_ne!(path.introduction_node_id, bob_id); + assert_ne!(path.introduction_node_id, charlie_id); + } + + // Use a one-hop blinded path when Bob is announced and all his peers are Tor-only. + announce_node_address(&nodes[4], &[alice, bob, charlie, david, &nodes[5]], tor.clone()); + announce_node_address(&nodes[5], &[alice, bob, charlie, david, &nodes[4]], tor.clone()); + + let offer = bob.node + .create_offer_builder("coffee".to_string()).unwrap() + .amount_msats(10_000_000) + .build().unwrap(); + assert_ne!(offer.signing_pubkey(), bob_id); + assert!(!offer.paths().is_empty()); + for path in offer.paths() { + assert_eq!(path.introduction_node_id, bob_id); + } +} + +/// Checks that blinded paths prefer an introduction node that is the most connected. +#[test] +fn prefers_more_connected_nodes_in_blinded_paths() { + let mut accept_forward_cfg = test_default_channel_config(); + accept_forward_cfg.accept_forwards_to_priv_channels = true; + + let mut features = channelmanager::provided_init_features(&accept_forward_cfg); + features.set_onion_messages_optional(); + features.set_route_blinding_optional(); + + let chanmon_cfgs = create_chanmon_cfgs(6); + let node_cfgs = create_node_cfgs(6, &chanmon_cfgs); + + *node_cfgs[1].override_init_features.borrow_mut() = Some(features); + + let node_chanmgrs = create_node_chanmgrs( + 6, &node_cfgs, &[None, Some(accept_forward_cfg), None, None, None, None] + ); + let nodes = create_network(6, &node_cfgs, &node_chanmgrs); + + create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000); + create_unannounced_chan_between_nodes_with_value(&nodes, 2, 3, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 1, 4, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 1, 5, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 2, 4, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 2, 5, 10_000_000, 1_000_000_000); + + // Add extra channels so that more than one of Bob's peers have MIN_PEER_CHANNELS and one has + // more than the others. + create_announced_chan_between_nodes_with_value(&nodes, 0, 4, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 3, 4, 10_000_000, 1_000_000_000); + + let (alice, bob, charlie, david) = (&nodes[0], &nodes[1], &nodes[2], &nodes[3]); + let bob_id = bob.node.get_our_node_id(); + + disconnect_peers(alice, &[charlie, david, &nodes[4], &nodes[5]]); + disconnect_peers(david, &[bob, &nodes[4], &nodes[5]]); + + let offer = bob.node + .create_offer_builder("coffee".to_string()).unwrap() + .amount_msats(10_000_000) + .build().unwrap(); + assert_ne!(offer.signing_pubkey(), bob_id); + assert!(!offer.paths().is_empty()); + for path in offer.paths() { + assert_eq!(path.introduction_node_id, nodes[4].node.get_our_node_id()); + } +} + +/// Checks that an offer can be paid through blinded paths and that ephemeral pubkeys are used +/// rather than exposing a node's pubkey. +#[test] +fn creates_and_pays_for_offer_using_two_hop_blinded_path() { + let mut accept_forward_cfg = test_default_channel_config(); + accept_forward_cfg.accept_forwards_to_priv_channels = true; + + let mut features = channelmanager::provided_init_features(&accept_forward_cfg); + features.set_onion_messages_optional(); + features.set_route_blinding_optional(); + + let chanmon_cfgs = create_chanmon_cfgs(6); + let node_cfgs = create_node_cfgs(6, &chanmon_cfgs); + + *node_cfgs[1].override_init_features.borrow_mut() = Some(features); + + let node_chanmgrs = create_node_chanmgrs( + 6, &node_cfgs, &[None, Some(accept_forward_cfg), None, None, None, None] + ); + let nodes = create_network(6, &node_cfgs, &node_chanmgrs); + + create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000); + create_unannounced_chan_between_nodes_with_value(&nodes, 2, 3, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 1, 4, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 1, 5, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 2, 4, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 2, 5, 10_000_000, 1_000_000_000); + + let (alice, bob, charlie, david) = (&nodes[0], &nodes[1], &nodes[2], &nodes[3]); + let alice_id = alice.node.get_our_node_id(); + let bob_id = bob.node.get_our_node_id(); + let charlie_id = charlie.node.get_our_node_id(); + let david_id = david.node.get_our_node_id(); + + disconnect_peers(alice, &[charlie, david, &nodes[4], &nodes[5]]); + disconnect_peers(david, &[bob, &nodes[4], &nodes[5]]); + + let offer = alice.node + .create_offer_builder("coffee".to_string()).unwrap() + .amount_msats(10_000_000) + .build().unwrap(); + assert_ne!(offer.signing_pubkey(), alice_id); + assert!(!offer.paths().is_empty()); + for path in offer.paths() { + assert_eq!(path.introduction_node_id, bob_id); + } + + let payment_id = PaymentId([1; 32]); + david.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None) + .unwrap(); + expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id); + + connect_peers(david, bob); + + let onion_message = david.onion_messenger.next_onion_message_for_peer(bob_id).unwrap(); + bob.onion_messenger.handle_onion_message(&david_id, &onion_message); + + connect_peers(alice, charlie); + + let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap(); + alice.onion_messenger.handle_onion_message(&bob_id, &onion_message); + + let (invoice_request, reply_path) = extract_invoice_request(alice, &onion_message); + assert_eq!(invoice_request.amount_msats(), None); + assert_ne!(invoice_request.payer_id(), david_id); + assert_eq!(reply_path.unwrap().introduction_node_id, charlie_id); + + let onion_message = alice.onion_messenger.next_onion_message_for_peer(charlie_id).unwrap(); + charlie.onion_messenger.handle_onion_message(&alice_id, &onion_message); + + let onion_message = charlie.onion_messenger.next_onion_message_for_peer(david_id).unwrap(); + david.onion_messenger.handle_onion_message(&charlie_id, &onion_message); + + let invoice = extract_invoice(david, &onion_message); + assert_eq!(invoice.amount_msats(), 10_000_000); + assert_ne!(invoice.signing_pubkey(), alice_id); + assert!(!invoice.payment_paths().is_empty()); + for (_, path) in invoice.payment_paths() { + assert_eq!(path.introduction_node_id, bob_id); + } + + route_bolt12_payment(david, &[charlie, bob, alice], &invoice); + expect_recent_payment!(david, RecentPaymentDetails::Pending, payment_id); + + claim_bolt12_payment(david, &[charlie, bob, alice]); + expect_recent_payment!(david, RecentPaymentDetails::Fulfilled, payment_id); +} + +/// Checks that a refund can be paid through blinded paths and that ephemeral pubkeys are used +/// rather than exposing a node's pubkey. +#[test] +fn creates_and_pays_for_refund_using_two_hop_blinded_path() { + let mut accept_forward_cfg = test_default_channel_config(); + accept_forward_cfg.accept_forwards_to_priv_channels = true; + + let mut features = channelmanager::provided_init_features(&accept_forward_cfg); + features.set_onion_messages_optional(); + features.set_route_blinding_optional(); + + let chanmon_cfgs = create_chanmon_cfgs(6); + let node_cfgs = create_node_cfgs(6, &chanmon_cfgs); + + *node_cfgs[1].override_init_features.borrow_mut() = Some(features); + + let node_chanmgrs = create_node_chanmgrs( + 6, &node_cfgs, &[None, Some(accept_forward_cfg), None, None, None, None] + ); + let nodes = create_network(6, &node_cfgs, &node_chanmgrs); + + create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000); + create_unannounced_chan_between_nodes_with_value(&nodes, 2, 3, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 1, 4, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 1, 5, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 2, 4, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 2, 5, 10_000_000, 1_000_000_000); + + let (alice, bob, charlie, david) = (&nodes[0], &nodes[1], &nodes[2], &nodes[3]); + let alice_id = alice.node.get_our_node_id(); + let bob_id = bob.node.get_our_node_id(); + let charlie_id = charlie.node.get_our_node_id(); + let david_id = david.node.get_our_node_id(); + + disconnect_peers(alice, &[charlie, david, &nodes[4], &nodes[5]]); + disconnect_peers(david, &[bob, &nodes[4], &nodes[5]]); + + let absolute_expiry = Duration::from_secs(u64::MAX); + let payment_id = PaymentId([1; 32]); + let refund = david.node + .create_refund_builder( + "refund".to_string(), 10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), None + ) + .unwrap() + .build().unwrap(); + assert_eq!(refund.amount_msats(), 10_000_000); + assert_eq!(refund.absolute_expiry(), Some(absolute_expiry)); + assert_ne!(refund.payer_id(), david_id); + assert!(!refund.paths().is_empty()); + for path in refund.paths() { + assert_eq!(path.introduction_node_id, charlie_id); + } + expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id); + + alice.node.request_refund_payment(&refund).unwrap(); + + connect_peers(alice, charlie); + + let onion_message = alice.onion_messenger.next_onion_message_for_peer(charlie_id).unwrap(); + charlie.onion_messenger.handle_onion_message(&alice_id, &onion_message); + + let onion_message = charlie.onion_messenger.next_onion_message_for_peer(david_id).unwrap(); + david.onion_messenger.handle_onion_message(&charlie_id, &onion_message); + + let invoice = extract_invoice(david, &onion_message); + assert_eq!(invoice.amount_msats(), 10_000_000); + assert_ne!(invoice.signing_pubkey(), alice_id); + assert!(!invoice.payment_paths().is_empty()); + for (_, path) in invoice.payment_paths() { + assert_eq!(path.introduction_node_id, bob_id); + } + + route_bolt12_payment(david, &[charlie, bob, alice], &invoice); + expect_recent_payment!(david, RecentPaymentDetails::Pending, payment_id); + + claim_bolt12_payment(david, &[charlie, bob, alice]); + expect_recent_payment!(david, RecentPaymentDetails::Fulfilled, payment_id); +} + +/// Checks that an offer can be paid through a one-hop blinded path and that ephemeral pubkeys are +/// used rather than exposing a node's pubkey. However, the node's pubkey is still used as the +/// introduction node of the blinded path. +#[test] +fn creates_and_pays_for_offer_using_one_hop_blinded_path() { + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000); + + let alice = &nodes[0]; + let alice_id = alice.node.get_our_node_id(); + let bob = &nodes[1]; + let bob_id = bob.node.get_our_node_id(); + + let offer = alice.node + .create_offer_builder("coffee".to_string()).unwrap() + .amount_msats(10_000_000) + .build().unwrap(); + assert_ne!(offer.signing_pubkey(), alice_id); + assert!(!offer.paths().is_empty()); + for path in offer.paths() { + assert_eq!(path.introduction_node_id, alice_id); + } + + let payment_id = PaymentId([1; 32]); + bob.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None).unwrap(); + expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id); + + let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap(); + alice.onion_messenger.handle_onion_message(&bob_id, &onion_message); + + let (invoice_request, reply_path) = extract_invoice_request(alice, &onion_message); + assert_eq!(invoice_request.amount_msats(), None); + assert_ne!(invoice_request.payer_id(), bob_id); + assert_eq!(reply_path.unwrap().introduction_node_id, bob_id); + + let onion_message = alice.onion_messenger.next_onion_message_for_peer(bob_id).unwrap(); + bob.onion_messenger.handle_onion_message(&alice_id, &onion_message); + + let invoice = extract_invoice(bob, &onion_message); + assert_eq!(invoice.amount_msats(), 10_000_000); + assert_ne!(invoice.signing_pubkey(), alice_id); + assert!(!invoice.payment_paths().is_empty()); + for (_, path) in invoice.payment_paths() { + assert_eq!(path.introduction_node_id, alice_id); + } + + route_bolt12_payment(bob, &[alice], &invoice); + expect_recent_payment!(bob, RecentPaymentDetails::Pending, payment_id); + + claim_bolt12_payment(bob, &[alice]); + expect_recent_payment!(bob, RecentPaymentDetails::Fulfilled, payment_id); +} + +/// Checks that a refund can be paid through a one-hop blinded path and that ephemeral pubkeys are +/// used rather than exposing a node's pubkey. However, the node's pubkey is still used as the +/// introduction node of the blinded path. +#[test] +fn creates_and_pays_for_refund_using_one_hop_blinded_path() { + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000); + + let alice = &nodes[0]; + let alice_id = alice.node.get_our_node_id(); + let bob = &nodes[1]; + let bob_id = bob.node.get_our_node_id(); + + let absolute_expiry = Duration::from_secs(u64::MAX); + let payment_id = PaymentId([1; 32]); + let refund = bob.node + .create_refund_builder( + "refund".to_string(), 10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), None + ) + .unwrap() + .build().unwrap(); + assert_eq!(refund.amount_msats(), 10_000_000); + assert_eq!(refund.absolute_expiry(), Some(absolute_expiry)); + assert_ne!(refund.payer_id(), bob_id); + assert!(!refund.paths().is_empty()); + for path in refund.paths() { + assert_eq!(path.introduction_node_id, bob_id); + } + expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id); + + alice.node.request_refund_payment(&refund).unwrap(); + + let onion_message = alice.onion_messenger.next_onion_message_for_peer(bob_id).unwrap(); + bob.onion_messenger.handle_onion_message(&alice_id, &onion_message); + + let invoice = extract_invoice(bob, &onion_message); + assert_eq!(invoice.amount_msats(), 10_000_000); + assert_ne!(invoice.signing_pubkey(), alice_id); + assert!(!invoice.payment_paths().is_empty()); + for (_, path) in invoice.payment_paths() { + assert_eq!(path.introduction_node_id, alice_id); + } + + route_bolt12_payment(bob, &[alice], &invoice); + expect_recent_payment!(bob, RecentPaymentDetails::Pending, payment_id); + + claim_bolt12_payment(bob, &[alice]); + expect_recent_payment!(bob, RecentPaymentDetails::Fulfilled, payment_id); +} + +/// Checks that an invoice for an offer without any blinded paths can be requested. Note that while +/// the requested is sent directly using the node's pubkey, the response and the payment still use +/// blinded paths as required by the spec. +#[test] +fn pays_for_offer_without_blinded_paths() { + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000); + + let alice = &nodes[0]; + let alice_id = alice.node.get_our_node_id(); + let bob = &nodes[1]; + let bob_id = bob.node.get_our_node_id(); + + let offer = alice.node + .create_offer_builder("coffee".to_string()).unwrap() + .clear_paths() + .amount_msats(10_000_000) + .build().unwrap(); + assert_eq!(offer.signing_pubkey(), alice_id); + assert!(offer.paths().is_empty()); + + let payment_id = PaymentId([1; 32]); + bob.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None).unwrap(); + expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id); + + let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap(); + alice.onion_messenger.handle_onion_message(&bob_id, &onion_message); + + let onion_message = alice.onion_messenger.next_onion_message_for_peer(bob_id).unwrap(); + bob.onion_messenger.handle_onion_message(&alice_id, &onion_message); + + let invoice = extract_invoice(bob, &onion_message); + route_bolt12_payment(bob, &[alice], &invoice); + expect_recent_payment!(bob, RecentPaymentDetails::Pending, payment_id); + + claim_bolt12_payment(bob, &[alice]); + expect_recent_payment!(bob, RecentPaymentDetails::Fulfilled, payment_id); +} + +/// Checks that a refund without any blinded paths can be paid. Note that while the invoice is sent +/// directly using the node's pubkey, the payment still use blinded paths as required by the spec. +#[test] +fn pays_for_refund_without_blinded_paths() { + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000); + + let alice = &nodes[0]; + let alice_id = alice.node.get_our_node_id(); + let bob = &nodes[1]; + let bob_id = bob.node.get_our_node_id(); + + let absolute_expiry = Duration::from_secs(u64::MAX); + let payment_id = PaymentId([1; 32]); + let refund = bob.node + .create_refund_builder( + "refund".to_string(), 10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), None + ) + .unwrap() + .clear_paths() + .build().unwrap(); + assert_eq!(refund.payer_id(), bob_id); + assert!(refund.paths().is_empty()); + expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id); + + alice.node.request_refund_payment(&refund).unwrap(); + + let onion_message = alice.onion_messenger.next_onion_message_for_peer(bob_id).unwrap(); + bob.onion_messenger.handle_onion_message(&alice_id, &onion_message); + + let invoice = extract_invoice(bob, &onion_message); + route_bolt12_payment(bob, &[alice], &invoice); + expect_recent_payment!(bob, RecentPaymentDetails::Pending, payment_id); + + claim_bolt12_payment(bob, &[alice]); + expect_recent_payment!(bob, RecentPaymentDetails::Fulfilled, payment_id); +} + +/// Fails creating an offer when a blinded path cannot be created without exposing the node's id. +#[test] +fn fails_creating_offer_without_blinded_paths() { + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000); + + match nodes[0].node.create_offer_builder("coffee".to_string()) { + Ok(_) => panic!("Expected error"), + Err(e) => assert_eq!(e, Bolt12SemanticError::MissingPaths), + } +} + +/// Fails creating a refund when a blinded path cannot be created without exposing the node's id. +#[test] +fn fails_creating_refund_without_blinded_paths() { + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000); + + let absolute_expiry = Duration::from_secs(u64::MAX); + let payment_id = PaymentId([1; 32]); + + match nodes[0].node.create_refund_builder( + "refund".to_string(), 10_000, absolute_expiry, payment_id, Retry::Attempts(0), None + ) { + Ok(_) => panic!("Expected error"), + Err(e) => assert_eq!(e, Bolt12SemanticError::MissingPaths), + } + + assert!(nodes[0].node.list_recent_payments().is_empty()); +} + +/// Fails creating an invoice request when a blinded reply path cannot be created without exposing +/// the node's id. +#[test] +fn fails_creating_invoice_request_without_blinded_reply_path() { + let chanmon_cfgs = create_chanmon_cfgs(6); + let node_cfgs = create_node_cfgs(6, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(6, &node_cfgs, &[None, None, None, None, None, None]); + let nodes = create_network(6, &node_cfgs, &node_chanmgrs); + + create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000); + create_unannounced_chan_between_nodes_with_value(&nodes, 2, 3, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 1, 4, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 1, 5, 10_000_000, 1_000_000_000); + + let (alice, bob, charlie, david) = (&nodes[0], &nodes[1], &nodes[2], &nodes[3]); + + disconnect_peers(alice, &[charlie, david, &nodes[4], &nodes[5]]); + disconnect_peers(david, &[bob, &nodes[4], &nodes[5]]); + + let offer = alice.node + .create_offer_builder("coffee".to_string()).unwrap() + .amount_msats(10_000_000) + .build().unwrap(); + + let payment_id = PaymentId([1; 32]); + + match david.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None) { + Ok(_) => panic!("Expected error"), + Err(e) => assert_eq!(e, Bolt12SemanticError::MissingPaths), + } + + assert!(nodes[0].node.list_recent_payments().is_empty()); +} + +#[test] +fn fails_creating_invoice_request_with_duplicate_payment_id() { + let chanmon_cfgs = create_chanmon_cfgs(6); + let node_cfgs = create_node_cfgs(6, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(6, &node_cfgs, &[None, None, None, None, None, None]); + let nodes = create_network(6, &node_cfgs, &node_chanmgrs); + + create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000); + create_unannounced_chan_between_nodes_with_value(&nodes, 2, 3, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 1, 4, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 1, 5, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 2, 4, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 2, 5, 10_000_000, 1_000_000_000); + + let (alice, _bob, charlie, david) = (&nodes[0], &nodes[1], &nodes[2], &nodes[3]); + + disconnect_peers(alice, &[charlie, david, &nodes[4], &nodes[5]]); + + let offer = alice.node + .create_offer_builder("coffee".to_string()).unwrap() + .amount_msats(10_000_000) + .build().unwrap(); + + let payment_id = PaymentId([1; 32]); + assert!( + david.node.pay_for_offer( + &offer, None, None, None, payment_id, Retry::Attempts(0), None + ).is_ok() + ); + expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id); + + match david.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None) { + Ok(_) => panic!("Expected error"), + Err(e) => assert_eq!(e, Bolt12SemanticError::DuplicatePaymentId), + } + + expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id); +} + +#[test] +fn fails_creating_refund_with_duplicate_payment_id() { + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000); + + let absolute_expiry = Duration::from_secs(u64::MAX); + let payment_id = PaymentId([1; 32]); + assert!( + nodes[0].node.create_refund_builder( + "refund".to_string(), 10_000, absolute_expiry, payment_id, Retry::Attempts(0), None + ).is_ok() + ); + expect_recent_payment!(nodes[0], RecentPaymentDetails::AwaitingInvoice, payment_id); + + match nodes[0].node.create_refund_builder( + "refund".to_string(), 10_000, absolute_expiry, payment_id, Retry::Attempts(0), None + ) { + Ok(_) => panic!("Expected error"), + Err(e) => assert_eq!(e, Bolt12SemanticError::DuplicatePaymentId), + } + + expect_recent_payment!(nodes[0], RecentPaymentDetails::AwaitingInvoice, payment_id); +} + +#[test] +fn fails_sending_invoice_without_blinded_payment_paths_for_offer() { + let mut accept_forward_cfg = test_default_channel_config(); + accept_forward_cfg.accept_forwards_to_priv_channels = true; + + // Clearing route_blinding prevents forming any payment paths since the node is unannounced. + let mut features = channelmanager::provided_init_features(&accept_forward_cfg); + features.set_onion_messages_optional(); + features.clear_route_blinding(); + + let chanmon_cfgs = create_chanmon_cfgs(6); + let node_cfgs = create_node_cfgs(6, &chanmon_cfgs); + + *node_cfgs[1].override_init_features.borrow_mut() = Some(features); + + let node_chanmgrs = create_node_chanmgrs( + 6, &node_cfgs, &[None, Some(accept_forward_cfg), None, None, None, None] + ); + let nodes = create_network(6, &node_cfgs, &node_chanmgrs); + + create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000); + create_unannounced_chan_between_nodes_with_value(&nodes, 2, 3, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 1, 4, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 1, 5, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 2, 4, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 2, 5, 10_000_000, 1_000_000_000); + + let (alice, bob, charlie, david) = (&nodes[0], &nodes[1], &nodes[2], &nodes[3]); + let alice_id = alice.node.get_our_node_id(); + let bob_id = bob.node.get_our_node_id(); + let charlie_id = charlie.node.get_our_node_id(); + let david_id = david.node.get_our_node_id(); + + disconnect_peers(alice, &[charlie, david, &nodes[4], &nodes[5]]); + disconnect_peers(david, &[bob, &nodes[4], &nodes[5]]); + + let offer = alice.node + .create_offer_builder("coffee".to_string()).unwrap() + .amount_msats(10_000_000) + .build().unwrap(); + + let payment_id = PaymentId([1; 32]); + david.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None) + .unwrap(); + + connect_peers(david, bob); + + let onion_message = david.onion_messenger.next_onion_message_for_peer(bob_id).unwrap(); + bob.onion_messenger.handle_onion_message(&david_id, &onion_message); + + connect_peers(alice, charlie); + + let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap(); + alice.onion_messenger.handle_onion_message(&bob_id, &onion_message); + + let onion_message = alice.onion_messenger.next_onion_message_for_peer(charlie_id).unwrap(); + charlie.onion_messenger.handle_onion_message(&alice_id, &onion_message); + + let onion_message = charlie.onion_messenger.next_onion_message_for_peer(david_id).unwrap(); + david.onion_messenger.handle_onion_message(&charlie_id, &onion_message); + + let invoice_error = extract_invoice_error(david, &onion_message); + assert_eq!(invoice_error, InvoiceError::from(Bolt12SemanticError::MissingPaths)); +} + +#[test] +fn fails_sending_invoice_without_blinded_payment_paths_for_refund() { + let mut accept_forward_cfg = test_default_channel_config(); + accept_forward_cfg.accept_forwards_to_priv_channels = true; + + // Clearing route_blinding prevents forming any payment paths since the node is unannounced. + let mut features = channelmanager::provided_init_features(&accept_forward_cfg); + features.set_onion_messages_optional(); + features.clear_route_blinding(); + + let chanmon_cfgs = create_chanmon_cfgs(6); + let node_cfgs = create_node_cfgs(6, &chanmon_cfgs); + + *node_cfgs[1].override_init_features.borrow_mut() = Some(features); + + let node_chanmgrs = create_node_chanmgrs( + 6, &node_cfgs, &[None, Some(accept_forward_cfg), None, None, None, None] + ); + let nodes = create_network(6, &node_cfgs, &node_chanmgrs); + + create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000); + create_unannounced_chan_between_nodes_with_value(&nodes, 2, 3, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 1, 4, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 1, 5, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 2, 4, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 2, 5, 10_000_000, 1_000_000_000); + + let (alice, bob, charlie, david) = (&nodes[0], &nodes[1], &nodes[2], &nodes[3]); + + disconnect_peers(alice, &[charlie, david, &nodes[4], &nodes[5]]); + disconnect_peers(david, &[bob, &nodes[4], &nodes[5]]); + + let absolute_expiry = Duration::from_secs(u64::MAX); + let payment_id = PaymentId([1; 32]); + let refund = david.node + .create_refund_builder( + "refund".to_string(), 10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), None + ) + .unwrap() + .build().unwrap(); + + match alice.node.request_refund_payment(&refund) { + Ok(_) => panic!("Expected error"), + Err(e) => assert_eq!(e, Bolt12SemanticError::MissingPaths), + } +} + +#[test] +fn fails_paying_invoice_more_than_once() { + let mut accept_forward_cfg = test_default_channel_config(); + accept_forward_cfg.accept_forwards_to_priv_channels = true; + + let mut features = channelmanager::provided_init_features(&accept_forward_cfg); + features.set_onion_messages_optional(); + features.set_route_blinding_optional(); + + let chanmon_cfgs = create_chanmon_cfgs(6); + let node_cfgs = create_node_cfgs(6, &chanmon_cfgs); + + *node_cfgs[1].override_init_features.borrow_mut() = Some(features); + + let node_chanmgrs = create_node_chanmgrs( + 6, &node_cfgs, &[None, Some(accept_forward_cfg), None, None, None, None] + ); + let nodes = create_network(6, &node_cfgs, &node_chanmgrs); + + create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000); + create_unannounced_chan_between_nodes_with_value(&nodes, 2, 3, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 1, 4, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 1, 5, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 2, 4, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 2, 5, 10_000_000, 1_000_000_000); + + let (alice, bob, charlie, david) = (&nodes[0], &nodes[1], &nodes[2], &nodes[3]); + let alice_id = alice.node.get_our_node_id(); + let bob_id = bob.node.get_our_node_id(); + let charlie_id = charlie.node.get_our_node_id(); + let david_id = david.node.get_our_node_id(); + + disconnect_peers(alice, &[charlie, david, &nodes[4], &nodes[5]]); + disconnect_peers(david, &[bob, &nodes[4], &nodes[5]]); + + let absolute_expiry = Duration::from_secs(u64::MAX); + let payment_id = PaymentId([1; 32]); + let refund = david.node + .create_refund_builder( + "refund".to_string(), 10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), None + ) + .unwrap() + .build().unwrap(); + expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id); + + // Alice sends the first invoice + alice.node.request_refund_payment(&refund).unwrap(); + + connect_peers(alice, charlie); + + let onion_message = alice.onion_messenger.next_onion_message_for_peer(charlie_id).unwrap(); + charlie.onion_messenger.handle_onion_message(&alice_id, &onion_message); + + let onion_message = charlie.onion_messenger.next_onion_message_for_peer(david_id).unwrap(); + david.onion_messenger.handle_onion_message(&charlie_id, &onion_message); + + // David pays the first invoice + let invoice1 = extract_invoice(david, &onion_message); + + route_bolt12_payment(david, &[charlie, bob, alice], &invoice1); + expect_recent_payment!(david, RecentPaymentDetails::Pending, payment_id); + + claim_bolt12_payment(david, &[charlie, bob, alice]); + expect_recent_payment!(david, RecentPaymentDetails::Fulfilled, payment_id); + + disconnect_peers(alice, &[charlie]); + + // Alice sends the second invoice + alice.node.request_refund_payment(&refund).unwrap(); + + connect_peers(alice, charlie); + connect_peers(david, bob); + + let onion_message = alice.onion_messenger.next_onion_message_for_peer(charlie_id).unwrap(); + charlie.onion_messenger.handle_onion_message(&alice_id, &onion_message); + + let onion_message = charlie.onion_messenger.next_onion_message_for_peer(david_id).unwrap(); + david.onion_messenger.handle_onion_message(&charlie_id, &onion_message); + + let invoice2 = extract_invoice(david, &onion_message); + assert_eq!(invoice1.payer_metadata(), invoice2.payer_metadata()); + + // David sends an error instead of paying the second invoice + let onion_message = david.onion_messenger.next_onion_message_for_peer(bob_id).unwrap(); + bob.onion_messenger.handle_onion_message(&david_id, &onion_message); + + let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap(); + alice.onion_messenger.handle_onion_message(&bob_id, &onion_message); + + let invoice_error = extract_invoice_error(alice, &onion_message); + assert_eq!(invoice_error, InvoiceError::from_string("DuplicateInvoice".to_string())); +} diff --git a/lightning/src/ln/onion_payment.rs b/lightning/src/ln/onion_payment.rs index 7c0d32a52a5..00843d5e4e9 100644 --- a/lightning/src/ln/onion_payment.rs +++ b/lightning/src/ln/onion_payment.rs @@ -3,15 +3,16 @@ //! Primarily features [`peel_payment_onion`], which allows the decoding of an onion statelessly //! and can be used to predict whether we'd accept a payment. -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::{self, Secp256k1, PublicKey}; +use bitcoin::secp256k1::{self, PublicKey, Scalar, Secp256k1}; use crate::blinded_path; use crate::blinded_path::payment::{PaymentConstraints, PaymentRelay}; use crate::chain::channelmonitor::{HTLC_FAIL_BACK_BUFFER, LATENCY_GRACE_PERIOD_BLOCKS}; use crate::ln::PaymentHash; -use crate::ln::channelmanager::{BlindedForward, CLTV_FAR_FAR_AWAY, HTLCFailureMsg, MIN_CLTV_EXPIRY_DELTA, PendingHTLCInfo, PendingHTLCRouting}; +use crate::ln::channelmanager::{BlindedFailure, BlindedForward, CLTV_FAR_FAR_AWAY, HTLCFailureMsg, MIN_CLTV_EXPIRY_DELTA, PendingHTLCInfo, PendingHTLCRouting}; use crate::ln::features::BlindedHopFeatures; use crate::ln::msgs; use crate::ln::onion_utils; @@ -24,7 +25,7 @@ use core::ops::Deref; /// Invalid inbound onion payment. #[derive(Debug)] -pub struct InboundOnionErr { +pub struct InboundHTLCErr { /// BOLT 4 error code. pub err_code: u16, /// Data attached to this error. @@ -33,6 +34,15 @@ pub struct InboundOnionErr { pub msg: &'static str, } +fn check_blinded_payment_constraints( + amt_msat: u64, cltv_expiry: u32, constraints: &PaymentConstraints +) -> Result<(), ()> { + if amt_msat < constraints.htlc_minimum_msat || + cltv_expiry > constraints.max_cltv_expiry + { return Err(()) } + Ok(()) +} + fn check_blinded_forward( inbound_amt_msat: u64, inbound_cltv_expiry: u32, payment_relay: &PaymentRelay, payment_constraints: &PaymentConstraints, features: &BlindedHopFeatures @@ -43,9 +53,8 @@ fn check_blinded_forward( let outgoing_cltv_value = inbound_cltv_expiry.checked_sub( payment_relay.cltv_expiry_delta as u32 ).ok_or(())?; - if inbound_amt_msat < payment_constraints.htlc_minimum_msat || - outgoing_cltv_value > payment_constraints.max_cltv_expiry - { return Err(()) } + check_blinded_payment_constraints(inbound_amt_msat, outgoing_cltv_value, payment_constraints)?; + if features.requires_unknown_bits_from(&BlindedHopFeatures::empty()) { return Err(()) } Ok((amt_to_forward, outgoing_cltv_value)) } @@ -54,7 +63,7 @@ pub(super) fn create_fwd_pending_htlc_info( 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 { +) -> Result { debug_assert!(next_packet_pubkey_opt.is_some()); let outgoing_packet = msgs::OnionPacket { version: 0, @@ -64,7 +73,7 @@ pub(super) fn create_fwd_pending_htlc_info( }; let ( - short_channel_id, amt_to_forward, outgoing_cltv_value, inbound_blinding_point + short_channel_id, amt_to_forward, outgoing_cltv_value, intro_node_blinding_point ) = match hop_data { msgs::InboundOnionPayload::Forward { short_channel_id, amt_to_forward, outgoing_cltv_value } => (short_channel_id, amt_to_forward, outgoing_cltv_value, None), @@ -76,16 +85,16 @@ pub(super) fn create_fwd_pending_htlc_info( ).map_err(|()| { // We should be returning malformed here if `msg.blinding_point` is set, but this is // unreachable right now since we checked it in `decode_update_add_htlc_onion`. - InboundOnionErr { + InboundHTLCErr { msg: "Underflow calculating outbound amount or cltv value for blinded forward", err_code: INVALID_ONION_BLINDING, err_data: vec![0; 32], } })?; - (short_channel_id, amt_to_forward, outgoing_cltv_value, Some(intro_node_blinding_point)) + (short_channel_id, amt_to_forward, outgoing_cltv_value, intro_node_blinding_point) }, msgs::InboundOnionPayload::Receive { .. } | msgs::InboundOnionPayload::BlindedReceive { .. } => - return Err(InboundOnionErr { + return Err(InboundHTLCErr { msg: "Final Node OnionHopData provided for us as an intermediary node", err_code: 0x4000 | 22, err_data: Vec::new(), @@ -96,7 +105,13 @@ pub(super) fn create_fwd_pending_htlc_info( routing: PendingHTLCRouting::Forward { onion_packet: outgoing_packet, short_channel_id, - blinded: inbound_blinding_point.map(|bp| BlindedForward { inbound_blinding_point: bp }), + blinded: intro_node_blinding_point.or(msg.blinding_point) + .map(|bp| BlindedForward { + inbound_blinding_point: bp, + failure: intro_node_blinding_point + .map(|_| BlindedFailure::FromIntroductionNode) + .unwrap_or(BlindedFailure::FromBlindedNode), + }), }, payment_hash: msg.payment_hash, incoming_shared_secret: shared_secret, @@ -111,27 +126,44 @@ pub(super) fn create_recv_pending_htlc_info( 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, current_height: u32, accept_mpp_keysend: bool, -) -> Result { - let (payment_data, keysend_preimage, custom_tlvs, onion_amt_msat, outgoing_cltv_value, payment_metadata) = match hop_data { +) -> Result { + let ( + payment_data, keysend_preimage, custom_tlvs, onion_amt_msat, onion_cltv_expiry, + payment_metadata, requires_blinded_error + ) = 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, sender_intended_htlc_amt_msat, + cltv_expiry_height, payment_metadata, .. } => - (payment_data, keysend_preimage, custom_tlvs, amt_msat, outgoing_cltv_value, payment_metadata), + (payment_data, keysend_preimage, custom_tlvs, sender_intended_htlc_amt_msat, + cltv_expiry_height, payment_metadata, false), msgs::InboundOnionPayload::BlindedReceive { - amt_msat, total_msat, outgoing_cltv_value, payment_secret, .. + sender_intended_htlc_amt_msat, total_msat, cltv_expiry_height, payment_secret, + intro_node_blinding_point, payment_constraints, .. } => { + check_blinded_payment_constraints( + sender_intended_htlc_amt_msat, cltv_expiry, &payment_constraints + ) + .map_err(|()| { + InboundHTLCErr { + err_code: INVALID_ONION_BLINDING, + err_data: vec![0; 32], + msg: "Amount or cltv_expiry violated blinded payment constraints", + } + })?; let payment_data = msgs::FinalOnionHopData { payment_secret, total_msat }; - (Some(payment_data), None, Vec::new(), amt_msat, outgoing_cltv_value, None) + (Some(payment_data), None, Vec::new(), sender_intended_htlc_amt_msat, cltv_expiry_height, + None, intro_node_blinding_point.is_none()) } msgs::InboundOnionPayload::Forward { .. } => { - return Err(InboundOnionErr { + return Err(InboundHTLCErr { err_code: 0x4000|22, err_data: Vec::new(), msg: "Got non final data with an HMAC of 0", }) }, msgs::InboundOnionPayload::BlindedForward { .. } => { - return Err(InboundOnionErr { + return Err(InboundHTLCErr { err_code: INVALID_ONION_BLINDING, err_data: vec![0; 32], msg: "Got blinded non final data with an HMAC of 0", @@ -139,8 +171,8 @@ pub(super) fn create_recv_pending_htlc_info( } }; // final_incorrect_cltv_expiry - if outgoing_cltv_value > cltv_expiry { - return Err(InboundOnionErr { + if onion_cltv_expiry > cltv_expiry { + return Err(InboundHTLCErr { 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() @@ -157,7 +189,7 @@ pub(super) fn create_recv_pending_htlc_info( 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(InboundOnionErr { + return Err(InboundHTLCErr { err_code: 0x4000 | 15, err_data, msg: "The final CLTV expiry is too soon to handle", }); @@ -166,7 +198,7 @@ pub(super) fn create_recv_pending_htlc_info( (allow_underpay && onion_amt_msat > amt_msat.saturating_add(counterparty_skimmed_fee_msat.unwrap_or(0))) { - return Err(InboundOnionErr { + return Err(InboundHTLCErr { 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", @@ -181,14 +213,14 @@ pub(super) fn create_recv_pending_htlc_info( // time discrepancies due to a hash collision with X. let hashed_preimage = PaymentHash(Sha256::hash(&payment_preimage.0).to_byte_array()); if hashed_preimage != payment_hash { - return Err(InboundOnionErr { + return Err(InboundHTLCErr { err_code: 0x4000|22, err_data: Vec::new(), msg: "Payment preimage didn't match payment hash", }); } if !accept_mpp_keysend && payment_data.is_some() { - return Err(InboundOnionErr { + return Err(InboundHTLCErr { err_code: 0x4000|22, err_data: Vec::new(), msg: "We don't support MPP keysend payments", @@ -198,19 +230,20 @@ pub(super) fn create_recv_pending_htlc_info( payment_data, payment_preimage, payment_metadata, - incoming_cltv_expiry: outgoing_cltv_value, + incoming_cltv_expiry: onion_cltv_expiry, custom_tlvs, } } else if let Some(data) = payment_data { PendingHTLCRouting::Receive { payment_data: data, payment_metadata, - incoming_cltv_expiry: outgoing_cltv_value, + incoming_cltv_expiry: onion_cltv_expiry, phantom_shared_secret, custom_tlvs, + requires_blinded_error, } } else { - return Err(InboundOnionErr { + return Err(InboundHTLCErr { err_code: 0x4000|0x2000|3, err_data: Vec::new(), msg: "We require payment_secrets", @@ -222,7 +255,7 @@ pub(super) fn create_recv_pending_htlc_info( incoming_shared_secret: shared_secret, incoming_amt_msat: Some(amt_msat), outgoing_amt_msat: onion_amt_msat, - outgoing_cltv_value, + outgoing_cltv_value: onion_cltv_expiry, skimmed_fee_msat: counterparty_skimmed_fee_msat, }) } @@ -239,7 +272,7 @@ pub(super) fn create_recv_pending_htlc_info( pub fn peel_payment_onion( msg: &msgs::UpdateAddHTLC, node_signer: &NS, logger: &L, secp_ctx: &Secp256k1, cur_height: u32, accept_mpp_keysend: bool, allow_skimmed_fees: bool, -) -> Result +) -> Result where NS::Target: NodeSigner, L::Target: Logger, @@ -252,7 +285,7 @@ where HTLCFailureMsg::Relay(r) => (0x4000 | 22, r.reason.data), }; let msg = "Failed to decode update add htlc onion"; - InboundOnionErr { msg, err_code, err_data } + InboundHTLCErr { msg, err_code, err_data } })?; Ok(match hop { onion_utils::Hop::Forward { next_hop_data, next_hop_hmac, new_packet_bytes } => { @@ -261,7 +294,7 @@ where } = match next_packet_details_opt { Some(next_packet_details) => next_packet_details, // Forward should always include the next hop details - None => return Err(InboundOnionErr { + None => return Err(InboundHTLCErr { msg: "Failed to decode update add htlc onion", err_code: 0x4000 | 22, err_data: Vec::new(), @@ -271,7 +304,7 @@ where if let Err((err_msg, code)) = check_incoming_htlc_cltv( cur_height, outgoing_cltv_value, msg.cltv_expiry ) { - return Err(InboundOnionErr { + return Err(InboundHTLCErr { msg: err_msg, err_code: code, err_data: Vec::new(), @@ -313,11 +346,16 @@ where ($msg: expr, $err_code: expr) => { { log_info!(logger, "Failed to accept/forward incoming HTLC: {}", $msg); + let (sha256_of_onion, failure_code) = if msg.blinding_point.is_some() { + ([0; 32], INVALID_ONION_BLINDING) + } else { + (Sha256::hash(&msg.onion_routing_packet.hop_data).to_byte_array(), $err_code) + }; return Err(HTLCFailureMsg::Malformed(msgs::UpdateFailMalformedHTLC { channel_id: msg.channel_id, htlc_id: msg.htlc_id, - sha256_of_onion: Sha256::hash(&msg.onion_routing_packet.hop_data).to_byte_array(), - failure_code: $err_code, + sha256_of_onion, + failure_code, })); } } @@ -327,8 +365,14 @@ where return_malformed_err!("invalid ephemeral pubkey", 0x8000 | 0x4000 | 6); } + let blinded_node_id_tweak = msg.blinding_point.map(|bp| { + let blinded_tlvs_ss = node_signer.ecdh(Recipient::Node, &bp, None).unwrap().secret_bytes(); + let mut hmac = HmacEngine::::new(b"blinded_node_id"); + hmac.input(blinded_tlvs_ss.as_ref()); + Scalar::from_be_bytes(Hmac::from_engine(hmac).to_byte_array()).unwrap() + }); let shared_secret = node_signer.ecdh( - Recipient::Node, &msg.onion_routing_packet.public_key.unwrap(), None + Recipient::Node, &msg.onion_routing_packet.public_key.unwrap(), blinded_node_id_tweak.as_ref() ).unwrap().secret_bytes(); if msg.onion_routing_packet.version != 0 { @@ -343,6 +387,10 @@ where macro_rules! return_err { ($msg: expr, $err_code: expr, $data: expr) => { { + if msg.blinding_point.is_some() { + return_malformed_err!($msg, INVALID_ONION_BLINDING) + } + log_info!(logger, "Failed to accept/forward incoming HTLC: {}", $msg); return Err(HTLCFailureMsg::Relay(msgs::UpdateFailHTLC { channel_id: msg.channel_id, @@ -356,7 +404,7 @@ where 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, node_signer + msg.payment_hash, msg.blinding_point, node_signer ) { Ok(res) => res, Err(onion_utils::OnionDecodeErr::Malformed { err_msg, err_code }) => { diff --git a/lightning/src/ln/onion_route_tests.rs b/lightning/src/ln/onion_route_tests.rs index cfdba07a2f2..4bdd234f5ae 100644 --- a/lightning/src/ln/onion_route_tests.rs +++ b/lightning/src/ln/onion_route_tests.rs @@ -1317,7 +1317,7 @@ fn test_phantom_failure_too_low_cltv() { // Ensure the payment fails with the expected error. let mut error_data = recv_value_msat.to_be_bytes().to_vec(); error_data.extend_from_slice( - &nodes[0].node.best_block.read().unwrap().height().to_be_bytes(), + &nodes[0].node.best_block.read().unwrap().height.to_be_bytes(), ); let mut fail_conditions = PaymentFailedConditions::new() .blamed_scid(phantom_scid) @@ -1447,7 +1447,7 @@ fn test_phantom_failure_too_low_recv_amt() { // Ensure the payment fails with the expected error. let mut error_data = bad_recv_amt_msat.to_be_bytes().to_vec(); - error_data.extend_from_slice(&nodes[1].node.best_block.read().unwrap().height().to_be_bytes()); + error_data.extend_from_slice(&nodes[1].node.best_block.read().unwrap().height.to_be_bytes()); let mut fail_conditions = PaymentFailedConditions::new() .blamed_scid(phantom_scid) .expected_htlc_error_data(0x4000 | 15, &error_data); @@ -1554,7 +1554,7 @@ fn test_phantom_failure_reject_payment() { // Ensure the payment fails with the expected error. let mut error_data = recv_amt_msat.to_be_bytes().to_vec(); - error_data.extend_from_slice(&nodes[1].node.best_block.read().unwrap().height().to_be_bytes()); + error_data.extend_from_slice(&nodes[1].node.best_block.read().unwrap().height.to_be_bytes()); let mut fail_conditions = PaymentFailedConditions::new() .blamed_scid(phantom_scid) .expected_htlc_error_data(0x4000 | 15, &error_data); diff --git a/lightning/src/ln/onion_utils.rs b/lightning/src/ln/onion_utils.rs index ea906c9e32a..c705c4afda8 100644 --- a/lightning/src/ln/onion_utils.rs +++ b/lightning/src/ln/onion_utils.rs @@ -7,30 +7,30 @@ // You may not use this file except in accordance with one or both of these // licenses. -use crate::ln::{PaymentHash, PaymentPreimage}; +use crate::crypto::chacha20::ChaCha20; +use crate::crypto::streams::ChaChaReader; use crate::ln::channelmanager::{HTLCSource, RecipientOnionFields}; use crate::ln::msgs; use crate::ln::wire::Encode; +use crate::ln::{PaymentHash, PaymentPreimage}; use crate::routing::gossip::NetworkUpdate; 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}; use crate::util::logger::Logger; +use crate::util::ser::{LengthCalculatingWriter, Readable, ReadableArgs, Writeable, Writer}; -use bitcoin::hashes::{Hash, HashEngine}; use bitcoin::hashes::cmp::fixed_time_eq; use bitcoin::hashes::hmac::{Hmac, HmacEngine}; use bitcoin::hashes::sha256::Hash as Sha256; +use bitcoin::hashes::{Hash, HashEngine}; -use bitcoin::secp256k1::{SecretKey, PublicKey, Scalar}; -use bitcoin::secp256k1::Secp256k1; -use bitcoin::secp256k1::ecdh::SharedSecret; use bitcoin::secp256k1; +use bitcoin::secp256k1::ecdh::SharedSecret; +use bitcoin::secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey}; -use crate::prelude::*; use crate::io::{Cursor, Read}; +use crate::prelude::*; use core::convert::{AsMut, TryInto}; use core::ops::Deref; @@ -55,16 +55,15 @@ pub(crate) fn gen_rho_from_shared_secret(shared_secret: &[u8]) -> [u8; 32] { #[inline] pub(crate) fn gen_rho_mu_from_shared_secret(shared_secret: &[u8]) -> ([u8; 32], [u8; 32]) { assert_eq!(shared_secret.len(), 32); - ({ - let mut hmac = HmacEngine::::new(&[0x72, 0x68, 0x6f]); // rho - hmac.input(&shared_secret); - Hmac::from_engine(hmac).to_byte_array() - }, - { - let mut hmac = HmacEngine::::new(&[0x6d, 0x75]); // mu - hmac.input(&shared_secret); - Hmac::from_engine(hmac).to_byte_array() - }) + let mut engine_rho = HmacEngine::::new(b"rho"); + engine_rho.input(&shared_secret); + let hmac_rho = Hmac::from_engine(engine_rho).to_byte_array(); + + let mut engine_mu = HmacEngine::::new(b"mu"); + engine_mu.input(&shared_secret); + let hmac_mu = Hmac::from_engine(engine_mu).to_byte_array(); + + (hmac_rho, hmac_mu) } #[inline] @@ -94,7 +93,7 @@ pub(super) fn gen_pad_from_shared_secret(shared_secret: &[u8]) -> [u8; 32] { /// Calculates a pubkey for the next hop, such as the next hop's packet pubkey or blinding point. pub(crate) fn next_hop_pubkey( - secp_ctx: &Secp256k1, curr_pubkey: PublicKey, shared_secret: &[u8] + secp_ctx: &Secp256k1, curr_pubkey: PublicKey, shared_secret: &[u8], ) -> Result { let blinding_factor = { let mut sha = Sha256::engine(); @@ -109,18 +108,21 @@ pub(crate) fn next_hop_pubkey( // 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( - secp_ctx: &Secp256k1, path: &Path, session_priv: &SecretKey, mut callback: FType + secp_ctx: &Secp256k1, path: &Path, session_priv: &SecretKey, mut callback: FType, ) -> Result<(), secp256k1::Error> where T: secp256k1::Signing, - FType: FnMut(SharedSecret, [u8; 32], PublicKey, Option<&RouteHop>, usize) + 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); 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()) + 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() { @@ -143,35 +145,43 @@ where } // can only fail if an intermediary hop has an invalid public key or session_priv is invalid -pub(super) fn construct_onion_keys(secp_ctx: &Secp256k1, path: &Path, session_priv: &SecretKey) -> Result, secp256k1::Error> { +pub(super) fn construct_onion_keys( + secp_ctx: &Secp256k1, path: &Path, session_priv: &SecretKey, +) -> Result, secp256k1::Error> { let mut res = Vec::with_capacity(path.hops.len()); - 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()); + 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 { - #[cfg(test)] - shared_secret, - #[cfg(test)] - blinding_factor: _blinding_factor, - ephemeral_pubkey, - rho, - mu, - }); - })?; + res.push(OnionKeys { + #[cfg(test)] + shared_secret, + #[cfg(test)] + blinding_factor: _blinding_factor, + ephemeral_pubkey, + rho, + mu, + }); + }, + )?; Ok(res) } /// 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) -> Result<(Vec, 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, +) -> Result<(Vec, 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 = Vec::with_capacity( - path.hops.len() + path.blinded_tail.as_ref().map_or(0, |t| t.hops.len()) + path.hops.len() + path.blinded_tail.as_ref().map_or(0, |t| t.hops.len()), ); for (idx, hop) in path.hops.iter().rev().enumerate() { @@ -179,20 +189,28 @@ pub(super) fn build_onion_payloads(path: &Path, total_msat: u64, mut recipient_o // exactly as it should be (and the next hop isn't trying to probe to find out if we're // 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 }; + let cltv = if cur_cltv == starting_htlc_offset { + hop.cltv_expiry_delta + starting_htlc_offset + } else { + cur_cltv + }; if idx == 0 { if let Some(BlindedTail { - blinding_point, hops, final_value_msat, excess_final_cltv_expiry_delta, .. - }) = &path.blinded_tail { + 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, + sender_intended_htlc_amt_msat: *final_value_msat, total_msat, - outgoing_cltv_value: cltv, + cltv_expiry_height: cur_cltv + excess_final_cltv_expiry_delta, encrypted_tlvs: blinded_hop.encrypted_payload.clone(), intro_node_blinding_point: blinding_point.take(), }); @@ -206,32 +224,32 @@ pub(super) fn build_onion_payloads(path: &Path, total_msat: u64, mut recipient_o } else { res.push(msgs::OutboundOnionPayload::Receive { payment_data: if let Some(secret) = recipient_onion.payment_secret.take() { - Some(msgs::FinalOnionHopData { - payment_secret: secret, - total_msat, - }) - } else { None }, + Some(msgs::FinalOnionHopData { payment_secret: secret, total_msat }) + } else { + None + }, payment_metadata: recipient_onion.payment_metadata.take(), keysend_preimage: *keysend_preimage, custom_tlvs: recipient_onion.custom_tlvs.clone(), - amt_msat: value_msat, - outgoing_cltv_value: cltv, + sender_intended_htlc_amt_msat: value_msat, + cltv_expiry_height: cltv, }); } } else { - res.insert(0, msgs::OutboundOnionPayload::Forward { + let payload = msgs::OutboundOnionPayload::Forward { short_channel_id: last_short_channel_id, amt_to_forward: value_msat, outgoing_cltv_value: cltv, - }); + }; + res.insert(0, payload); } cur_value_msat += hop.fee_msat; if cur_value_msat >= 21000000 * 100000000 * 1000 { - return Err(APIError::InvalidRoute{err: "Channel fees overflowed?".to_owned()}); + return Err(APIError::InvalidRoute { err: "Channel fees overflowed?".to_owned() }); } cur_cltv += hop.cltv_expiry_delta as u32; if cur_cltv >= 500000000 { - return Err(APIError::InvalidRoute{err: "Channel CLTV overflowed?".to_owned()}); + return Err(APIError::InvalidRoute { err: "Channel CLTV overflowed?".to_owned() }); } last_short_channel_id = hop.short_channel_id; } @@ -240,14 +258,14 @@ pub(super) fn build_onion_payloads(path: &Path, total_msat: u64, mut recipient_o /// Length of the onion data packet. Before TLV-based onions this was 20 65-byte hops, though now /// the hops can be of variable length. -pub(crate) const ONION_DATA_LEN: usize = 20*65; +pub(crate) const ONION_DATA_LEN: usize = 20 * 65; pub(super) const INVALID_ONION_BLINDING: u16 = 0x8000 | 0x4000 | 24; #[inline] fn shift_slice_right(arr: &mut [u8], amt: usize) { for i in (amt..arr.len()).rev() { - arr[i] = arr[i-amt]; + arr[i] = arr[i - amt]; } for i in 0..amt { arr[i] = 0; @@ -256,28 +274,41 @@ fn shift_slice_right(arr: &mut [u8], amt: usize) { pub(super) fn construct_onion_packet( payloads: Vec, onion_keys: Vec, prng_seed: [u8; 32], - associated_data: &PaymentHash + associated_data: &PaymentHash, ) -> Result { let mut packet_data = [0; ONION_DATA_LEN]; let mut chacha = ChaCha20::new(&prng_seed, &[0; 8]); chacha.process(&[0; ONION_DATA_LEN], &mut packet_data); + let packet = FixedSizeOnionPacket(packet_data); construct_onion_packet_with_init_noise::<_, _>( - payloads, onion_keys, FixedSizeOnionPacket(packet_data), Some(associated_data)) + payloads, + onion_keys, + packet, + Some(associated_data), + ) } #[cfg(test)] /// Used in testing to write bogus `BogusOnionHopData` as well as `RawOnionHopData`, which is /// otherwise not representable in `msgs::OnionHopData`. -pub(super) fn construct_onion_packet_with_writable_hopdata(payloads: Vec, onion_keys: Vec, prng_seed: [u8; 32], associated_data: &PaymentHash) -> Result { +pub(super) fn construct_onion_packet_with_writable_hopdata( + payloads: Vec, onion_keys: Vec, prng_seed: [u8; 32], + associated_data: &PaymentHash, +) -> Result { let mut packet_data = [0; ONION_DATA_LEN]; let mut chacha = ChaCha20::new(&prng_seed, &[0; 8]); chacha.process(&[0; ONION_DATA_LEN], &mut packet_data); + let packet = FixedSizeOnionPacket(packet_data); construct_onion_packet_with_init_noise::<_, _>( - payloads, onion_keys, FixedSizeOnionPacket(packet_data), Some(associated_data)) + payloads, + onion_keys, + packet, + Some(associated_data), + ) } /// Since onion message packets and onion payment packets have different lengths but are otherwise @@ -303,8 +334,8 @@ pub(crate) fn payloads_serialized_length(payloads: &Vec) -> u } pub(crate) fn construct_onion_message_packet>>( - payloads: Vec, onion_keys: Vec, prng_seed: [u8; 32], packet_data_len: usize) -> Result -{ + payloads: Vec, onion_keys: Vec, prng_seed: [u8; 32], packet_data_len: usize, +) -> Result { let mut packet_data = vec![0; packet_data_len]; let mut chacha = ChaCha20::new(&prng_seed, &[0; 8]); @@ -314,8 +345,9 @@ pub(crate) fn construct_onion_message_packet( - mut payloads: Vec, onion_keys: Vec, mut packet_data: P::Data, associated_data: Option<&PaymentHash>) -> Result -{ + mut payloads: Vec, onion_keys: Vec, mut packet_data: P::Data, + associated_data: Option<&PaymentHash>, +) -> Result { let filler = { let packet_data = packet_data.as_mut(); const ONION_HOP_DATA_LEN: usize = 65; // We may decrease this eventually after TLV is common @@ -324,7 +356,8 @@ fn construct_onion_packet_with_init_noise( let mut pos = 0; for (i, (payload, keys)) in payloads.iter().zip(onion_keys.iter()).enumerate() { let mut chacha = ChaCha20::new(&keys.rho, &[0u8; 8]); - for _ in 0..(packet_data.len() - pos) { // TODO: Batch this. + // TODO: Batch this. + for _ in 0..(packet_data.len() - pos) { let mut dummy = [0; 1]; chacha.process_in_place(&mut dummy); // We don't have a seek function :( } @@ -336,7 +369,9 @@ fn construct_onion_packet_with_init_noise( return Err(()); } - if i == payloads.len() - 1 { break; } + if i == payloads.len() - 1 { + break; + } res.resize(pos, 0u8); chacha.process_in_place(&mut res); @@ -376,19 +411,21 @@ fn construct_onion_packet_with_init_noise( /// Encrypts a failure packet. raw_packet can either be a /// msgs::DecodedOnionErrorPacket.encode() result or a msgs::OnionErrorPacket.data element. -pub(super) fn encrypt_failure_packet(shared_secret: &[u8], raw_packet: &[u8]) -> msgs::OnionErrorPacket { +pub(super) fn encrypt_failure_packet( + shared_secret: &[u8], raw_packet: &[u8], +) -> msgs::OnionErrorPacket { let ammag = gen_ammag_from_shared_secret(&shared_secret); let mut packet_crypted = Vec::with_capacity(raw_packet.len()); packet_crypted.resize(raw_packet.len(), 0); let mut chacha = ChaCha20::new(&ammag, &[0u8; 8]); chacha.process(&raw_packet, &mut packet_crypted[..]); - msgs::OnionErrorPacket { - data: packet_crypted, - } + msgs::OnionErrorPacket { data: packet_crypted } } -pub(super) fn build_failure_packet(shared_secret: &[u8], failure_type: u16, failure_data: &[u8]) -> msgs::DecodedOnionErrorPacket { +pub(super) fn build_failure_packet( + shared_secret: &[u8], failure_type: u16, failure_data: &[u8], +) -> msgs::DecodedOnionErrorPacket { assert_eq!(shared_secret.len(), 32); assert!(failure_data.len() <= 256 - 2); @@ -406,11 +443,7 @@ pub(super) fn build_failure_packet(shared_secret: &[u8], failure_type: u16, fail res.resize(256 - 2 - failure_data.len(), 0); res }; - let mut packet = msgs::DecodedOnionErrorPacket { - hmac: [0; 32], - failuremsg, - pad, - }; + let mut packet = msgs::DecodedOnionErrorPacket { hmac: [0; 32], failuremsg, pad }; let mut hmac = HmacEngine::::new(&um); hmac.input(&packet.encode()[32..]); @@ -420,7 +453,9 @@ pub(super) fn build_failure_packet(shared_secret: &[u8], failure_type: u16, fail } #[cfg(test)] -pub(super) fn build_first_hop_failure_packet(shared_secret: &[u8], failure_type: u16, failure_data: &[u8]) -> msgs::OnionErrorPacket { +pub(super) fn build_first_hop_failure_packet( + shared_secret: &[u8], failure_type: u16, failure_data: &[u8], +) -> msgs::OnionErrorPacket { let failure_packet = build_failure_packet(shared_secret, failure_type, failure_data); encrypt_failure_packet(shared_secret, &failure_packet.encode()[..]) } @@ -429,6 +464,7 @@ pub(crate) struct DecodedOnionFailure { pub(crate) network_update: Option, pub(crate) short_channel_id: Option, pub(crate) payment_failed_permanently: bool, + pub(crate) failed_within_blinded_path: bool, #[cfg(test)] pub(crate) onion_error_code: Option, #[cfg(test)] @@ -438,7 +474,7 @@ pub(crate) struct DecodedOnionFailure { /// Note that we always decrypt `packet` in-place here even if the deserialization into /// [`msgs::DecodedOnionErrorPacket`] ultimately fails. fn decrypt_onion_error_packet( - packet: &mut Vec, shared_secret: SharedSecret + packet: &mut Vec, shared_secret: SharedSecret, ) -> Result { let ammag = gen_ammag_from_shared_secret(shared_secret.as_ref()); let mut chacha = ChaCha20::new(&ammag, &[0u8; 8]); @@ -450,19 +486,26 @@ fn decrypt_onion_error_packet( /// OutboundRoute). #[inline] pub(super) fn process_onion_failure( - secp_ctx: &Secp256k1, logger: &L, htlc_source: &HTLCSource, mut encrypted_packet: Vec -) -> 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!() }; + secp_ctx: &Secp256k1, logger: &L, htlc_source: &HTLCSource, mut encrypted_packet: Vec, +) -> DecodedOnionFailure +where + L::Target: Logger, +{ + let (path, session_priv, first_hop_htlc_msat) = match htlc_source { + HTLCSource::OutboundRoute { + ref path, ref session_priv, ref first_hop_htlc_msat, .. + } => (path, session_priv, first_hop_htlc_msat), + _ => { + unreachable!() + }, + }; // Learnings from the HTLC failure to inform future payment retries and scoring. struct FailureLearnings { network_update: Option, short_channel_id: Option, payment_failed_permanently: bool, + failed_within_blinded_path: bool, } let mut res: Option = None; let mut htlc_msat = *first_hop_htlc_msat; @@ -476,10 +519,10 @@ pub(super) fn process_onion_failure( 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 callback = |shared_secret, _, _, route_hop_opt: Option<&RouteHop>, route_hop_idx| { + if res.is_some() { + return; + } let route_hop = match route_hop_opt { Some(hop) => hop, @@ -488,9 +531,12 @@ pub(super) fn process_onion_failure( 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 + network_update: None, + short_channel_id: None, + payment_failed_permanently: false, + failed_within_blinded_path: true, }); - return + return; }, }; @@ -499,31 +545,39 @@ pub(super) fn process_onion_failure( 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 { + 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. - #[cfg(not(test))] { + #[cfg(not(test))] + { error_code_ret = Some(BADONION | PERM | 24); // invalid_onion_blinding error_packet_ret = Some(vec![0; 32]); } - #[cfg(test)] { + #[cfg(test)] + { // Actually parse the onion error data in tests so we can check that blinded hops fail // back correctly. - let err_packet = decrypt_onion_error_packet( - &mut encrypted_packet, shared_secret - ).unwrap(); - error_code_ret = - Some(u16::from_be_bytes(err_packet.failuremsg.get(0..2).unwrap().try_into().unwrap())); + let err_packet = + decrypt_onion_error_packet(&mut encrypted_packet, shared_secret) + .unwrap(); + error_code_ret = Some(u16::from_be_bytes( + err_packet.failuremsg.get(0..2).unwrap().try_into().unwrap(), + )); error_packet_ret = Some(err_packet.failuremsg[2..].to_vec()); } res = Some(FailureLearnings { - network_update: None, short_channel_id: None, payment_failed_permanently: false + network_update: None, + short_channel_id: None, + payment_failed_permanently: false, + failed_within_blinded_path: true, }); - return - } + return; + }, } }; @@ -532,13 +586,15 @@ pub(super) fn process_onion_failure( let err_packet = match decrypt_onion_error_packet(&mut encrypted_packet, shared_secret) { Ok(p) => p, - Err(_) => return + Err(_) => return, }; let um = gen_um_from_shared_secret(shared_secret.as_ref()); let mut hmac = HmacEngine::::new(&um); hmac.input(&err_packet.encode()[32..]); - if !fixed_time_eq(&Hmac::from_engine(hmac).to_byte_array(), &err_packet.hmac) { return } + if !fixed_time_eq(&Hmac::from_engine(hmac).to_byte_array(), &err_packet.hmac) { + return; + } let error_code_slice = match err_packet.failuremsg.get(0..2) { Some(s) => s, None => { @@ -550,10 +606,13 @@ pub(super) fn process_onion_failure( }); 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 + network_update, + short_channel_id, + payment_failed_permanently: is_from_final_node, + failed_within_blinded_path: false, }); - return - } + return; + }, }; let error_code = u16::from_be_bytes(error_code_slice.try_into().expect("len is 2")); @@ -564,7 +623,7 @@ pub(super) fn process_onion_failure( // 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, + 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 @@ -585,7 +644,8 @@ pub(super) fn process_onion_failure( }); } 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 }); + 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 { @@ -596,9 +656,15 @@ pub(super) fn process_onion_failure( 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) { + 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 @@ -608,7 +674,9 @@ pub(super) fn process_onion_failure( // 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() { + 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."); @@ -619,20 +687,36 @@ pub(super) fn process_onion_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, + 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 @@ -648,14 +732,15 @@ pub(super) fn process_onion_failure( 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 { + 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, - }) + 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. @@ -670,9 +755,11 @@ pub(super) fn process_onion_failure( // 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, + log_info!( + logger, "Failed to read a channel_update of len {} in an onion", - update_slice.len()); + update_slice.len() + ); } } } @@ -691,50 +778,80 @@ pub(super) fn process_onion_failure( // 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), + 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 it's really not worth trying any further. - network_update = Some(NetworkUpdate::NodeFailure { - node_id: route_hop.pubkey, - is_permanent: true, - }); + network_update = + Some(NetworkUpdate::NodeFailure { node_id: route_hop.pubkey, is_permanent: true }); short_channel_id = Some(route_hop.short_channel_id); } res = Some(FailureLearnings { - network_update, short_channel_id, - payment_failed_permanently: error_code & PERM == PERM && is_from_final_node + network_update, + short_channel_id, + payment_failed_permanently: error_code & PERM == PERM && is_from_final_node, + failed_within_blinded_path: false, }); 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); + 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); + 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?"); + }; + + construct_onion_keys_callback(secp_ctx, &path, session_priv, callback) + .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 { + network_update, + short_channel_id, + payment_failed_permanently, + failed_within_blinded_path, + }) = res + { DecodedOnionFailure { - network_update, short_channel_id, payment_failed_permanently, + network_update, + short_channel_id, + payment_failed_permanently, + failed_within_blinded_path, #[cfg(test)] onion_error_code: error_code_ret, #[cfg(test)] - onion_error_data: error_packet_ret + 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, + network_update: None, + short_channel_id: None, + payment_failed_permanently: is_from_final_node, + failed_within_blinded_path: false, #[cfg(test)] onion_error_code: None, #[cfg(test)] - onion_error_data: None + onion_error_data: None, } } } @@ -746,13 +863,8 @@ pub(super) struct HTLCFailReason(HTLCFailReasonRepr); #[derive(Clone)] // See Channel::revoke_and_ack for why, tl;dr: Rust bug #[cfg_attr(test, derive(PartialEq))] enum HTLCFailReasonRepr { - LightningError { - err: msgs::OnionErrorPacket, - }, - Reason { - failure_code: u16, - data: Vec, - } + LightningError { err: msgs::OnionErrorPacket }, + Reason { failure_code: u16, data: Vec }, } impl core::fmt::Debug for HTLCFailReason { @@ -763,7 +875,7 @@ impl core::fmt::Debug for HTLCFailReason { }, HTLCFailReasonRepr::LightningError { .. } => { write!(f, "pre-built LightningError") - } + }, } } } @@ -790,6 +902,7 @@ impl_writeable_tlv_based_enum!(HTLCFailReasonRepr, ;); impl HTLCFailReason { + #[rustfmt::skip] pub(super) fn reason(failure_code: u16, data: Vec) -> Self { const BADONION: u16 = 0x8000; const PERM: u16 = 0x4000; @@ -840,28 +953,42 @@ impl HTLCFailReason { Self(HTLCFailReasonRepr::LightningError { err: msg.reason.clone() }) } - pub(super) fn get_encrypted_failure_packet(&self, incoming_packet_shared_secret: &[u8; 32], phantom_shared_secret: &Option<[u8; 32]>) - -> msgs::OnionErrorPacket { + pub(super) fn get_encrypted_failure_packet( + &self, incoming_packet_shared_secret: &[u8; 32], phantom_shared_secret: &Option<[u8; 32]>, + ) -> msgs::OnionErrorPacket { match self.0 { HTLCFailReasonRepr::Reason { ref failure_code, ref data } => { if let Some(phantom_ss) = phantom_shared_secret { - let phantom_packet = build_failure_packet(phantom_ss, *failure_code, &data[..]).encode(); - let encrypted_phantom_packet = encrypt_failure_packet(phantom_ss, &phantom_packet); - encrypt_failure_packet(incoming_packet_shared_secret, &encrypted_phantom_packet.data[..]) + let phantom_packet = + build_failure_packet(phantom_ss, *failure_code, &data[..]).encode(); + let encrypted_phantom_packet = + encrypt_failure_packet(phantom_ss, &phantom_packet); + encrypt_failure_packet( + incoming_packet_shared_secret, + &encrypted_phantom_packet.data[..], + ) } else { - let packet = build_failure_packet(incoming_packet_shared_secret, *failure_code, &data[..]).encode(); + let packet = build_failure_packet( + incoming_packet_shared_secret, + *failure_code, + &data[..], + ) + .encode(); encrypt_failure_packet(incoming_packet_shared_secret, &packet) } }, HTLCFailReasonRepr::LightningError { ref err } => { encrypt_failure_packet(incoming_packet_shared_secret, &err.data) - } + }, } } pub(super) fn decode_onion_failure( - &self, secp_ctx: &Secp256k1, logger: &L, htlc_source: &HTLCSource - ) -> DecodedOnionFailure where L::Target: Logger { + &self, secp_ctx: &Secp256k1, logger: &L, htlc_source: &HTLCSource, + ) -> DecodedOnionFailure + where + L::Target: Logger, + { match self.0 { HTLCFailReasonRepr::LightningError { ref err } => { process_onion_failure(secp_ctx, logger, &htlc_source, err.data.clone()) @@ -878,13 +1005,16 @@ impl HTLCFailReason { network_update: None, payment_failed_permanently: false, short_channel_id: Some(path.hops[0].short_channel_id), + failed_within_blinded_path: false, #[cfg(test)] onion_error_code: Some(*failure_code), #[cfg(test)] onion_error_data: Some(data.clone()), } - } else { unreachable!(); } - } + } else { + unreachable!(); + } + }, } } } @@ -896,7 +1026,7 @@ pub(crate) trait NextPacketBytes: AsMut<[u8]> { } impl NextPacketBytes for FixedSizeOnionPacket { - fn new(_len: usize) -> Self { + fn new(_len: usize) -> Self { Self([0 as u8; ONION_DATA_LEN]) } } @@ -927,29 +1057,28 @@ pub(crate) enum Hop { #[derive(Debug)] pub(crate) enum OnionDecodeErr { /// The HMAC of the onion packet did not match the hop data. - Malformed { - err_msg: &'static str, - err_code: u16, - }, + Malformed { err_msg: &'static str, err_code: u16 }, /// We failed to decode the onion payload. - Relay { - err_msg: &'static str, - err_code: u16, - }, + Relay { err_msg: &'static str, err_code: u16 }, } pub(crate) fn decode_next_payment_hop( shared_secret: [u8; 32], hop_data: &[u8], hmac_bytes: [u8; 32], payment_hash: PaymentHash, - node_signer: &NS, -) -> Result where NS::Target: NodeSigner { - match decode_next_hop(shared_secret, hop_data, hmac_bytes, Some(payment_hash), node_signer) { + blinding_point: Option, node_signer: &NS, +) -> Result +where + NS::Target: NodeSigner, +{ + match decode_next_hop( + shared_secret, + hop_data, + hmac_bytes, + Some(payment_hash), + (blinding_point, 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 { - next_hop_data, - next_hop_hmac, - new_packet_bytes - }) + Ok(Hop::Forward { next_hop_data, next_hop_hmac, new_packet_bytes }) }, Err(e) => Err(e), } @@ -960,27 +1089,35 @@ pub(crate) fn decode_next_payment_hop( pub fn create_payment_onion( secp_ctx: &Secp256k1, path: &Path, session_priv: &SecretKey, total_msat: u64, recipient_onion: RecipientOnionFields, cur_block_height: u32, payment_hash: &PaymentHash, - keysend_preimage: &Option, prng_seed: [u8; 32] + keysend_preimage: &Option, prng_seed: [u8; 32], ) -> Result<(msgs::OnionPacket, u64, u32), APIError> { - let onion_keys = construct_onion_keys(&secp_ctx, &path, &session_priv) - .map_err(|_| APIError::InvalidRoute{ - err: "Pubkey along hop was maliciously selected".to_owned() - })?; + let onion_keys = construct_onion_keys(&secp_ctx, &path, &session_priv).map_err(|_| { + APIError::InvalidRoute { err: "Pubkey along hop was maliciously selected".to_owned() } + })?; let (onion_payloads, htlc_msat, htlc_cltv) = build_onion_payloads( - &path, total_msat, recipient_onion, cur_block_height, keysend_preimage + &path, + total_msat, + recipient_onion, + cur_block_height, + keysend_preimage, )?; let onion_packet = construct_onion_packet(onion_payloads, onion_keys, prng_seed, payment_hash) - .map_err(|_| APIError::InvalidRoute{ - err: "Route size too large considering onion data".to_owned() + .map_err(|_| APIError::InvalidRoute { + err: "Route size too large considering onion data".to_owned(), })?; Ok((onion_packet, htlc_msat, htlc_cltv)) } -pub(crate) fn decode_next_untagged_hop, N: NextPacketBytes>(shared_secret: [u8; 32], hop_data: &[u8], hmac_bytes: [u8; 32], read_args: T) -> Result<(R, Option<([u8; 32], N)>), OnionDecodeErr> { +pub(crate) fn decode_next_untagged_hop, N: NextPacketBytes>( + shared_secret: [u8; 32], hop_data: &[u8], hmac_bytes: [u8; 32], read_args: T, +) -> Result<(R, Option<([u8; 32], N)>), OnionDecodeErr> { decode_next_hop(shared_secret, hop_data, hmac_bytes, None, read_args) } -fn decode_next_hop, N: NextPacketBytes>(shared_secret: [u8; 32], hop_data: &[u8], hmac_bytes: [u8; 32], payment_hash: Option, read_args: T) -> Result<(R, Option<([u8; 32], N)>), OnionDecodeErr> { +fn decode_next_hop, N: NextPacketBytes>( + shared_secret: [u8; 32], hop_data: &[u8], hmac_bytes: [u8; 32], + payment_hash: Option, read_args: T, +) -> Result<(R, Option<([u8; 32], N)>), OnionDecodeErr> { let (rho, mu) = gen_rho_mu_from_shared_secret(&shared_secret); let mut hmac = HmacEngine::::new(&mu); hmac.input(hop_data); @@ -999,11 +1136,14 @@ fn decode_next_hop, N: NextPacketBytes>(shared_secret: [u8 match R::read(&mut chacha_stream, read_args) { Err(err) => { let error_code = match err { - msgs::DecodeError::UnknownVersion => 0x4000 | 1, // unknown realm byte - msgs::DecodeError::UnknownRequiredFeature| - msgs::DecodeError::InvalidValue| - msgs::DecodeError::ShortRead => 0x4000 | 22, // invalid_onion_payload - _ => 0x2000 | 2, // Should never happen + // Unknown realm byte + msgs::DecodeError::UnknownVersion => 0x4000 | 1, + // invalid_onion_payload + msgs::DecodeError::UnknownRequiredFeature + | msgs::DecodeError::InvalidValue + | msgs::DecodeError::ShortRead => 0x4000 | 22, + // Should never happen + _ => 0x2000 | 2, }; return Err(OnionDecodeErr::Relay { err_msg: "Unable to decode our hop data", @@ -1053,7 +1193,7 @@ fn decode_next_hop, N: NextPacketBytes>(shared_secret: [u8 // Once we've emptied the set of bytes our peer gave us, encrypt 0 bytes until we // fill the onion hop data we'll forward to our next-hop peer. chacha_stream.chacha.process_in_place(&mut new_packet_bytes.as_mut()[read_pos..]); - return Ok((msg, Some((hmac, new_packet_bytes)))) // This packet needs forwarding + return Ok((msg, Some((hmac, new_packet_bytes)))); // This packet needs forwarding } }, } @@ -1062,21 +1202,22 @@ fn decode_next_hop, N: NextPacketBytes>(shared_secret: [u8 #[cfg(test)] mod tests { use crate::io; - use crate::prelude::*; - use crate::ln::PaymentHash; use crate::ln::features::{ChannelFeatures, NodeFeatures}; - use crate::routing::router::{Path, Route, RouteHop}; use crate::ln::msgs; - use crate::util::ser::{Writeable, Writer, VecWriter}; + use crate::ln::PaymentHash; + use crate::prelude::*; + use crate::routing::router::{Path, Route, RouteHop}; + use crate::util::ser::{VecWriter, Writeable, Writer}; use bitcoin::hashes::hex::FromHex; use bitcoin::secp256k1::Secp256k1; - use bitcoin::secp256k1::{PublicKey,SecretKey}; + use bitcoin::secp256k1::{PublicKey, SecretKey}; use super::OnionKeys; fn get_test_session_key() -> SecretKey { - SecretKey::from_slice(&>::from_hex("4141414141414141414141414141414141414141414141414141414141414141").unwrap()[..]).unwrap() + let hex = "4141414141414141414141414141414141414141414141414141414141414141"; + SecretKey::from_slice(&>::from_hex(hex).unwrap()[..]).unwrap() } fn build_test_onion_keys() -> Vec { @@ -1114,7 +1255,9 @@ mod tests { route_params: None, }; - let onion_keys = super::construct_onion_keys(&secp_ctx, &route.paths[0], &get_test_session_key()).unwrap(); + let onion_keys = + super::construct_onion_keys(&secp_ctx, &route.paths[0], &get_test_session_key()) + .unwrap(); assert_eq!(onion_keys.len(), route.paths[0].hops.len()); onion_keys } @@ -1125,35 +1268,110 @@ mod tests { // Test generation of ephemeral keys and secrets. These values used to be part of the BOLT4 // test vectors, but have since been removed. We keep them as they provide test coverage. - assert_eq!(onion_keys[0].shared_secret.secret_bytes(), >::from_hex("53eb63ea8a3fec3b3cd433b85cd62a4b145e1dda09391b348c4e1cd36a03ea66").unwrap()[..]); - assert_eq!(onion_keys[0].blinding_factor[..], >::from_hex("2ec2e5da605776054187180343287683aa6a51b4b1c04d6dd49c45d8cffb3c36").unwrap()[..]); - assert_eq!(onion_keys[0].ephemeral_pubkey.serialize()[..], >::from_hex("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619").unwrap()[..]); - assert_eq!(onion_keys[0].rho, >::from_hex("ce496ec94def95aadd4bec15cdb41a740c9f2b62347c4917325fcc6fb0453986").unwrap()[..]); - assert_eq!(onion_keys[0].mu, >::from_hex("b57061dc6d0a2b9f261ac410c8b26d64ac5506cbba30267a649c28c179400eba").unwrap()[..]); - - assert_eq!(onion_keys[1].shared_secret.secret_bytes(), >::from_hex("a6519e98832a0b179f62123b3567c106db99ee37bef036e783263602f3488fae").unwrap()[..]); - assert_eq!(onion_keys[1].blinding_factor[..], >::from_hex("bf66c28bc22e598cfd574a1931a2bafbca09163df2261e6d0056b2610dab938f").unwrap()[..]); - assert_eq!(onion_keys[1].ephemeral_pubkey.serialize()[..], >::from_hex("028f9438bfbf7feac2e108d677e3a82da596be706cc1cf342b75c7b7e22bf4e6e2").unwrap()[..]); - assert_eq!(onion_keys[1].rho, >::from_hex("450ffcabc6449094918ebe13d4f03e433d20a3d28a768203337bc40b6e4b2c59").unwrap()[..]); - assert_eq!(onion_keys[1].mu, >::from_hex("05ed2b4a3fb023c2ff5dd6ed4b9b6ea7383f5cfe9d59c11d121ec2c81ca2eea9").unwrap()[..]); - - assert_eq!(onion_keys[2].shared_secret.secret_bytes(), >::from_hex("3a6b412548762f0dbccce5c7ae7bb8147d1caf9b5471c34120b30bc9c04891cc").unwrap()[..]); - assert_eq!(onion_keys[2].blinding_factor[..], >::from_hex("a1f2dadd184eb1627049673f18c6325814384facdee5bfd935d9cb031a1698a5").unwrap()[..]); - assert_eq!(onion_keys[2].ephemeral_pubkey.serialize()[..], >::from_hex("03bfd8225241ea71cd0843db7709f4c222f62ff2d4516fd38b39914ab6b83e0da0").unwrap()[..]); - assert_eq!(onion_keys[2].rho, >::from_hex("11bf5c4f960239cb37833936aa3d02cea82c0f39fd35f566109c41f9eac8deea").unwrap()[..]); - assert_eq!(onion_keys[2].mu, >::from_hex("caafe2820fa00eb2eeb78695ae452eba38f5a53ed6d53518c5c6edf76f3f5b78").unwrap()[..]); - - assert_eq!(onion_keys[3].shared_secret.secret_bytes(), >::from_hex("21e13c2d7cfe7e18836df50872466117a295783ab8aab0e7ecc8c725503ad02d").unwrap()[..]); - assert_eq!(onion_keys[3].blinding_factor[..], >::from_hex("7cfe0b699f35525029ae0fa437c69d0f20f7ed4e3916133f9cacbb13c82ff262").unwrap()[..]); - assert_eq!(onion_keys[3].ephemeral_pubkey.serialize()[..], >::from_hex("031dde6926381289671300239ea8e57ffaf9bebd05b9a5b95beaf07af05cd43595").unwrap()[..]); - assert_eq!(onion_keys[3].rho, >::from_hex("cbe784ab745c13ff5cffc2fbe3e84424aa0fd669b8ead4ee562901a4a4e89e9e").unwrap()[..]); - assert_eq!(onion_keys[3].mu, >::from_hex("5052aa1b3d9f0655a0932e50d42f0c9ba0705142c25d225515c45f47c0036ee9").unwrap()[..]); - - assert_eq!(onion_keys[4].shared_secret.secret_bytes(), >::from_hex("b5756b9b542727dbafc6765a49488b023a725d631af688fc031217e90770c328").unwrap()[..]); - assert_eq!(onion_keys[4].blinding_factor[..], >::from_hex("c96e00dddaf57e7edcd4fb5954be5b65b09f17cb6d20651b4e90315be5779205").unwrap()[..]); - assert_eq!(onion_keys[4].ephemeral_pubkey.serialize()[..], >::from_hex("03a214ebd875aab6ddfd77f22c5e7311d7f77f17a169e599f157bbcdae8bf071f4").unwrap()[..]); - assert_eq!(onion_keys[4].rho, >::from_hex("034e18b8cc718e8af6339106e706c52d8df89e2b1f7e9142d996acf88df8799b").unwrap()[..]); - assert_eq!(onion_keys[4].mu, >::from_hex("8e45e5c61c2b24cb6382444db6698727afb063adecd72aada233d4bf273d975a").unwrap()[..]); + let hex = "53eb63ea8a3fec3b3cd433b85cd62a4b145e1dda09391b348c4e1cd36a03ea66"; + assert_eq!( + onion_keys[0].shared_secret.secret_bytes(), + >::from_hex(hex).unwrap()[..] + ); + + let hex = "2ec2e5da605776054187180343287683aa6a51b4b1c04d6dd49c45d8cffb3c36"; + assert_eq!(onion_keys[0].blinding_factor[..], >::from_hex(hex).unwrap()[..]); + + let hex = "02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619"; + assert_eq!( + onion_keys[0].ephemeral_pubkey.serialize()[..], + >::from_hex(hex).unwrap()[..] + ); + + let hex = "ce496ec94def95aadd4bec15cdb41a740c9f2b62347c4917325fcc6fb0453986"; + assert_eq!(onion_keys[0].rho, >::from_hex(hex).unwrap()[..]); + + let hex = "b57061dc6d0a2b9f261ac410c8b26d64ac5506cbba30267a649c28c179400eba"; + assert_eq!(onion_keys[0].mu, >::from_hex(hex).unwrap()[..]); + + let hex = "a6519e98832a0b179f62123b3567c106db99ee37bef036e783263602f3488fae"; + assert_eq!( + onion_keys[1].shared_secret.secret_bytes(), + >::from_hex(hex).unwrap()[..] + ); + + let hex = "bf66c28bc22e598cfd574a1931a2bafbca09163df2261e6d0056b2610dab938f"; + assert_eq!(onion_keys[1].blinding_factor[..], >::from_hex(hex).unwrap()[..]); + + let hex = "028f9438bfbf7feac2e108d677e3a82da596be706cc1cf342b75c7b7e22bf4e6e2"; + assert_eq!( + onion_keys[1].ephemeral_pubkey.serialize()[..], + >::from_hex(hex).unwrap()[..] + ); + + let hex = "450ffcabc6449094918ebe13d4f03e433d20a3d28a768203337bc40b6e4b2c59"; + assert_eq!(onion_keys[1].rho, >::from_hex(hex).unwrap()[..]); + + let hex = "05ed2b4a3fb023c2ff5dd6ed4b9b6ea7383f5cfe9d59c11d121ec2c81ca2eea9"; + assert_eq!(onion_keys[1].mu, >::from_hex(hex).unwrap()[..]); + + let hex = "3a6b412548762f0dbccce5c7ae7bb8147d1caf9b5471c34120b30bc9c04891cc"; + assert_eq!( + onion_keys[2].shared_secret.secret_bytes(), + >::from_hex(hex).unwrap()[..] + ); + + let hex = "a1f2dadd184eb1627049673f18c6325814384facdee5bfd935d9cb031a1698a5"; + assert_eq!(onion_keys[2].blinding_factor[..], >::from_hex(hex).unwrap()[..]); + + let hex = "03bfd8225241ea71cd0843db7709f4c222f62ff2d4516fd38b39914ab6b83e0da0"; + assert_eq!( + onion_keys[2].ephemeral_pubkey.serialize()[..], + >::from_hex(hex).unwrap()[..] + ); + + let hex = "11bf5c4f960239cb37833936aa3d02cea82c0f39fd35f566109c41f9eac8deea"; + assert_eq!(onion_keys[2].rho, >::from_hex(hex).unwrap()[..]); + + let hex = "caafe2820fa00eb2eeb78695ae452eba38f5a53ed6d53518c5c6edf76f3f5b78"; + assert_eq!(onion_keys[2].mu, >::from_hex(hex).unwrap()[..]); + + let hex = "21e13c2d7cfe7e18836df50872466117a295783ab8aab0e7ecc8c725503ad02d"; + assert_eq!( + onion_keys[3].shared_secret.secret_bytes(), + >::from_hex(hex).unwrap()[..] + ); + + let hex = "7cfe0b699f35525029ae0fa437c69d0f20f7ed4e3916133f9cacbb13c82ff262"; + assert_eq!(onion_keys[3].blinding_factor[..], >::from_hex(hex).unwrap()[..]); + + let hex = "031dde6926381289671300239ea8e57ffaf9bebd05b9a5b95beaf07af05cd43595"; + assert_eq!( + onion_keys[3].ephemeral_pubkey.serialize()[..], + >::from_hex(hex).unwrap()[..] + ); + + let hex = "cbe784ab745c13ff5cffc2fbe3e84424aa0fd669b8ead4ee562901a4a4e89e9e"; + assert_eq!(onion_keys[3].rho, >::from_hex(hex).unwrap()[..]); + + let hex = "5052aa1b3d9f0655a0932e50d42f0c9ba0705142c25d225515c45f47c0036ee9"; + assert_eq!(onion_keys[3].mu, >::from_hex(hex).unwrap()[..]); + + let hex = "b5756b9b542727dbafc6765a49488b023a725d631af688fc031217e90770c328"; + assert_eq!( + onion_keys[4].shared_secret.secret_bytes(), + >::from_hex(hex).unwrap()[..] + ); + + let hex = "c96e00dddaf57e7edcd4fb5954be5b65b09f17cb6d20651b4e90315be5779205"; + assert_eq!(onion_keys[4].blinding_factor[..], >::from_hex(hex).unwrap()[..]); + + let hex = "03a214ebd875aab6ddfd77f22c5e7311d7f77f17a169e599f157bbcdae8bf071f4"; + assert_eq!( + onion_keys[4].ephemeral_pubkey.serialize()[..], + >::from_hex(hex).unwrap()[..] + ); + + let hex = "034e18b8cc718e8af6339106e706c52d8df89e2b1f7e9142d996acf88df8799b"; + assert_eq!(onion_keys[4].rho, >::from_hex(hex).unwrap()[..]); + + let hex = "8e45e5c61c2b24cb6382444db6698727afb063adecd72aada233d4bf273d975a"; + assert_eq!(onion_keys[4].mu, >::from_hex(hex).unwrap()[..]); // Packet creation test vectors from BOLT 4 (see // https://github.com/lightning/bolts/blob/16973e2b857e853308cafd59e42fa830d75b1642/bolt04/onion-test.json). @@ -1218,26 +1436,37 @@ mod tests { let mut w = VecWriter(Vec::new()); payloads[0].write(&mut w).unwrap(); let hop_1_serialized_payload = w.0; - let expected_serialized_hop_1_payload = &>::from_hex("1202023a98040205dc06080000000000000001").unwrap()[..]; + let hex = "1202023a98040205dc06080000000000000001"; + let expected_serialized_hop_1_payload = &>::from_hex(hex).unwrap()[..]; assert_eq!(hop_1_serialized_payload, expected_serialized_hop_1_payload); w = VecWriter(Vec::new()); payloads[2].write(&mut w).unwrap(); let hop_3_serialized_payload = w.0; - let expected_serialized_hop_3_payload = &>::from_hex("12020230d4040204e206080000000000000003").unwrap()[..]; + let hex = "12020230d4040204e206080000000000000003"; + let expected_serialized_hop_3_payload = &>::from_hex(hex).unwrap()[..]; assert_eq!(hop_3_serialized_payload, expected_serialized_hop_3_payload); w = VecWriter(Vec::new()); payloads[3].write(&mut w).unwrap(); let hop_4_serialized_payload = w.0; - let expected_serialized_hop_4_payload = &>::from_hex("1202022710040203e806080000000000000004").unwrap()[..]; + let hex = "1202022710040203e806080000000000000004"; + let expected_serialized_hop_4_payload = &>::from_hex(hex).unwrap()[..]; assert_eq!(hop_4_serialized_payload, expected_serialized_hop_4_payload); - let pad_keytype_seed = super::gen_pad_from_shared_secret(&get_test_session_key().secret_bytes()); + let pad_keytype_seed = + super::gen_pad_from_shared_secret(&get_test_session_key().secret_bytes()); - let packet: msgs::OnionPacket = super::construct_onion_packet_with_writable_hopdata::<_>(payloads, onion_keys, pad_keytype_seed, &PaymentHash([0x42; 32])).unwrap(); + let packet: msgs::OnionPacket = super::construct_onion_packet_with_writable_hopdata::<_>( + payloads, + onion_keys, + pad_keytype_seed, + &PaymentHash([0x42; 32]), + ) + .unwrap(); - assert_eq!(packet.encode(), >::from_hex("0002EEC7245D6B7D2CCB30380BFBE2A3648CD7A942653F5AA340EDCEA1F283686619F7F3416A5AA36DC7EEB3EC6D421E9615471AB870A33AC07FA5D5A51DF0A8823AABE3FEA3F90D387529D4F72837F9E687230371CCD8D263072206DBED0234F6505E21E282ABD8C0E4F5B9FF8042800BBAB065036EADD0149B37F27DDE664725A49866E052E809D2B0198AB9610FAA656BBF4EC516763A59F8F42C171B179166BA38958D4F51B39B3E98706E2D14A2DAFD6A5DF808093ABFCA5AEAACA16EDED5DB7D21FB0294DD1A163EDF0FB445D5C8D7D688D6DD9C541762BF5A5123BF9939D957FE648416E88F1B0928BFA034982B22548E1A4D922690EECF546275AFB233ACF4323974680779F1A964CFE687456035CC0FBA8A5428430B390F0057B6D1FE9A8875BFA89693EEB838CE59F09D207A503EE6F6299C92D6361BC335FCBF9B5CD44747AADCE2CE6069CFDC3D671DAEF9F8AE590CF93D957C9E873E9A1BC62D9640DC8FC39C14902D49A1C80239B6C5B7FD91D05878CBF5FFC7DB2569F47C43D6C0D27C438ABFF276E87364DEB8858A37E5A62C446AF95D8B786EAF0B5FCF78D98B41496794F8DCAAC4EEF34B2ACFB94C7E8C32A9E9866A8FA0B6F2A06F00A1CCDE569F97EEC05C803BA7500ACC96691D8898D73D8E6A47B8F43C3D5DE74458D20EDA61474C426359677001FBD75A74D7D5DB6CB4FEB83122F133206203E4E2D293F838BF8C8B3A29ACB321315100B87E80E0EDB272EE80FDA944E3FB6084ED4D7F7C7D21C69D9DA43D31A90B70693F9B0CC3EAC74C11AB8FF655905688916CFA4EF0BD04135F2E50B7C689A21D04E8E981E74C6058188B9B1F9DFC3EEC6838E9FFBCF22CE738D8A177C19318DFFEF090CEE67E12DE1A3E2A39F61247547BA5257489CBC11D7D91ED34617FCC42F7A9DA2E3CF31A94A210A1018143173913C38F60E62B24BF0D7518F38B5BAB3E6A1F8AEB35E31D6442C8ABB5178EFC892D2E787D79C6AD9E2FC271792983FA9955AC4D1D84A36C024071BC6E431B625519D556AF38185601F70E29035EA6A09C8B676C9D88CF7E05E0F17098B584C4168735940263F940033A220F40BE4C85344128B14BEB9E75696DB37014107801A59B13E89CD9D2258C169D523BE6D31552C44C82FF4BB18EC9F099F3BF0E5B1BB2BA9A87D7E26F98D294927B600B5529C47E04D98956677CBCEE8FA2B60F49776D8B8C367465B7C626DA53700684FB6C918EAD0EAB8360E4F60EDD25B4F43816A75ECF70F909301825B512469F8389D79402311D8AECB7B3EF8599E79485A4388D87744D899F7C47EE644361E17040A7958C8911BE6F463AB6A9B2AFACD688EC55EF517B38F1339EFC54487232798BB25522FF4572FF68567FE830F92F7B8113EFCE3E98C3FFFBAEDCE4FD8B50E41DA97C0C08E423A72689CC68E68F752A5E3A9003E64E35C957CA2E1C48BB6F64B05F56B70B575AD2F278D57850A7AD568C24A4D32A3D74B29F03DC125488BC7C637DA582357F40B0A52D16B3B40BB2C2315D03360BC24209E20972C200566BCF3BBE5C5B0AEDD83132A8A4D5B4242BA370B6D67D9B67EB01052D132C7866B9CB502E44796D9D356E4E3CB47CC527322CD24976FE7C9257A2864151A38E568EF7A79F10D6EF27CC04CE382347A2488B1F404FDBF407FE1CA1C9D0D5649E34800E25E18951C98CAE9F43555EEF65FEE1EA8F15828807366C3B612CD5753BF9FB8FCED08855F742CDDD6F765F74254F03186683D646E6F09AC2805586C7CF11998357CAFC5DF3F285329366F475130C928B2DCEBA4AA383758E7A9D20705C4BB9DB619E2992F608A1BA65DB254BB389468741D0502E2588AEB54390AC600C19AF5C8E61383FC1BEBE0029E4474051E4EF908828DB9CCA13277EF65DB3FD47CCC2179126AAEFB627719F421E20").unwrap()); + let hex = "0002EEC7245D6B7D2CCB30380BFBE2A3648CD7A942653F5AA340EDCEA1F283686619F7F3416A5AA36DC7EEB3EC6D421E9615471AB870A33AC07FA5D5A51DF0A8823AABE3FEA3F90D387529D4F72837F9E687230371CCD8D263072206DBED0234F6505E21E282ABD8C0E4F5B9FF8042800BBAB065036EADD0149B37F27DDE664725A49866E052E809D2B0198AB9610FAA656BBF4EC516763A59F8F42C171B179166BA38958D4F51B39B3E98706E2D14A2DAFD6A5DF808093ABFCA5AEAACA16EDED5DB7D21FB0294DD1A163EDF0FB445D5C8D7D688D6DD9C541762BF5A5123BF9939D957FE648416E88F1B0928BFA034982B22548E1A4D922690EECF546275AFB233ACF4323974680779F1A964CFE687456035CC0FBA8A5428430B390F0057B6D1FE9A8875BFA89693EEB838CE59F09D207A503EE6F6299C92D6361BC335FCBF9B5CD44747AADCE2CE6069CFDC3D671DAEF9F8AE590CF93D957C9E873E9A1BC62D9640DC8FC39C14902D49A1C80239B6C5B7FD91D05878CBF5FFC7DB2569F47C43D6C0D27C438ABFF276E87364DEB8858A37E5A62C446AF95D8B786EAF0B5FCF78D98B41496794F8DCAAC4EEF34B2ACFB94C7E8C32A9E9866A8FA0B6F2A06F00A1CCDE569F97EEC05C803BA7500ACC96691D8898D73D8E6A47B8F43C3D5DE74458D20EDA61474C426359677001FBD75A74D7D5DB6CB4FEB83122F133206203E4E2D293F838BF8C8B3A29ACB321315100B87E80E0EDB272EE80FDA944E3FB6084ED4D7F7C7D21C69D9DA43D31A90B70693F9B0CC3EAC74C11AB8FF655905688916CFA4EF0BD04135F2E50B7C689A21D04E8E981E74C6058188B9B1F9DFC3EEC6838E9FFBCF22CE738D8A177C19318DFFEF090CEE67E12DE1A3E2A39F61247547BA5257489CBC11D7D91ED34617FCC42F7A9DA2E3CF31A94A210A1018143173913C38F60E62B24BF0D7518F38B5BAB3E6A1F8AEB35E31D6442C8ABB5178EFC892D2E787D79C6AD9E2FC271792983FA9955AC4D1D84A36C024071BC6E431B625519D556AF38185601F70E29035EA6A09C8B676C9D88CF7E05E0F17098B584C4168735940263F940033A220F40BE4C85344128B14BEB9E75696DB37014107801A59B13E89CD9D2258C169D523BE6D31552C44C82FF4BB18EC9F099F3BF0E5B1BB2BA9A87D7E26F98D294927B600B5529C47E04D98956677CBCEE8FA2B60F49776D8B8C367465B7C626DA53700684FB6C918EAD0EAB8360E4F60EDD25B4F43816A75ECF70F909301825B512469F8389D79402311D8AECB7B3EF8599E79485A4388D87744D899F7C47EE644361E17040A7958C8911BE6F463AB6A9B2AFACD688EC55EF517B38F1339EFC54487232798BB25522FF4572FF68567FE830F92F7B8113EFCE3E98C3FFFBAEDCE4FD8B50E41DA97C0C08E423A72689CC68E68F752A5E3A9003E64E35C957CA2E1C48BB6F64B05F56B70B575AD2F278D57850A7AD568C24A4D32A3D74B29F03DC125488BC7C637DA582357F40B0A52D16B3B40BB2C2315D03360BC24209E20972C200566BCF3BBE5C5B0AEDD83132A8A4D5B4242BA370B6D67D9B67EB01052D132C7866B9CB502E44796D9D356E4E3CB47CC527322CD24976FE7C9257A2864151A38E568EF7A79F10D6EF27CC04CE382347A2488B1F404FDBF407FE1CA1C9D0D5649E34800E25E18951C98CAE9F43555EEF65FEE1EA8F15828807366C3B612CD5753BF9FB8FCED08855F742CDDD6F765F74254F03186683D646E6F09AC2805586C7CF11998357CAFC5DF3F285329366F475130C928B2DCEBA4AA383758E7A9D20705C4BB9DB619E2992F608A1BA65DB254BB389468741D0502E2588AEB54390AC600C19AF5C8E61383FC1BEBE0029E4474051E4EF908828DB9CCA13277EF65DB3FD47CCC2179126AAEFB627719F421E20"; + assert_eq!(packet.encode(), >::from_hex(hex).unwrap()); } #[test] @@ -1245,27 +1474,49 @@ mod tests { // Returning Errors test vectors from BOLT 4 let onion_keys = build_test_onion_keys(); - let onion_error = super::build_failure_packet(onion_keys[4].shared_secret.as_ref(), 0x2002, &[0; 0]); - assert_eq!(onion_error.encode(), >::from_hex("4c2fc8bc08510334b6833ad9c3e79cd1b52ae59dfe5c2a4b23ead50f09f7ee0b0002200200fe0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap()); - - let onion_packet_1 = super::encrypt_failure_packet(onion_keys[4].shared_secret.as_ref(), &onion_error.encode()[..]); - assert_eq!(onion_packet_1.data, >::from_hex("a5e6bd0c74cb347f10cce367f949098f2457d14c046fd8a22cb96efb30b0fdcda8cb9168b50f2fd45edd73c1b0c8b33002df376801ff58aaa94000bf8a86f92620f343baef38a580102395ae3abf9128d1047a0736ff9b83d456740ebbb4aeb3aa9737f18fb4afb4aa074fb26c4d702f42968888550a3bded8c05247e045b866baef0499f079fdaeef6538f31d44deafffdfd3afa2fb4ca9082b8f1c465371a9894dd8c243fb4847e004f5256b3e90e2edde4c9fb3082ddfe4d1e734cacd96ef0706bf63c9984e22dc98851bcccd1c3494351feb458c9c6af41c0044bea3c47552b1d992ae542b17a2d0bba1a096c78d169034ecb55b6e3a7263c26017f033031228833c1daefc0dedb8cf7c3e37c9c37ebfe42f3225c326e8bcfd338804c145b16e34e4").unwrap()); + let onion_error = + super::build_failure_packet(onion_keys[4].shared_secret.as_ref(), 0x2002, &[0; 0]); + let hex = "4c2fc8bc08510334b6833ad9c3e79cd1b52ae59dfe5c2a4b23ead50f09f7ee0b0002200200fe0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; + assert_eq!(onion_error.encode(), >::from_hex(hex).unwrap()); + + let onion_packet_1 = super::encrypt_failure_packet( + onion_keys[4].shared_secret.as_ref(), + &onion_error.encode()[..], + ); + let hex = "a5e6bd0c74cb347f10cce367f949098f2457d14c046fd8a22cb96efb30b0fdcda8cb9168b50f2fd45edd73c1b0c8b33002df376801ff58aaa94000bf8a86f92620f343baef38a580102395ae3abf9128d1047a0736ff9b83d456740ebbb4aeb3aa9737f18fb4afb4aa074fb26c4d702f42968888550a3bded8c05247e045b866baef0499f079fdaeef6538f31d44deafffdfd3afa2fb4ca9082b8f1c465371a9894dd8c243fb4847e004f5256b3e90e2edde4c9fb3082ddfe4d1e734cacd96ef0706bf63c9984e22dc98851bcccd1c3494351feb458c9c6af41c0044bea3c47552b1d992ae542b17a2d0bba1a096c78d169034ecb55b6e3a7263c26017f033031228833c1daefc0dedb8cf7c3e37c9c37ebfe42f3225c326e8bcfd338804c145b16e34e4"; + assert_eq!(onion_packet_1.data, >::from_hex(hex).unwrap()); - let onion_packet_2 = super::encrypt_failure_packet(onion_keys[3].shared_secret.as_ref(), &onion_packet_1.data[..]); - assert_eq!(onion_packet_2.data, >::from_hex("c49a1ce81680f78f5f2000cda36268de34a3f0a0662f55b4e837c83a8773c22aa081bab1616a0011585323930fa5b9fae0c85770a2279ff59ec427ad1bbff9001c0cd1497004bd2a0f68b50704cf6d6a4bf3c8b6a0833399a24b3456961ba00736785112594f65b6b2d44d9f5ea4e49b5e1ec2af978cbe31c67114440ac51a62081df0ed46d4a3df295da0b0fe25c0115019f03f15ec86fabb4c852f83449e812f141a9395b3f70b766ebbd4ec2fae2b6955bd8f32684c15abfe8fd3a6261e52650e8807a92158d9f1463261a925e4bfba44bd20b166d532f0017185c3a6ac7957adefe45559e3072c8dc35abeba835a8cb01a71a15c736911126f27d46a36168ca5ef7dccd4e2886212602b181463e0dd30185c96348f9743a02aca8ec27c0b90dca270").unwrap()); + let onion_packet_2 = super::encrypt_failure_packet( + onion_keys[3].shared_secret.as_ref(), + &onion_packet_1.data[..], + ); + let hex = "c49a1ce81680f78f5f2000cda36268de34a3f0a0662f55b4e837c83a8773c22aa081bab1616a0011585323930fa5b9fae0c85770a2279ff59ec427ad1bbff9001c0cd1497004bd2a0f68b50704cf6d6a4bf3c8b6a0833399a24b3456961ba00736785112594f65b6b2d44d9f5ea4e49b5e1ec2af978cbe31c67114440ac51a62081df0ed46d4a3df295da0b0fe25c0115019f03f15ec86fabb4c852f83449e812f141a9395b3f70b766ebbd4ec2fae2b6955bd8f32684c15abfe8fd3a6261e52650e8807a92158d9f1463261a925e4bfba44bd20b166d532f0017185c3a6ac7957adefe45559e3072c8dc35abeba835a8cb01a71a15c736911126f27d46a36168ca5ef7dccd4e2886212602b181463e0dd30185c96348f9743a02aca8ec27c0b90dca270"; + assert_eq!(onion_packet_2.data, >::from_hex(hex).unwrap()); - let onion_packet_3 = super::encrypt_failure_packet(onion_keys[2].shared_secret.as_ref(), &onion_packet_2.data[..]); - assert_eq!(onion_packet_3.data, >::from_hex("a5d3e8634cfe78b2307d87c6d90be6fe7855b4f2cc9b1dfb19e92e4b79103f61ff9ac25f412ddfb7466e74f81b3e545563cdd8f5524dae873de61d7bdfccd496af2584930d2b566b4f8d3881f8c043df92224f38cf094cfc09d92655989531524593ec6d6caec1863bdfaa79229b5020acc034cd6deeea1021c50586947b9b8e6faa83b81fbfa6133c0af5d6b07c017f7158fa94f0d206baf12dda6b68f785b773b360fd0497e16cc402d779c8d48d0fa6315536ef0660f3f4e1865f5b38ea49c7da4fd959de4e83ff3ab686f059a45c65ba2af4a6a79166aa0f496bf04d06987b6d2ea205bdb0d347718b9aeff5b61dfff344993a275b79717cd815b6ad4c0beb568c4ac9c36ff1c315ec1119a1993c4b61e6eaa0375e0aaf738ac691abd3263bf937e3").unwrap()); + let onion_packet_3 = super::encrypt_failure_packet( + onion_keys[2].shared_secret.as_ref(), + &onion_packet_2.data[..], + ); + let hex = "a5d3e8634cfe78b2307d87c6d90be6fe7855b4f2cc9b1dfb19e92e4b79103f61ff9ac25f412ddfb7466e74f81b3e545563cdd8f5524dae873de61d7bdfccd496af2584930d2b566b4f8d3881f8c043df92224f38cf094cfc09d92655989531524593ec6d6caec1863bdfaa79229b5020acc034cd6deeea1021c50586947b9b8e6faa83b81fbfa6133c0af5d6b07c017f7158fa94f0d206baf12dda6b68f785b773b360fd0497e16cc402d779c8d48d0fa6315536ef0660f3f4e1865f5b38ea49c7da4fd959de4e83ff3ab686f059a45c65ba2af4a6a79166aa0f496bf04d06987b6d2ea205bdb0d347718b9aeff5b61dfff344993a275b79717cd815b6ad4c0beb568c4ac9c36ff1c315ec1119a1993c4b61e6eaa0375e0aaf738ac691abd3263bf937e3"; + assert_eq!(onion_packet_3.data, >::from_hex(hex).unwrap()); - let onion_packet_4 = super::encrypt_failure_packet(onion_keys[1].shared_secret.as_ref(), &onion_packet_3.data[..]); - assert_eq!(onion_packet_4.data, >::from_hex("aac3200c4968f56b21f53e5e374e3a2383ad2b1b6501bbcc45abc31e59b26881b7dfadbb56ec8dae8857add94e6702fb4c3a4de22e2e669e1ed926b04447fc73034bb730f4932acd62727b75348a648a1128744657ca6a4e713b9b646c3ca66cac02cdab44dd3439890ef3aaf61708714f7375349b8da541b2548d452d84de7084bb95b3ac2345201d624d31f4d52078aa0fa05a88b4e20202bd2b86ac5b52919ea305a8949de95e935eed0319cf3cf19ebea61d76ba92532497fcdc9411d06bcd4275094d0a4a3c5d3a945e43305a5a9256e333e1f64dbca5fcd4e03a39b9012d197506e06f29339dfee3331995b21615337ae060233d39befea925cc262873e0530408e6990f1cbd233a150ef7b004ff6166c70c68d9f8c853c1abca640b8660db2921").unwrap()); + let onion_packet_4 = super::encrypt_failure_packet( + onion_keys[1].shared_secret.as_ref(), + &onion_packet_3.data[..], + ); + let hex = "aac3200c4968f56b21f53e5e374e3a2383ad2b1b6501bbcc45abc31e59b26881b7dfadbb56ec8dae8857add94e6702fb4c3a4de22e2e669e1ed926b04447fc73034bb730f4932acd62727b75348a648a1128744657ca6a4e713b9b646c3ca66cac02cdab44dd3439890ef3aaf61708714f7375349b8da541b2548d452d84de7084bb95b3ac2345201d624d31f4d52078aa0fa05a88b4e20202bd2b86ac5b52919ea305a8949de95e935eed0319cf3cf19ebea61d76ba92532497fcdc9411d06bcd4275094d0a4a3c5d3a945e43305a5a9256e333e1f64dbca5fcd4e03a39b9012d197506e06f29339dfee3331995b21615337ae060233d39befea925cc262873e0530408e6990f1cbd233a150ef7b004ff6166c70c68d9f8c853c1abca640b8660db2921"; + assert_eq!(onion_packet_4.data, >::from_hex(hex).unwrap()); - let onion_packet_5 = super::encrypt_failure_packet(onion_keys[0].shared_secret.as_ref(), &onion_packet_4.data[..]); - assert_eq!(onion_packet_5.data, >::from_hex("9c5add3963fc7f6ed7f148623c84134b5647e1306419dbe2174e523fa9e2fbed3a06a19f899145610741c83ad40b7712aefaddec8c6baf7325d92ea4ca4d1df8bce517f7e54554608bf2bd8071a4f52a7a2f7ffbb1413edad81eeea5785aa9d990f2865dc23b4bc3c301a94eec4eabebca66be5cf638f693ec256aec514620cc28ee4a94bd9565bc4d4962b9d3641d4278fb319ed2b84de5b665f307a2db0f7fbb757366067d88c50f7e829138fde4f78d39b5b5802f1b92a8a820865af5cc79f9f30bc3f461c66af95d13e5e1f0381c184572a91dee1c849048a647a1158cf884064deddbf1b0b88dfe2f791428d0ba0f6fb2f04e14081f69165ae66d9297c118f0907705c9c4954a199bae0bb96fad763d690e7daa6cfda59ba7f2c8d11448b604d12d").unwrap()); + let onion_packet_5 = super::encrypt_failure_packet( + onion_keys[0].shared_secret.as_ref(), + &onion_packet_4.data[..], + ); + let hex = "9c5add3963fc7f6ed7f148623c84134b5647e1306419dbe2174e523fa9e2fbed3a06a19f899145610741c83ad40b7712aefaddec8c6baf7325d92ea4ca4d1df8bce517f7e54554608bf2bd8071a4f52a7a2f7ffbb1413edad81eeea5785aa9d990f2865dc23b4bc3c301a94eec4eabebca66be5cf638f693ec256aec514620cc28ee4a94bd9565bc4d4962b9d3641d4278fb319ed2b84de5b665f307a2db0f7fbb757366067d88c50f7e829138fde4f78d39b5b5802f1b92a8a820865af5cc79f9f30bc3f461c66af95d13e5e1f0381c184572a91dee1c849048a647a1158cf884064deddbf1b0b88dfe2f791428d0ba0f6fb2f04e14081f69165ae66d9297c118f0907705c9c4954a199bae0bb96fad763d690e7daa6cfda59ba7f2c8d11448b604d12d"; + assert_eq!(onion_packet_5.data, >::from_hex(hex).unwrap()); } struct RawOnionHopData { - data: Vec + data: Vec, } impl RawOnionHopData { fn new(orig: msgs::OutboundOnionPayload) -> Self { diff --git a/lightning/src/ln/outbound_payment.rs b/lightning/src/ln/outbound_payment.rs index e1748dca7f2..b05d6f3f729 100644 --- a/lightning/src/ln/outbound_payment.rs +++ b/lightning/src/ln/outbound_payment.rs @@ -19,11 +19,11 @@ use crate::ln::{PaymentHash, PaymentPreimage, PaymentSecret}; 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::routing::router::{BlindedTail, InFlightHtlcs, Path, PaymentParameters, Route, RouteParameters, Router}; use crate::util::errors::APIError; use crate::util::logger::Logger; use crate::util::time::Time; -#[cfg(all(not(feature = "no-std"), test))] +#[cfg(all(feature = "std", test))] use crate::util::time::tests::SinceEpoch; use crate::util::ser::ReadableArgs; @@ -129,6 +129,11 @@ impl PendingOutboundPayment { params.previously_failed_channels.push(scid); } } + pub fn insert_previously_failed_blinded_path(&mut self, blinded_tail: &BlindedTail) { + if let PendingOutboundPayment::Retryable { payment_params: Some(params), .. } = self { + params.insert_previously_failed_blinded_path(blinded_tail); + } + } fn is_awaiting_invoice(&self) -> bool { match self { PendingOutboundPayment::AwaitingInvoice { .. } => true, @@ -166,7 +171,7 @@ impl PendingOutboundPayment { } fn mark_fulfilled(&mut self) { - let mut session_privs = HashSet::new(); + let mut session_privs = new_hash_set(); core::mem::swap(&mut session_privs, match self { PendingOutboundPayment::Legacy { session_privs } | PendingOutboundPayment::Retryable { session_privs, .. } | @@ -181,7 +186,7 @@ impl PendingOutboundPayment { fn mark_abandoned(&mut self, reason: PaymentFailureReason) { if let PendingOutboundPayment::Retryable { session_privs, payment_hash, .. } = self { - let mut our_session_privs = HashSet::new(); + let mut our_session_privs = new_hash_set(); core::mem::swap(&mut our_session_privs, session_privs); *self = PendingOutboundPayment::Abandoned { session_privs: our_session_privs, @@ -190,7 +195,7 @@ impl PendingOutboundPayment { }; } else if let PendingOutboundPayment::InvoiceReceived { payment_hash, .. } = self { *self = PendingOutboundPayment::Abandoned { - session_privs: HashSet::new(), + session_privs: new_hash_set(), payment_hash: *payment_hash, reason: Some(reason) }; @@ -243,7 +248,7 @@ impl PendingOutboundPayment { if insert_res { if let PendingOutboundPayment::Retryable { ref mut pending_amt_msat, ref mut pending_fee_msat, - ref mut remaining_max_total_routing_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(); @@ -282,7 +287,7 @@ pub enum Retry { /// 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(u32), - #[cfg(not(feature = "no-std"))] + #[cfg(feature = "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. /// @@ -290,13 +295,13 @@ pub enum Retry { Timeout(core::time::Duration), } -#[cfg(feature = "no-std")] +#[cfg(not(feature = "std"))] impl_writeable_tlv_based_enum!(Retry, ; (0, Attempts) ); -#[cfg(not(feature = "no-std"))] +#[cfg(feature = "std")] impl_writeable_tlv_based_enum!(Retry, ; (0, Attempts), @@ -309,10 +314,10 @@ impl Retry { (Retry::Attempts(max_retry_count), PaymentAttempts { count, .. }) => { max_retry_count > count }, - #[cfg(all(not(feature = "no-std"), not(test)))] + #[cfg(all(feature = "std", not(test)))] (Retry::Timeout(max_duration), PaymentAttempts { first_attempted_at, .. }) => *max_duration >= crate::util::time::MonotonicTime::now().duration_since(*first_attempted_at), - #[cfg(all(not(feature = "no-std"), test))] + #[cfg(all(feature = "std", test))] (Retry::Timeout(max_duration), PaymentAttempts { first_attempted_at, .. }) => *max_duration >= SinceEpoch::now().duration_since(*first_attempted_at), } @@ -338,27 +343,27 @@ pub(crate) struct PaymentAttemptsUsingTime { /// it means the result of the first attempt is not known yet. 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"))] + #[cfg(feature = "std")] first_attempted_at: T, - #[cfg(feature = "no-std")] + #[cfg(not(feature = "std"))] phantom: core::marker::PhantomData, } -#[cfg(not(any(feature = "no-std", test)))] -type ConfiguredTime = crate::util::time::MonotonicTime; -#[cfg(feature = "no-std")] +#[cfg(not(feature = "std"))] type ConfiguredTime = crate::util::time::Eternity; -#[cfg(all(not(feature = "no-std"), test))] +#[cfg(all(feature = "std", not(test)))] +type ConfiguredTime = crate::util::time::MonotonicTime; +#[cfg(all(feature = "std", test))] type ConfiguredTime = SinceEpoch; impl PaymentAttemptsUsingTime { pub(crate) fn new() -> Self { PaymentAttemptsUsingTime { count: 0, - #[cfg(not(feature = "no-std"))] + #[cfg(feature = "std")] first_attempted_at: T::now(), - #[cfg(feature = "no-std")] + #[cfg(not(feature = "std"))] phantom: core::marker::PhantomData, } } @@ -366,9 +371,9 @@ impl PaymentAttemptsUsingTime { impl Display for PaymentAttemptsUsingTime { fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { - #[cfg(feature = "no-std")] + #[cfg(not(feature = "std"))] return write!(f, "attempts: {}", self.count); - #[cfg(not(feature = "no-std"))] + #[cfg(feature = "std")] return write!( f, "attempts: {}, duration: {}s", @@ -670,7 +675,7 @@ pub(super) struct OutboundPayments { impl OutboundPayments { pub(super) fn new() -> Self { Self { - pending_outbound_payments: Mutex::new(HashMap::new()), + pending_outbound_payments: Mutex::new(new_hash_map()), retry_lock: Mutex::new(()), } } @@ -1263,7 +1268,7 @@ impl OutboundPayments { retry_strategy, attempts: PaymentAttempts::new(), payment_params, - session_privs: HashSet::new(), + session_privs: new_hash_set(), pending_amt_msat: 0, pending_fee_msat: Some(0), payment_hash, @@ -1604,11 +1609,12 @@ impl OutboundPayments { #[cfg(test)] let DecodedOnionFailure { network_update, short_channel_id, payment_failed_permanently, onion_error_code, - onion_error_data + onion_error_data, failed_within_blinded_path } = onion_error.decode_onion_failure(secp_ctx, logger, &source); #[cfg(not(test))] - let DecodedOnionFailure { network_update, short_channel_id, payment_failed_permanently } = - onion_error.decode_onion_failure(secp_ctx, logger, &source); + let DecodedOnionFailure { + network_update, short_channel_id, payment_failed_permanently, failed_within_blinded_path + } = 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]; @@ -1647,6 +1653,12 @@ impl OutboundPayments { // next-hop is needlessly blaming us! payment.get_mut().insert_previously_failed_scid(scid); } + if failed_within_blinded_path { + debug_assert!(short_channel_id.is_none()); + if let Some(bt) = &path.blinded_tail { + payment.get_mut().insert_previously_failed_blinded_path(&bt); + } else { debug_assert!(false); } + } if payment_is_probe || !is_retryable_now || payment_failed_permanently { let reason = if payment_failed_permanently { @@ -1876,7 +1888,7 @@ mod tests { 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 router = test_utils::TestRouter::new(network_graph, &logger, &scorer); let secp_ctx = Secp256k1::new(); let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet); @@ -1920,7 +1932,7 @@ mod tests { 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 router = test_utils::TestRouter::new(network_graph, &logger, &scorer); let secp_ctx = Secp256k1::new(); let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet); @@ -1959,7 +1971,7 @@ mod tests { 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 router = test_utils::TestRouter::new(network_graph, &logger, &scorer); let secp_ctx = Secp256k1::new(); let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet); @@ -2166,7 +2178,7 @@ mod tests { 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 router = test_utils::TestRouter::new(network_graph, &logger, &scorer); let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet); let pending_events = Mutex::new(VecDeque::new()); @@ -2217,7 +2229,7 @@ mod tests { 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 router = test_utils::TestRouter::new(network_graph, &logger, &scorer); let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet); let pending_events = Mutex::new(VecDeque::new()); @@ -2271,71 +2283,12 @@ mod tests { 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 expiration = StaleExpiration::AbsoluteTimeout(Duration::from_secs(100)); - - 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, expiration, 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 router = test_utils::TestRouter::new(network_graph, &logger, &scorer); let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet); let pending_events = Mutex::new(VecDeque::new()); diff --git a/lightning/src/ln/payment_tests.rs b/lightning/src/ln/payment_tests.rs index 01f59528989..09c022a4c87 100644 --- a/lightning/src/ln/payment_tests.rs +++ b/lightning/src/ln/payment_tests.rs @@ -42,10 +42,11 @@ use crate::prelude::*; use crate::ln::functional_test_utils; use crate::ln::functional_test_utils::*; use crate::routing::gossip::NodeId; + #[cfg(feature = "std")] use { crate::util::time::tests::SinceEpoch, - std::time::{SystemTime, Instant, Duration} + std::time::{SystemTime, Instant, Duration}, }; #[test] @@ -276,10 +277,12 @@ fn mpp_retry_overpay() { // Can't use claim_payment_along_route as it doesn't support overpayment, so we break out the // individual steps here. + nodes[3].node.claim_funds(payment_preimage); 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); + let expected_route = &[&[&nodes[1], &nodes[3]][..], &[&nodes[2], &nodes[3]][..]]; + let args = ClaimAlongRouteArgs::new(&nodes[0], &expected_route[..], payment_preimage) + .with_expected_min_htlc_overpay(extra_fees); + let expected_total_fee_msat = pass_claimed_payment_along_route(args); expect_payment_sent!(&nodes[0], payment_preimage, Some(expected_total_fee_msat)); } @@ -638,7 +641,7 @@ fn do_retry_with_no_persist(confirm_before_reload: bool) { 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, funding_tx) = create_announced_chan_between_nodes(&nodes, 0, 1); let (_, _, chan_id_2, _) = create_announced_chan_between_nodes(&nodes, 1, 2); // Serialize the ChannelManager prior to sending payments @@ -750,14 +753,21 @@ fn do_retry_with_no_persist(confirm_before_reload: bool) { assert_eq!(nodes[0].node.list_usable_channels().len(), 1); mine_transaction(&nodes[1], &as_commitment_tx); - 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_commitment_tx); + let bs_htlc_claim_txn = { + let mut txn = nodes[1].tx_broadcaster.unique_txn_broadcast(); + assert_eq!(txn.len(), 2); + check_spends!(txn[0], funding_tx); + check_spends!(txn[1], as_commitment_tx); + txn.pop().unwrap() + }; if !confirm_before_reload { mine_transaction(&nodes[0], &as_commitment_tx); + let txn = nodes[0].tx_broadcaster.unique_txn_broadcast(); + assert_eq!(txn.len(), 1); + assert_eq!(txn[0].txid(), as_commitment_tx.txid()); } - mine_transaction(&nodes[0], &bs_htlc_claim_txn[0]); + mine_transaction(&nodes[0], &bs_htlc_claim_txn); 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) = { @@ -767,7 +777,7 @@ fn do_retry_with_no_persist(confirm_before_reload: bool) { }; check_spends!(first_htlc_timeout_tx, as_commitment_tx); check_spends!(second_htlc_timeout_tx, as_commitment_tx); - if first_htlc_timeout_tx.input[0].previous_output == bs_htlc_claim_txn[0].input[0].previous_output { + if first_htlc_timeout_tx.input[0].previous_output == bs_htlc_claim_txn.input[0].previous_output { confirm_transaction(&nodes[0], &second_htlc_timeout_tx); } else { confirm_transaction(&nodes[0], &first_htlc_timeout_tx); @@ -919,19 +929,23 @@ fn do_test_completed_payment_not_retryable_on_reload(use_dust: bool) { // the HTLC-Timeout transaction beyond 1 conf). For dust HTLCs, the HTLC is considered resolved // after the commitment transaction, so always connect the commitment transaction. mine_transaction(&nodes[0], &bs_commitment_tx[0]); + if nodes[0].connect_style.borrow().updates_best_block_first() { + let _ = nodes[0].tx_broadcaster.txn_broadcast(); + } mine_transaction(&nodes[1], &bs_commitment_tx[0]); if !use_dust { connect_blocks(&nodes[0], TEST_FINAL_CLTV + (MIN_CLTV_EXPIRY_DELTA as u32)); connect_blocks(&nodes[1], TEST_FINAL_CLTV + (MIN_CLTV_EXPIRY_DELTA as u32)); let as_htlc_timeout = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0); - check_spends!(as_htlc_timeout[0], bs_commitment_tx[0]); assert_eq!(as_htlc_timeout.len(), 1); + check_spends!(as_htlc_timeout[0], bs_commitment_tx[0]); mine_transaction(&nodes[0], &as_htlc_timeout[0]); - // nodes[0] may rebroadcast (or RBF-bump) its HTLC-Timeout, so wipe the announced set. - nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().clear(); mine_transaction(&nodes[1], &as_htlc_timeout[0]); } + if nodes[0].connect_style.borrow().updates_best_block_first() { + let _ = nodes[0].tx_broadcaster.txn_broadcast(); + } // Create a new channel on which to retry the payment before we fail the payment via the // HTLC-Timeout transaction. This avoids ChannelManager timing out the payment due to us @@ -1049,32 +1063,36 @@ fn do_test_dup_htlc_onchain_fails_on_reload(persist_manager_post_event: bool, co // Connect blocks until the CLTV timeout is up so that we get an HTLC-Timeout transaction connect_blocks(&nodes[0], TEST_FINAL_CLTV + LATENCY_GRACE_PERIOD_BLOCKS + 1); - let node_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0); - assert_eq!(node_txn.len(), 3); - assert_eq!(node_txn[0].txid(), node_txn[1].txid()); - check_spends!(node_txn[1], funding_tx); - check_spends!(node_txn[2], node_txn[1]); - let timeout_txn = vec![node_txn[2].clone()]; + let (commitment_tx, htlc_timeout_tx) = { + let mut txn = nodes[0].tx_broadcaster.unique_txn_broadcast(); + assert_eq!(txn.len(), 2); + check_spends!(txn[0], funding_tx); + check_spends!(txn[1], txn[0]); + (txn.remove(0), txn.remove(0)) + }; nodes[1].node.claim_funds(payment_preimage); check_added_monitors!(nodes[1], 1); expect_payment_claimed!(nodes[1], payment_hash, 10_000_000); - connect_block(&nodes[1], &create_dummy_block(nodes[1].best_block_hash(), 42, vec![node_txn[1].clone()])); + mine_transaction(&nodes[1], &commitment_tx); check_closed_broadcast!(nodes[1], true); check_added_monitors!(nodes[1], 1); check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [nodes[0].node.get_our_node_id()], 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]); + let htlc_success_tx = { + let mut txn = nodes[1].tx_broadcaster.txn_broadcast(); + assert_eq!(txn.len(), 1); + check_spends!(txn[0], commitment_tx); + txn.pop().unwrap() + }; - connect_block(&nodes[0], &create_dummy_block(nodes[0].best_block_hash(), 42, vec![node_txn[1].clone()])); + mine_transaction(&nodes[0], &commitment_tx); if confirm_commitment_tx { connect_blocks(&nodes[0], BREAKDOWN_TIMEOUT as u32 - 1); } - let claim_block = create_dummy_block(nodes[0].best_block_hash(), 42, if payment_timeout { timeout_txn } else { vec![claim_txn[0].clone()] }); + let claim_block = create_dummy_block(nodes[0].best_block_hash(), 42, if payment_timeout { vec![htlc_timeout_tx] } else { vec![htlc_success_tx] }); if payment_timeout { assert!(confirm_commitment_tx); // Otherwise we're spending below our CSV! @@ -2139,9 +2157,10 @@ fn do_accept_underpaying_htlcs_config(num_mpp_parts: usize) { let mut expected_paths = Vec::new(); for _ in 0..num_mpp_parts { expected_paths_vecs.push(vec!(&nodes[1], &nodes[2])); } for i in 0..num_mpp_parts { expected_paths.push(&expected_paths_vecs[i][..]); } - let total_fee_msat = do_claim_payment_along_route_with_extra_penultimate_hop_fees( - &nodes[0], &expected_paths[..], &vec![skimmed_fee_msat as u32; num_mpp_parts][..], false, - payment_preimage); + expected_paths[0].last().unwrap().node.claim_funds(payment_preimage); + let args = ClaimAlongRouteArgs::new(&nodes[0], &expected_paths[..], payment_preimage) + .with_expected_extra_fees(vec![skimmed_fee_msat as u32; num_mpp_parts]); + let total_fee_msat = pass_claimed_payment_along_route(args); // 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, true); @@ -2299,7 +2318,7 @@ fn do_automatic_retries(test: AutoRetry) { let mut msg_events = nodes[0].node.get_and_clear_pending_msg_events(); assert_eq!(msg_events.len(), 0); } else if test == AutoRetry::FailTimeout { - #[cfg(not(feature = "no-std"))] { + #[cfg(feature = "std")] { // Ensure ChannelManager will not retry a payment if it times out due to Retry::Timeout. nodes[0].node.send_payment(payment_hash, RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_hash.0), route_params, Retry::Timeout(Duration::from_secs(60))).unwrap(); @@ -3339,6 +3358,7 @@ fn test_threaded_payment_retries() { // We really want std::thread::scope, but its not stable until 1.63. Until then, we get unsafe. let node_ref = NodePtr::from_node(&nodes[0]); move || { + let _ = &node_ref; let node_a = unsafe { &*node_ref.0 }; while Instant::now() < end_time { node_a.node.get_and_clear_pending_events(); // wipe the PendingHTLCsForwardable @@ -3705,7 +3725,7 @@ fn do_test_custom_tlvs(spontaneous: bool, even_tlvs: bool, known_tlvs: bool) { 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); + let expected_total_fee_msat = pass_claimed_payment_along_route(ClaimAlongRouteArgs::new(&nodes[0], &[&[&nodes[1]]], our_payment_preimage)); expect_payment_sent!(&nodes[0], our_payment_preimage, Some(expected_total_fee_msat)); }, (false, false) => { diff --git a/lightning/src/ln/peer_channel_encryptor.rs b/lightning/src/ln/peer_channel_encryptor.rs index c9e1ac47886..51e34bdb969 100644 --- a/lightning/src/ln/peer_channel_encryptor.rs +++ b/lightning/src/ln/peer_channel_encryptor.rs @@ -24,8 +24,8 @@ use bitcoin::secp256k1; use hex::DisplayHex; -use crate::util::chacha20poly1305rfc::ChaCha20Poly1305RFC; -use crate::util::crypto::hkdf_extract_expand_twice; +use crate::crypto::chacha20poly1305rfc::ChaCha20Poly1305RFC; +use crate::crypto::utils::hkdf_extract_expand_twice; use crate::util::ser::VecWriter; use core::ops::Deref; @@ -188,7 +188,7 @@ impl PeerChannelEncryptor { nonce[4..].copy_from_slice(&n.to_le_bytes()[..]); let mut chacha = ChaCha20Poly1305RFC::new(key, &nonce, h); - if !chacha.decrypt(&cyphertext[0..cyphertext.len() - 16], res, &cyphertext[cyphertext.len() - 16..]) { + if chacha.variable_time_decrypt(&cyphertext[0..cyphertext.len() - 16], res, &cyphertext[cyphertext.len() - 16..]).is_err() { return Err(LightningError{err: "Bad MAC".to_owned(), action: msgs::ErrorAction::DisconnectPeer{ msg: None }}); } Ok(()) diff --git a/lightning/src/ln/peer_handler.rs b/lightning/src/ln/peer_handler.rs index 6e0823b48bb..9b32f1eb390 100644 --- a/lightning/src/ln/peer_handler.rs +++ b/lightning/src/ln/peer_handler.rs @@ -18,35 +18,42 @@ use bitcoin::blockdata::constants::ChainHash; use bitcoin::secp256k1::{self, Secp256k1, SecretKey, PublicKey}; -use crate::sign::{KeysManager, NodeSigner, Recipient}; +use crate::sign::{NodeSigner, Recipient}; use crate::events::{EventHandler, EventsProvider, MessageSendEvent, MessageSendEventsProvider}; use crate::ln::ChannelId; use crate::ln::features::{InitFeatures, NodeFeatures}; use crate::ln::msgs; use crate::ln::msgs::{ChannelMessageHandler, LightningError, SocketAddress, OnionMessageHandler, RoutingMessageHandler}; -#[cfg(not(c_bindings))] -use crate::ln::channelmanager::{SimpleArcChannelManager, SimpleRefChannelManager}; use crate::util::ser::{VecWriter, Writeable, Writer}; use crate::ln::peer_channel_encryptor::{PeerChannelEncryptor, NextNoiseStep, MessageBuf, MSG_BUF_ALLOC_SIZE}; use crate::ln::wire; use crate::ln::wire::{Encode, Type}; -#[cfg(not(c_bindings))] -use crate::onion_message::{SimpleArcOnionMessenger, SimpleRefOnionMessenger}; -use crate::onion_message::{CustomOnionMessageHandler, OffersMessage, OffersMessageHandler, OnionMessageContents, PendingOnionMessage}; -use crate::routing::gossip::{NetworkGraph, P2PGossipSync, NodeId, NodeAlias}; +use crate::onion_message::messenger::{CustomOnionMessageHandler, PendingOnionMessage}; +use crate::onion_message::offers::{OffersMessage, OffersMessageHandler}; +use crate::onion_message::packet::OnionMessageContents; +use crate::routing::gossip::{NodeId, NodeAlias}; use crate::util::atomic_counter::AtomicCounter; -use crate::util::logger::{Logger, WithContext}; +use crate::util::logger::{Level, Logger, WithContext}; use crate::util::string::PrintableString; use crate::prelude::*; use crate::io; use alloc::collections::VecDeque; -use crate::sync::{Arc, Mutex, MutexGuard, FairRwLock}; +use crate::sync::{Mutex, MutexGuard, FairRwLock}; use core::sync::atomic::{AtomicBool, AtomicU32, AtomicI32, Ordering}; use core::{cmp, hash, fmt, mem}; use core::ops::Deref; use core::convert::Infallible; -#[cfg(feature = "std")] use std::error; +#[cfg(feature = "std")] +use std::error; +#[cfg(not(c_bindings))] +use { + crate::ln::channelmanager::{SimpleArcChannelManager, SimpleRefChannelManager}, + crate::onion_message::messenger::{SimpleArcOnionMessenger, SimpleRefOnionMessenger}, + crate::routing::gossip::{NetworkGraph, P2PGossipSync}, + crate::sign::KeysManager, + crate::sync::Arc, +}; use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::hashes::sha256::HashEngine as Sha256Engine; @@ -215,10 +222,10 @@ impl ChannelMessageHandler for ErroringMessageHandler { // Any messages which are related to a specific channel generate an error message to let the // peer know we don't care about channels. fn handle_open_channel(&self, their_node_id: &PublicKey, msg: &msgs::OpenChannel) { - ErroringMessageHandler::push_error(self, their_node_id, msg.temporary_channel_id); + ErroringMessageHandler::push_error(self, their_node_id, msg.common_fields.temporary_channel_id); } fn handle_accept_channel(&self, their_node_id: &PublicKey, msg: &msgs::AcceptChannel) { - ErroringMessageHandler::push_error(self, their_node_id, msg.temporary_channel_id); + ErroringMessageHandler::push_error(self, their_node_id, msg.common_fields.temporary_channel_id); } fn handle_funding_created(&self, their_node_id: &PublicKey, msg: &msgs::FundingCreated) { ErroringMessageHandler::push_error(self, their_node_id, msg.temporary_channel_id); @@ -300,6 +307,7 @@ impl ChannelMessageHandler for ErroringMessageHandler { features.set_channel_type_optional(); features.set_scid_privacy_optional(); features.set_zero_conf_optional(); + features.set_route_blinding_optional(); features } @@ -312,12 +320,12 @@ impl ChannelMessageHandler for ErroringMessageHandler { #[cfg(dual_funding)] fn handle_open_channel_v2(&self, their_node_id: &PublicKey, msg: &msgs::OpenChannelV2) { - ErroringMessageHandler::push_error(self, their_node_id, msg.temporary_channel_id); + ErroringMessageHandler::push_error(self, their_node_id, msg.common_fields.temporary_channel_id); } #[cfg(dual_funding)] fn handle_accept_channel_v2(&self, their_node_id: &PublicKey, msg: &msgs::AcceptChannelV2) { - ErroringMessageHandler::push_error(self, their_node_id, msg.temporary_channel_id); + ErroringMessageHandler::push_error(self, their_node_id, msg.common_fields.temporary_channel_id); } #[cfg(dual_funding)] @@ -392,7 +400,7 @@ pub struct MessageHandler where /// A message handler which handles onion messages. This should generally be an /// [`OnionMessenger`], but can also be an [`IgnoringMessageHandler`]. /// - /// [`OnionMessenger`]: crate::onion_message::OnionMessenger + /// [`OnionMessenger`]: crate::onion_message::messenger::OnionMessenger pub onion_message_handler: OM, /// A message handler which handles custom messages. The only LDK-provided implementation is @@ -438,6 +446,26 @@ pub trait SocketDescriptor : cmp::Eq + hash::Hash + Clone { fn disconnect_socket(&mut self); } +/// Details of a connected peer as returned by [`PeerManager::list_peers`]. +pub struct PeerDetails { + /// The node id of the peer. + /// + /// For outbound connections, this [`PublicKey`] will be the same as the `their_node_id` parameter + /// passed in to [`PeerManager::new_outbound_connection`]. + pub counterparty_node_id: PublicKey, + /// The socket address the peer provided in the initial handshake. + /// + /// Will only be `Some` if an address had been previously provided to + /// [`PeerManager::new_outbound_connection`] or [`PeerManager::new_inbound_connection`]. + pub socket_address: Option, + /// The features the peer provided in the initial handshake. + pub init_features: InitFeatures, + /// Indicates the direction of the peer connection. + /// + /// Will be `true` for inbound connections, and `false` for outbound connections. + pub is_inbound_connection: bool, +} + /// Error for PeerManager errors. If you get one of these, you must disconnect the socket and /// generate no further read_event/write_buffer_space_avail/socket_disconnected calls for the /// descriptor. @@ -951,8 +979,8 @@ impl Vec<(PublicKey, Option)> { + /// Returns a list of [`PeerDetails`] for connected peers that have completed the initial + /// handshake. + pub fn list_peers(&self) -> Vec { 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_socket_address.clone())) + let details = PeerDetails { + // unwrap safety: their_node_id is guaranteed to be `Some` after the handshake + // completed. + counterparty_node_id: p.their_node_id.unwrap().0, + socket_address: p.their_socket_address.clone(), + // unwrap safety: their_features is guaranteed to be `Some` after the handshake + // completed. + init_features: p.their_features.clone().unwrap(), + is_inbound_connection: p.inbound_connection, + }; + Some(details) }).collect() } + /// Returns the [`PeerDetails`] of a connected peer that has completed the initial handshake. + /// + /// Will return `None` if the peer is unknown or it hasn't completed the initial handshake. + pub fn peer_by_node_id(&self, their_node_id: &PublicKey) -> Option { + let peers = self.peers.read().unwrap(); + peers.values().find_map(|peer_mutex| { + let p = peer_mutex.lock().unwrap(); + if !p.handshake_complete() { + return None; + } + + // unwrap safety: their_node_id is guaranteed to be `Some` after the handshake + // completed. + let counterparty_node_id = p.their_node_id.unwrap().0; + + if counterparty_node_id != *their_node_id { + return None; + } + + let details = PeerDetails { + counterparty_node_id, + socket_address: p.their_socket_address.clone(), + // unwrap safety: their_features is guaranteed to be `Some` after the handshake + // completed. + init_features: p.their_features.clone().unwrap(), + is_inbound_connection: p.inbound_connection, + }; + Some(details) + }) + } + fn get_ephemeral_key(&self) -> SecretKey { let mut ephemeral_hash = self.ephemeral_key_midstate.clone(); let counter = self.peer_counter.get_increment(); @@ -1277,7 +1338,7 @@ impl(&self, peer: &mut Peer, message: &M) { - let logger = WithContext::from(&self.logger, Some(peer.their_node_id.unwrap().0), None); + let logger = WithContext::from(&self.logger, peer.their_node_id.map(|p| p.0), None); if is_gossip_msg(message.type_id()) { log_gossip!(logger, "Enqueueing message {:?} to {}", message, log_pubkey!(peer.their_node_id.unwrap().0)); } else { @@ -1336,7 +1397,9 @@ impl { - log_given_level!(logger, level, "Error handling message{}; ignoring: {}", OptionalFromDebugger(&peer_node_id), e.err); + log_given_level!(logger, level, "Error handling {}message{}; ignoring: {}", + if level == Level::Gossip { "gossip " } else { "" }, + OptionalFromDebugger(&peer_node_id), e.err); continue }, msgs::ErrorAction::IgnoreDuplicateGossip => continue, // Don't even bother logging these @@ -1382,7 +1445,7 @@ impl { - let logger = WithContext::from(&self.logger, Some(peer.their_node_id.unwrap().0), None); + let logger = WithContext::from(&self.logger, peer.their_node_id.map(|p| p.0), None); match self.node_id_to_descriptor.lock().unwrap().entry(peer.their_node_id.unwrap().0) { hash_map::Entry::Occupied(e) => { log_trace!(logger, "Got second connection with {}, closing", log_pubkey!(peer.their_node_id.unwrap().0)); @@ -1462,7 +1525,7 @@ impl x, Err(e) => { @@ -1615,7 +1678,7 @@ impl { - log_debug!(WithContext::from(&self.logger, Some(*node_id), Some(msg.temporary_channel_id)), "Handling SendAcceptChannel event in peer_handler for node {} for channel {}", + log_debug!(WithContext::from(&self.logger, Some(*node_id), Some(msg.common_fields.temporary_channel_id)), "Handling SendAcceptChannel event in peer_handler for node {} for channel {}", log_pubkey!(node_id), - &msg.temporary_channel_id); + &msg.common_fields.temporary_channel_id); self.enqueue_message(&mut *get_peer_for_forwarding!(node_id), msg); }, MessageSendEvent::SendAcceptChannelV2 { ref node_id, ref msg } => { - log_debug!(WithContext::from(&self.logger, Some(*node_id), Some(msg.temporary_channel_id)), "Handling SendAcceptChannelV2 event in peer_handler for node {} for channel {}", + log_debug!(WithContext::from(&self.logger, Some(*node_id), Some(msg.common_fields.temporary_channel_id)), "Handling SendAcceptChannelV2 event in peer_handler for node {} for channel {}", log_pubkey!(node_id), - &msg.temporary_channel_id); + &msg.common_fields.temporary_channel_id); self.enqueue_message(&mut *get_peer_for_forwarding!(node_id), msg); }, MessageSendEvent::SendOpenChannel { ref node_id, ref msg } => { - log_debug!(WithContext::from(&self.logger, Some(*node_id), Some(msg.temporary_channel_id)), "Handling SendOpenChannel event in peer_handler for node {} for channel {}", + log_debug!(WithContext::from(&self.logger, Some(*node_id), Some(msg.common_fields.temporary_channel_id)), "Handling SendOpenChannel event in peer_handler for node {} for channel {}", log_pubkey!(node_id), - &msg.temporary_channel_id); + &msg.common_fields.temporary_channel_id); self.enqueue_message(&mut *get_peer_for_forwarding!(node_id), msg); }, MessageSendEvent::SendOpenChannelV2 { ref node_id, ref msg } => { - log_debug!(WithContext::from(&self.logger, Some(*node_id), Some(msg.temporary_channel_id)), "Handling SendOpenChannelV2 event in peer_handler for node {} for channel {}", + log_debug!(WithContext::from(&self.logger, Some(*node_id), Some(msg.common_fields.temporary_channel_id)), "Handling SendOpenChannelV2 event in peer_handler for node {} for channel {}", log_pubkey!(node_id), - &msg.temporary_channel_id); + &msg.common_fields.temporary_channel_id); self.enqueue_message(&mut *get_peer_for_forwarding!(node_id), msg); }, MessageSendEvent::SendFundingCreated { ref node_id, ref msg } => { log_debug!(WithContext::from(&self.logger, Some(*node_id), Some(msg.temporary_channel_id)), "Handling SendFundingCreated event in peer_handler for node {} for channel {} (which becomes {})", log_pubkey!(node_id), &msg.temporary_channel_id, - log_funding_channel_id!(msg.funding_txid, msg.funding_output_index)); + ChannelId::v1_from_funding_txid(msg.funding_txid.as_byte_array(), 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 self.enqueue_message(&mut *get_peer_for_forwarding!(node_id), msg); @@ -2236,7 +2299,7 @@ impl::Target as wire::CustomMessageReader>::CustomMessage>::Error(msg)); peers_to_disconnect.insert(node_id, msg); @@ -2245,7 +2308,7 @@ impl { assert_eq!(script.into_inner(), unsupported_shutdown_script.clone().into_inner()); }, Err(e) => panic!("Unexpected error: {:?}", e), Ok(_) => panic!("Expected error"), } - nodes[1].node.close_channel(&OutPoint { txid: chan.3.txid(), index: 0 }.to_channel_id(), &nodes[0].node.get_our_node_id()).unwrap(); + nodes[1].node.close_channel(&chan.2, &nodes[0].node.get_our_node_id()).unwrap(); check_added_monitors!(nodes[1], 1); // Use a non-v0 segwit script unsupported without option_shutdown_anysegwit @@ -1004,7 +1025,7 @@ fn test_invalid_shutdown_script() { let nodes = create_network(3, &node_cfgs, &node_chanmgrs); let chan = create_announced_chan_between_nodes(&nodes, 0, 1); - nodes[1].node.close_channel(&OutPoint { txid: chan.3.txid(), index: 0 }.to_channel_id(), &nodes[0].node.get_our_node_id()).unwrap(); + nodes[1].node.close_channel(&chan.2, &nodes[0].node.get_our_node_id()).unwrap(); check_added_monitors!(nodes[1], 1); // Use a segwit v0 script with an unsupported witness program @@ -1038,7 +1059,7 @@ fn test_user_shutdown_script() { let shutdown_script = ShutdownScript::try_from(script.clone()).unwrap(); let chan = create_announced_chan_between_nodes(&nodes, 0, 1); - nodes[1].node.close_channel_with_feerate_and_script(&OutPoint { txid: chan.3.txid(), index: 0 }.to_channel_id(), &nodes[0].node.get_our_node_id(), None, Some(shutdown_script)).unwrap(); + nodes[1].node.close_channel_with_feerate_and_script(&chan.2, &nodes[0].node.get_our_node_id(), None, Some(shutdown_script)).unwrap(); check_added_monitors!(nodes[1], 1); let mut node_0_shutdown = get_event_msg!(nodes[1], MessageSendEvent::SendShutdown, nodes[0].node.get_our_node_id()); @@ -1065,7 +1086,7 @@ fn test_already_set_user_shutdown_script() { let shutdown_script = ShutdownScript::try_from(script).unwrap(); let chan = create_announced_chan_between_nodes(&nodes, 0, 1); - let result = nodes[1].node.close_channel_with_feerate_and_script(&OutPoint { txid: chan.3.txid(), index: 0 }.to_channel_id(), &nodes[0].node.get_our_node_id(), None, Some(shutdown_script)); + let result = nodes[1].node.close_channel_with_feerate_and_script(&chan.2, &nodes[0].node.get_our_node_id(), None, Some(shutdown_script)); assert_eq!(result, Err(APIError::APIMisuseError { err: "Cannot override shutdown script for a channel with one already set".to_string() })); } @@ -1132,9 +1153,9 @@ fn do_test_closing_signed_reinit_timeout(timeout_step: TimeoutStep) { 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, [nodes[0].node.get_our_node_id()], 100000); + check_closed_event!(nodes[1], 1, ClosureReason::CounterpartyInitiatedCooperativeClosure, [nodes[0].node.get_our_node_id()], 100000); } - check_closed_event!(nodes[0], 1, ClosureReason::CooperativeClosure, [nodes[1].node.get_our_node_id()], 100000); + check_closed_event!(nodes[0], 1, ClosureReason::LocallyInitiatedCooperativeClosure, [nodes[1].node.get_our_node_id()], 100000); } if timeout_step != TimeoutStep::NoTimeout { @@ -1197,7 +1218,7 @@ fn do_simple_legacy_shutdown_test(high_initiator_fee: bool) { *feerate_lock *= 10; } - nodes[0].node.close_channel(&OutPoint { txid: chan.3.txid(), index: 0 }.to_channel_id(), &nodes[1].node.get_our_node_id()).unwrap(); + 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); let node_1_shutdown = get_event_msg!(nodes[1], MessageSendEvent::SendShutdown, nodes[0].node.get_our_node_id()); @@ -1218,8 +1239,8 @@ fn do_simple_legacy_shutdown_test(high_initiator_fee: bool) { 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, [nodes[1].node.get_our_node_id()], 100000); - check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure, [nodes[0].node.get_our_node_id()], 100000); + check_closed_event!(nodes[0], 1, ClosureReason::LocallyInitiatedCooperativeClosure, [nodes[1].node.get_our_node_id()], 100000); + check_closed_event!(nodes[1], 1, ClosureReason::CounterpartyInitiatedCooperativeClosure, [nodes[0].node.get_our_node_id()], 100000); } #[test] @@ -1237,7 +1258,7 @@ fn simple_target_feerate_shutdown() { let nodes = create_network(2, &node_cfgs, &node_chanmgrs); let chan = create_announced_chan_between_nodes(&nodes, 0, 1); - let chan_id = OutPoint { txid: chan.3.txid(), index: 0 }.to_channel_id(); + let chan_id = chan.2; nodes[0].node.close_channel_with_feerate_and_script(&chan_id, &nodes[1].node.get_our_node_id(), Some(253 * 10), None).unwrap(); let node_0_shutdown = get_event_msg!(nodes[0], MessageSendEvent::SendShutdown, nodes[1].node.get_our_node_id()); @@ -1273,8 +1294,8 @@ fn simple_target_feerate_shutdown() { 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, [nodes[1].node.get_our_node_id()], 100000); - check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure, [nodes[0].node.get_our_node_id()], 100000); + check_closed_event!(nodes[0], 1, ClosureReason::LocallyInitiatedCooperativeClosure, [nodes[1].node.get_our_node_id()], 100000); + check_closed_event!(nodes[1], 1, ClosureReason::LocallyInitiatedCooperativeClosure, [nodes[0].node.get_our_node_id()], 100000); } fn do_outbound_update_no_early_closing_signed(use_htlc: bool) { @@ -1366,8 +1387,8 @@ fn do_outbound_update_no_early_closing_signed(use_htlc: bool) { 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); + check_closed_event!(nodes[0], 1, ClosureReason::LocallyInitiatedCooperativeClosure, [nodes[1].node.get_our_node_id()], 100000); + check_closed_event!(nodes[1], 1, ClosureReason::LocallyInitiatedCooperativeClosure, [nodes[0].node.get_our_node_id()], 100000); } #[test] @@ -1375,3 +1396,41 @@ fn outbound_update_no_early_closing_signed() { do_outbound_update_no_early_closing_signed(true); do_outbound_update_no_early_closing_signed(false); } + +#[test] +fn batch_funding_failure() { + // Provides test coverage of batch funding failure, which previously deadlocked + 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); + + exchange_open_accept_chan(&nodes[0], &nodes[1], 1_000_000, 0); + exchange_open_accept_chan(&nodes[0], &nodes[2], 1_000_000, 0); + + let events = nodes[0].node.get_and_clear_pending_events(); + assert_eq!(events.len(), 2); + // Build a transaction which only has the output for one of the two channels we're trying to + // confirm. Previously this led to a deadlock in channel closure handling. + let mut tx = Transaction { version: 2, lock_time: LockTime::ZERO, input: Vec::new(), output: Vec::new() }; + let mut chans = Vec::new(); + for (idx, ev) in events.iter().enumerate() { + if let Event::FundingGenerationReady { temporary_channel_id, counterparty_node_id, output_script, .. } = ev { + if idx == 0 { + tx.output.push(TxOut { value: 1_000_000, script_pubkey: output_script.clone() }); + } + chans.push((temporary_channel_id, counterparty_node_id)); + } else { panic!(); } + } + + // We should probably end up with an error for both channels, but currently we don't generate + // an error for the failing channel itself. + let err = "Error in transaction funding: Misuse error: No output matched the script_pubkey and value in the FundingGenerationReady event".to_string(); + let close = [ExpectedCloseEvent::from_id_reason(ChannelId::v1_from_funding_txid(tx.txid().as_ref(), 0), true, ClosureReason::ProcessingError { err })]; + + nodes[0].node.batch_funding_transaction_generated(&chans, tx).unwrap_err(); + + get_event_msg!(nodes[0], MessageSendEvent::SendFundingCreated, nodes[1].node.get_our_node_id()); + check_closed_events(&nodes[0], &close); + assert_eq!(nodes[0].node.list_channels().len(), 0); +} diff --git a/lightning/src/offers/invoice.rs b/lightning/src/offers/invoice.rs index bb29c76164e..fc837102348 100644 --- a/lightning/src/offers/invoice.rs +++ b/lightning/src/offers/invoice.rs @@ -22,13 +22,14 @@ //! //! use bitcoin::hashes::Hash; //! use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey}; -//! use core::convert::{Infallible, TryFrom}; +//! use core::convert::TryFrom; +//! use lightning::offers::invoice::UnsignedBolt12Invoice; //! use lightning::offers::invoice_request::InvoiceRequest; //! use lightning::offers::refund::Refund; //! use lightning::util::ser::Writeable; //! //! # use lightning::ln::PaymentHash; -//! # use lightning::offers::invoice::BlindedPayInfo; +//! # use lightning::offers::invoice::{BlindedPayInfo, ExplicitSigningPubkey, InvoiceBuilder}; //! # use lightning::blinded_path::BlindedPath; //! # //! # fn create_payment_paths() -> Vec<(BlindedPayInfo, BlindedPath)> { unimplemented!() } @@ -44,6 +45,7 @@ //! let mut buffer = Vec::new(); //! //! // Invoice for the "offer to be paid" flow. +//! # >::from( //! InvoiceRequest::try_from(bytes)? #![cfg_attr(feature = "std", doc = " .respond_with(payment_paths, payment_hash)? @@ -51,12 +53,13 @@ #![cfg_attr(not(feature = "std"), doc = " .respond_with_no_std(payment_paths, payment_hash, core::time::Duration::from_secs(0))? ")] +//! # ) //! .relative_expiry(3600) //! .allow_mpp() //! .fallback_v0_p2wpkh(&wpubkey_hash) //! .build()? -//! .sign::<_, Infallible>( -//! |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) +//! .sign(|message: &UnsignedBolt12Invoice| +//! Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) //! ) //! .expect("failed verifying signature") //! .write(&mut buffer) @@ -74,6 +77,7 @@ //! # let mut buffer = Vec::new(); //! //! // Invoice for the "offer for money" flow. +//! # >::from( //! "lnr1qcp4256ypq" //! .parse::()? #![cfg_attr(feature = "std", doc = " @@ -82,12 +86,13 @@ #![cfg_attr(not(feature = "std"), doc = " .respond_with_no_std(payment_paths, payment_hash, pubkey, core::time::Duration::from_secs(0))? ")] +//! # ) //! .relative_expiry(3600) //! .allow_mpp() //! .fallback_v0_p2wpkh(&wpubkey_hash) //! .build()? -//! .sign::<_, Infallible>( -//! |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) +//! .sign(|message: &UnsignedBolt12Invoice| +//! Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) //! ) //! .expect("failed verifying signature") //! .write(&mut buffer) @@ -105,7 +110,7 @@ use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, self}; use bitcoin::secp256k1::schnorr::Signature; use bitcoin::address::{Address, Payload, WitnessProgram, WitnessVersion}; use bitcoin::key::TweakedPublicKey; -use core::convert::{AsRef, Infallible, TryFrom}; +use core::convert::{AsRef, TryFrom}; use core::time::Duration; use crate::io; use crate::blinded_path::BlindedPath; @@ -115,7 +120,7 @@ use crate::ln::features::{BlindedHopFeatures, Bolt12InvoiceFeatures, InvoiceRequ 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, TaggedHash, TlvStream, WithoutSignatures, self}; +use crate::offers::merkle::{SignError, SignFn, 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}; @@ -151,6 +156,38 @@ pub struct InvoiceBuilder<'a, S: SigningPubkeyStrategy> { signing_pubkey_strategy: S, } +/// Builds a [`Bolt12Invoice`] from either: +/// - an [`InvoiceRequest`] for the "offer to be paid" flow or +/// - a [`Refund`] for the "offer for money" flow. +/// +/// See [module-level documentation] for usage. +/// +/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest +/// [`Refund`]: crate::offers::refund::Refund +/// [module-level documentation]: self +#[cfg(c_bindings)] +pub struct InvoiceWithExplicitSigningPubkeyBuilder<'a> { + invreq_bytes: &'a Vec, + invoice: InvoiceContents, + signing_pubkey_strategy: ExplicitSigningPubkey, +} + +/// Builds a [`Bolt12Invoice`] from either: +/// - an [`InvoiceRequest`] for the "offer to be paid" flow or +/// - a [`Refund`] for the "offer for money" flow. +/// +/// See [module-level documentation] for usage. +/// +/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest +/// [`Refund`]: crate::offers::refund::Refund +/// [module-level documentation]: self +#[cfg(c_bindings)] +pub struct InvoiceWithDerivedSigningPubkeyBuilder<'a> { + invreq_bytes: &'a Vec, + invoice: InvoiceContents, + signing_pubkey_strategy: DerivedSigningPubkey, +} + /// Indicates how [`Bolt12Invoice::signing_pubkey`] was set. /// /// This is not exported to bindings users as builder patterns don't map outside of move semantics. @@ -169,7 +206,8 @@ pub struct DerivedSigningPubkey(KeyPair); impl SigningPubkeyStrategy for ExplicitSigningPubkey {} impl SigningPubkeyStrategy for DerivedSigningPubkey {} -impl<'a> InvoiceBuilder<'a, ExplicitSigningPubkey> { +macro_rules! invoice_explicit_signing_pubkey_builder_methods { ($self: ident, $self_type: ty) => { + #[cfg_attr(c_bindings, allow(dead_code))] pub(super) fn for_offer( invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, created_at: Duration, payment_hash: PaymentHash @@ -186,6 +224,7 @@ impl<'a> InvoiceBuilder<'a, ExplicitSigningPubkey> { Self::new(&invoice_request.bytes, contents, ExplicitSigningPubkey {}) } + #[cfg_attr(c_bindings, allow(dead_code))] pub(super) fn for_refund( refund: &'a Refund, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, created_at: Duration, payment_hash: PaymentHash, signing_pubkey: PublicKey @@ -200,9 +239,34 @@ impl<'a> InvoiceBuilder<'a, ExplicitSigningPubkey> { Self::new(&refund.bytes, contents, ExplicitSigningPubkey {}) } -} -impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> { + /// Builds an unsigned [`Bolt12Invoice`] after checking for valid semantics. It can be signed by + /// [`UnsignedBolt12Invoice::sign`]. + pub fn build($self: $self_type) -> Result { + #[cfg(feature = "std")] { + if $self.invoice.is_offer_or_refund_expired() { + return Err(Bolt12SemanticError::AlreadyExpired); + } + } + + #[cfg(not(feature = "std"))] { + if $self.invoice.is_offer_or_refund_expired_no_std($self.invoice.created_at()) { + return Err(Bolt12SemanticError::AlreadyExpired); + } + } + + let Self { invreq_bytes, invoice, .. } = $self; + #[cfg(not(c_bindings))] { + Ok(UnsignedBolt12Invoice::new(invreq_bytes, invoice)) + } + #[cfg(c_bindings)] { + Ok(UnsignedBolt12Invoice::new(invreq_bytes, invoice.clone())) + } + } +} } + +macro_rules! invoice_derived_signing_pubkey_builder_methods { ($self: ident, $self_type: ty) => { + #[cfg_attr(c_bindings, allow(dead_code))] pub(super) fn for_offer_using_keys( invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, created_at: Duration, payment_hash: PaymentHash, keys: KeyPair @@ -219,6 +283,7 @@ impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> { Self::new(&invoice_request.bytes, contents, DerivedSigningPubkey(keys)) } + #[cfg_attr(c_bindings, allow(dead_code))] pub(super) fn for_refund_using_keys( refund: &'a Refund, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, created_at: Duration, payment_hash: PaymentHash, keys: KeyPair, @@ -234,9 +299,43 @@ impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> { Self::new(&refund.bytes, contents, DerivedSigningPubkey(keys)) } -} -impl<'a, S: SigningPubkeyStrategy> InvoiceBuilder<'a, S> { + /// Builds a signed [`Bolt12Invoice`] after checking for valid semantics. + pub fn build_and_sign( + $self: $self_type, secp_ctx: &Secp256k1 + ) -> Result { + #[cfg(feature = "std")] { + if $self.invoice.is_offer_or_refund_expired() { + return Err(Bolt12SemanticError::AlreadyExpired); + } + } + + #[cfg(not(feature = "std"))] { + if $self.invoice.is_offer_or_refund_expired_no_std($self.invoice.created_at()) { + return Err(Bolt12SemanticError::AlreadyExpired); + } + } + + let Self { + invreq_bytes, invoice, signing_pubkey_strategy: DerivedSigningPubkey(keys) + } = $self; + #[cfg(not(c_bindings))] + let unsigned_invoice = UnsignedBolt12Invoice::new(invreq_bytes, invoice); + #[cfg(c_bindings)] + let mut unsigned_invoice = UnsignedBolt12Invoice::new(invreq_bytes, invoice.clone()); + + let invoice = unsigned_invoice + .sign(|message: &UnsignedBolt12Invoice| + Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) + ) + .unwrap(); + Ok(invoice) + } +} } + +macro_rules! invoice_builder_methods { ( + $self: ident, $self_type: ty, $return_type: ty, $return_value: expr, $type_param: ty $(, $self_mut: tt)? +) => { pub(crate) fn amount_msats( invoice_request: &InvoiceRequest ) -> Result { @@ -253,6 +352,7 @@ impl<'a, S: SigningPubkeyStrategy> InvoiceBuilder<'a, S> { } } + #[cfg_attr(c_bindings, allow(dead_code))] fn fields( payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, created_at: Duration, payment_hash: PaymentHash, amount_msats: u64, signing_pubkey: PublicKey @@ -263,8 +363,9 @@ impl<'a, S: SigningPubkeyStrategy> InvoiceBuilder<'a, S> { } } + #[cfg_attr(c_bindings, allow(dead_code))] fn new( - invreq_bytes: &'a Vec, contents: InvoiceContents, signing_pubkey_strategy: S + invreq_bytes: &'a Vec, contents: InvoiceContents, signing_pubkey_strategy: $type_param ) -> Result { if contents.fields().payment_paths.is_empty() { return Err(Bolt12SemanticError::MissingPaths); @@ -278,108 +379,120 @@ impl<'a, S: SigningPubkeyStrategy> InvoiceBuilder<'a, S> { /// [`Bolt12Invoice::is_expired`]. /// /// Successive calls to this method will override the previous setting. - pub fn relative_expiry(mut self, relative_expiry_secs: u32) -> Self { + pub fn relative_expiry($($self_mut)* $self: $self_type, relative_expiry_secs: u32) -> $return_type { let relative_expiry = Duration::from_secs(relative_expiry_secs as u64); - self.invoice.fields_mut().relative_expiry = Some(relative_expiry); - self + $self.invoice.fields_mut().relative_expiry = Some(relative_expiry); + $return_value } /// 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. - pub fn fallback_v0_p2wsh(mut self, script_hash: &WScriptHash) -> Self { + pub fn fallback_v0_p2wsh($($self_mut)* $self: $self_type, script_hash: &WScriptHash) -> $return_type { let address = FallbackAddress { version: WitnessVersion::V0.to_num(), program: Vec::from(script_hash.to_byte_array()), }; - self.invoice.fields_mut().fallbacks.get_or_insert_with(Vec::new).push(address); - self + $self.invoice.fields_mut().fallbacks.get_or_insert_with(Vec::new).push(address); + $return_value } /// 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. - pub fn fallback_v0_p2wpkh(mut self, pubkey_hash: &WPubkeyHash) -> Self { + pub fn fallback_v0_p2wpkh($($self_mut)* $self: $self_type, pubkey_hash: &WPubkeyHash) -> $return_type { let address = FallbackAddress { version: WitnessVersion::V0.to_num(), program: Vec::from(pubkey_hash.to_byte_array()), }; - self.invoice.fields_mut().fallbacks.get_or_insert_with(Vec::new).push(address); - self + $self.invoice.fields_mut().fallbacks.get_or_insert_with(Vec::new).push(address); + $return_value } /// 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. - pub fn fallback_v1_p2tr_tweaked(mut self, output_key: &TweakedPublicKey) -> Self { + pub fn fallback_v1_p2tr_tweaked($($self_mut)* $self: $self_type, output_key: &TweakedPublicKey) -> $return_type { let address = FallbackAddress { version: WitnessVersion::V1.to_num(), program: Vec::from(&output_key.serialize()[..]), }; - self.invoice.fields_mut().fallbacks.get_or_insert_with(Vec::new).push(address); - self + $self.invoice.fields_mut().fallbacks.get_or_insert_with(Vec::new).push(address); + $return_value } /// 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 + pub fn allow_mpp($($self_mut)* $self: $self_type) -> $return_type { + $self.invoice.fields_mut().features.set_basic_mpp_optional(); + $return_value } -} +} } impl<'a> InvoiceBuilder<'a, ExplicitSigningPubkey> { - /// Builds an unsigned [`Bolt12Invoice`] after checking for valid semantics. It can be signed by - /// [`UnsignedBolt12Invoice::sign`]. - pub fn build(self) -> Result { - #[cfg(feature = "std")] { - if self.invoice.is_offer_or_refund_expired() { - return Err(Bolt12SemanticError::AlreadyExpired); - } - } + invoice_explicit_signing_pubkey_builder_methods!(self, Self); +} - #[cfg(not(feature = "std"))] { - if self.invoice.is_offer_or_refund_expired_no_std(self.invoice.created_at()) { - return Err(Bolt12SemanticError::AlreadyExpired); - } - } +impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> { + invoice_derived_signing_pubkey_builder_methods!(self, Self); +} - let InvoiceBuilder { invreq_bytes, invoice, .. } = self; - Ok(UnsignedBolt12Invoice::new(invreq_bytes, invoice)) - } +impl<'a, S: SigningPubkeyStrategy> InvoiceBuilder<'a, S> { + invoice_builder_methods!(self, Self, Self, self, S, mut); } -impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> { - /// Builds a signed [`Bolt12Invoice`] after checking for valid semantics. - pub fn build_and_sign( - self, secp_ctx: &Secp256k1 - ) -> Result { - #[cfg(feature = "std")] { - if self.invoice.is_offer_or_refund_expired() { - return Err(Bolt12SemanticError::AlreadyExpired); - } - } +#[cfg(all(c_bindings, not(test)))] +impl<'a> InvoiceWithExplicitSigningPubkeyBuilder<'a> { + invoice_explicit_signing_pubkey_builder_methods!(self, &mut Self); + invoice_builder_methods!(self, &mut Self, (), (), ExplicitSigningPubkey); +} - #[cfg(not(feature = "std"))] { - if self.invoice.is_offer_or_refund_expired_no_std(self.invoice.created_at()) { - return Err(Bolt12SemanticError::AlreadyExpired); - } +#[cfg(all(c_bindings, test))] +impl<'a> InvoiceWithExplicitSigningPubkeyBuilder<'a> { + invoice_explicit_signing_pubkey_builder_methods!(self, &mut Self); + invoice_builder_methods!(self, &mut Self, &mut Self, self, ExplicitSigningPubkey); +} + +#[cfg(all(c_bindings, not(test)))] +impl<'a> InvoiceWithDerivedSigningPubkeyBuilder<'a> { + invoice_derived_signing_pubkey_builder_methods!(self, &mut Self); + invoice_builder_methods!(self, &mut Self, (), (), DerivedSigningPubkey); +} + +#[cfg(all(c_bindings, test))] +impl<'a> InvoiceWithDerivedSigningPubkeyBuilder<'a> { + invoice_derived_signing_pubkey_builder_methods!(self, &mut Self); + invoice_builder_methods!(self, &mut Self, &mut Self, self, DerivedSigningPubkey); +} + +#[cfg(c_bindings)] +impl<'a> From> +for InvoiceBuilder<'a, ExplicitSigningPubkey> { + fn from(builder: InvoiceWithExplicitSigningPubkeyBuilder<'a>) -> Self { + let InvoiceWithExplicitSigningPubkeyBuilder { + invreq_bytes, invoice, signing_pubkey_strategy, + } = builder; + + Self { + invreq_bytes, invoice, signing_pubkey_strategy, } + } +} - let InvoiceBuilder { - invreq_bytes, invoice, signing_pubkey_strategy: DerivedSigningPubkey(keys) - } = self; - let unsigned_invoice = UnsignedBolt12Invoice::new(invreq_bytes, invoice); +#[cfg(c_bindings)] +impl<'a> From> +for InvoiceBuilder<'a, DerivedSigningPubkey> { + fn from(builder: InvoiceWithDerivedSigningPubkeyBuilder<'a>) -> Self { + let InvoiceWithDerivedSigningPubkeyBuilder { + invreq_bytes, invoice, signing_pubkey_strategy, + } = builder; - let invoice = unsigned_invoice - .sign::<_, Infallible>( - |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) - ) - .unwrap(); - Ok(invoice) + Self { + invreq_bytes, invoice, signing_pubkey_strategy, + } } } @@ -395,6 +508,30 @@ pub struct UnsignedBolt12Invoice { tagged_hash: TaggedHash, } +/// A function for signing an [`UnsignedBolt12Invoice`]. +pub trait SignBolt12InvoiceFn { + /// Signs a [`TaggedHash`] computed over the merkle root of `message`'s TLV stream. + fn sign_invoice(&self, message: &UnsignedBolt12Invoice) -> Result; +} + +impl SignBolt12InvoiceFn for F +where + F: Fn(&UnsignedBolt12Invoice) -> Result, +{ + fn sign_invoice(&self, message: &UnsignedBolt12Invoice) -> Result { + self(message) + } +} + +impl SignFn for F +where + F: SignBolt12InvoiceFn, +{ + fn sign(&self, message: &UnsignedBolt12Invoice) -> Result { + self.sign_invoice(message) + } +} + 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 @@ -416,32 +553,50 @@ impl UnsignedBolt12Invoice { pub fn tagged_hash(&self) -> &TaggedHash { &self.tagged_hash } +} +macro_rules! unsigned_invoice_sign_method { ($self: ident, $self_type: ty $(, $self_mut: tt)?) => { /// 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(mut self, sign: F) -> Result> - where - F: FnOnce(&Self) -> Result - { - let pubkey = self.contents.fields().signing_pubkey; - let signature = merkle::sign_message(sign, &self, pubkey)?; + pub fn sign( + $($self_mut)* $self: $self_type, sign: F + ) -> Result { + 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 self.bytes).unwrap(); + signature_tlv_stream.write(&mut $self.bytes).unwrap(); Ok(Bolt12Invoice { - bytes: self.bytes, - contents: self.contents, + #[cfg(not(c_bindings))] + bytes: $self.bytes, + #[cfg(c_bindings)] + bytes: $self.bytes.clone(), + #[cfg(not(c_bindings))] + contents: $self.contents, + #[cfg(c_bindings)] + contents: $self.contents.clone(), signature, - tagged_hash: self.tagged_hash, + #[cfg(not(c_bindings))] + tagged_hash: $self.tagged_hash, + #[cfg(c_bindings)] + tagged_hash: $self.tagged_hash.clone(), }) } +} } + +#[cfg(not(c_bindings))] +impl UnsignedBolt12Invoice { + unsigned_invoice_sign_method!(self, Self, mut); +} + +#[cfg(c_bindings)] +impl UnsignedBolt12Invoice { + unsigned_invoice_sign_method!(self, &mut Self); } impl AsRef for UnsignedBolt12Invoice { @@ -1306,10 +1461,19 @@ mod tests { use crate::ln::msgs::DecodeError; use crate::offers::invoice_request::InvoiceRequestTlvStreamRef; use crate::offers::merkle::{SignError, SignatureTlvStreamRef, TaggedHash, self}; - use crate::offers::offer::{Amount, OfferBuilder, OfferTlvStreamRef, Quantity}; + use crate::offers::offer::{Amount, OfferTlvStreamRef, Quantity}; + #[cfg(not(c_bindings))] + use { + crate::offers::offer::OfferBuilder, + crate::offers::refund::RefundBuilder, + }; + #[cfg(c_bindings)] + use { + crate::offers::offer::OfferWithExplicitMetadataBuilder as OfferBuilder, + crate::offers::refund::RefundMaybeWithDerivedMetadataBuilder as RefundBuilder, + }; use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError}; use crate::offers::payer::PayerTlvStreamRef; - use crate::offers::refund::RefundBuilder; use crate::offers::test_utils::*; use crate::util::ser::{BigSize, Iterable, Writeable}; use crate::util::string::PrintableString; @@ -1384,6 +1548,8 @@ mod tests { }, } + #[cfg(c_bindings)] + let mut unsigned_invoice = unsigned_invoice; let invoice = unsigned_invoice.sign(recipient_sign).unwrap(); let mut buffer = Vec::new(); @@ -1644,6 +1810,8 @@ mod tests { ], }; + #[cfg(c_bindings)] + use crate::offers::offer::OfferWithDerivedMetadataBuilder as OfferBuilder; let offer = OfferBuilder ::deriving_signing_pubkey(desc, node_id, &expanded_key, &entropy, &secp_ctx) .amount_msats(1000) @@ -1867,10 +2035,10 @@ mod tests { .sign(payer_sign).unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .build().unwrap() - .sign(|_| Err(())) + .sign(fail_sign) { Ok(_) => panic!("expected error"), - Err(e) => assert_eq!(e, SignError::Signing(())), + Err(e) => assert_eq!(e, SignError::Signing), } match OfferBuilder::new("foo".into(), recipient_pubkey()) @@ -2093,11 +2261,18 @@ mod tests { .request_invoice(vec![1; 32], payer_pubkey()).unwrap() .build().unwrap() .sign(payer_sign).unwrap(); + #[cfg(not(c_bindings))] + let invoice_builder = invoice_request + .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap(); + #[cfg(c_bindings)] let mut invoice_builder = invoice_request - .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() + .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap(); + let invoice_builder = invoice_builder .fallback_v0_p2wsh(&script.wscript_hash()) .fallback_v0_p2wpkh(&pubkey.wpubkey_hash().unwrap()) .fallback_v1_p2tr_tweaked(&tweaked_pubkey); + #[cfg(not(c_bindings))] + let mut invoice_builder = invoice_builder; // Only standard addresses will be included. let fallbacks = invoice_builder.invoice.fields_mut().fallbacks.as_mut().unwrap(); diff --git a/lightning/src/offers/invoice_request.rs b/lightning/src/offers/invoice_request.rs index 4dd85b352f7..f282075d933 100644 --- a/lightning/src/offers/invoice_request.rs +++ b/lightning/src/offers/invoice_request.rs @@ -25,8 +25,8 @@ //! //! use bitcoin::network::constants::Network; //! use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey}; -//! use core::convert::Infallible; //! use lightning::ln::features::OfferFeatures; +//! use lightning::offers::invoice_request::UnsignedInvoiceRequest; //! use lightning::offers::offer::Offer; //! use lightning::util::ser::Writeable; //! @@ -36,16 +36,19 @@ //! let pubkey = PublicKey::from(keys); //! let mut buffer = Vec::new(); //! +//! # use lightning::offers::invoice_request::{ExplicitPayerId, InvoiceRequestBuilder}; +//! # >::from( //! "lno1qcp4256ypq" //! .parse::()? //! .request_invoice(vec![42; 64], pubkey)? +//! # ) //! .chain(Network::Testnet)? //! .amount_msats(1000)? //! .quantity(5)? //! .payer_note("foo".to_string()) //! .build()? -//! .sign::<_, Infallible>( -//! |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) +//! .sign(|message: &UnsignedInvoiceRequest| +//! Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) //! ) //! .expect("failed verifying signature") //! .write(&mut buffer) @@ -58,7 +61,7 @@ use bitcoin::blockdata::constants::ChainHash; use bitcoin::network::constants::Network; use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, self}; use bitcoin::secp256k1::schnorr::Signature; -use core::convert::{AsRef, Infallible, TryFrom}; +use core::convert::{AsRef, TryFrom}; use core::ops::Deref; use crate::sign::EntropySource; use crate::io; @@ -68,8 +71,8 @@ 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, TaggedHash, self}; +use crate::offers::invoice::BlindedPayInfo; +use crate::offers::merkle::{SignError, SignFn, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, self}; use crate::offers::offer::{Offer, OfferContents, OfferTlvStream, OfferTlvStreamRef}; use crate::offers::parse::{Bolt12ParseError, ParsedMessage, Bolt12SemanticError}; use crate::offers::payer::{PayerContents, PayerTlvStream, PayerTlvStreamRef}; @@ -77,6 +80,15 @@ use crate::offers::signer::{Metadata, MetadataMaterial}; use crate::util::ser::{HighZeroBytesDroppedBigSize, SeekReadable, WithoutLength, Writeable, Writer}; use crate::util::string::PrintableString; +#[cfg(not(c_bindings))] +use { + crate::offers::invoice::{DerivedSigningPubkey, ExplicitSigningPubkey, InvoiceBuilder}, +}; +#[cfg(c_bindings)] +use { + crate::offers::invoice::{InvoiceWithDerivedSigningPubkeyBuilder, InvoiceWithExplicitSigningPubkeyBuilder}, +}; + use crate::prelude::*; /// Tag for the hash function used when signing an [`InvoiceRequest`]'s merkle root. @@ -99,6 +111,34 @@ pub struct InvoiceRequestBuilder<'a, 'b, P: PayerIdStrategy, T: secp256k1::Signi secp_ctx: Option<&'b Secp256k1>, } +/// Builds an [`InvoiceRequest`] from an [`Offer`] for the "offer to be paid" flow. +/// +/// See [module-level documentation] for usage. +/// +/// [module-level documentation]: self +#[cfg(c_bindings)] +pub struct InvoiceRequestWithExplicitPayerIdBuilder<'a, 'b> { + offer: &'a Offer, + invoice_request: InvoiceRequestContentsWithoutPayerId, + payer_id: Option, + payer_id_strategy: core::marker::PhantomData, + secp_ctx: Option<&'b Secp256k1>, +} + +/// Builds an [`InvoiceRequest`] from an [`Offer`] for the "offer to be paid" flow. +/// +/// See [module-level documentation] for usage. +/// +/// [module-level documentation]: self +#[cfg(c_bindings)] +pub struct InvoiceRequestWithDerivedPayerIdBuilder<'a, 'b> { + offer: &'a Offer, + invoice_request: InvoiceRequestContentsWithoutPayerId, + payer_id: Option, + payer_id_strategy: core::marker::PhantomData, + secp_ctx: Option<&'b Secp256k1>, +} + /// Indicates how [`InvoiceRequest::payer_id`] will be set. /// /// This is not exported to bindings users as builder patterns don't map outside of move semantics. @@ -117,7 +157,8 @@ pub struct DerivedPayerId {} impl PayerIdStrategy for ExplicitPayerId {} impl PayerIdStrategy for DerivedPayerId {} -impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, ExplicitPayerId, T> { +macro_rules! invoice_request_explicit_payer_id_builder_methods { ($self: ident, $self_type: ty) => { + #[cfg_attr(c_bindings, allow(dead_code))] pub(super) fn new(offer: &'a Offer, metadata: Vec, payer_id: PublicKey) -> Self { Self { offer, @@ -128,6 +169,7 @@ impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, ExplicitPayerI } } + #[cfg_attr(c_bindings, allow(dead_code))] pub(super) fn deriving_metadata( offer: &'a Offer, payer_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES, payment_id: PaymentId, @@ -144,12 +186,23 @@ impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, ExplicitPayerI secp_ctx: None, } } -} -impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, DerivedPayerId, T> { + /// Builds an unsigned [`InvoiceRequest`] after checking for valid semantics. It can be signed + /// by [`UnsignedInvoiceRequest::sign`]. + pub fn build($self: $self_type) -> Result { + let (unsigned_invoice_request, keys, _) = $self.build_with_checks()?; + debug_assert!(keys.is_none()); + Ok(unsigned_invoice_request) + } +} } + +macro_rules! invoice_request_derived_payer_id_builder_methods { ( + $self: ident, $self_type: ty, $secp_context: ty +) => { + #[cfg_attr(c_bindings, allow(dead_code))] pub(super) fn deriving_payer_id( offer: &'a Offer, expanded_key: &ExpandedKey, entropy_source: ES, - secp_ctx: &'b Secp256k1, payment_id: PaymentId + secp_ctx: &'b Secp256k1<$secp_context>, payment_id: PaymentId ) -> Self where ES::Target: EntropySource { let nonce = Nonce::from_entropy_source(entropy_source); let payment_id = Some(payment_id); @@ -163,9 +216,29 @@ impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, DerivedPayerId secp_ctx: Some(secp_ctx), } } -} -impl<'a, 'b, P: PayerIdStrategy, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, P, T> { + /// Builds a signed [`InvoiceRequest`] after checking for valid semantics. + pub fn build_and_sign($self: $self_type) -> Result { + let (unsigned_invoice_request, keys, secp_ctx) = $self.build_with_checks()?; + #[cfg(c_bindings)] + let mut unsigned_invoice_request = unsigned_invoice_request; + debug_assert!(keys.is_some()); + + let secp_ctx = secp_ctx.unwrap(); + let keys = keys.unwrap(); + let invoice_request = unsigned_invoice_request + .sign(|message: &UnsignedInvoiceRequest| + Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) + ) + .unwrap(); + Ok(invoice_request) + } +} } + +macro_rules! invoice_request_builder_methods { ( + $self: ident, $self_type: ty, $return_type: ty, $return_value: expr, $secp_context: ty $(, $self_mut: tt)? +) => { + #[cfg_attr(c_bindings, allow(dead_code))] fn create_contents(offer: &Offer, metadata: Metadata) -> InvoiceRequestContentsWithoutPayerId { let offer = offer.contents.clone(); InvoiceRequestContentsWithoutPayerId { @@ -179,8 +252,8 @@ impl<'a, 'b, P: PayerIdStrategy, T: secp256k1::Signing> InvoiceRequestBuilder<'a /// by the offer. /// /// Successive calls to this method will override the previous setting. - pub fn chain(self, network: Network) -> Result { - self.chain_hash(ChainHash::using_genesis_block(network)) + pub fn chain($self: $self_type, network: Network) -> Result<$return_type, Bolt12SemanticError> { + $self.chain_hash(ChainHash::using_genesis_block(network)) } /// Sets the [`InvoiceRequest::chain`] for paying an invoice. If not called, the chain hash of @@ -188,13 +261,13 @@ impl<'a, 'b, P: PayerIdStrategy, T: secp256k1::Signing> InvoiceRequestBuilder<'a /// offer. /// /// Successive calls to this method will override the previous setting. - pub(crate) fn chain_hash(mut self, chain: ChainHash) -> Result { - if !self.offer.supports_chain(chain) { + pub(crate) fn chain_hash($($self_mut)* $self: $self_type, chain: ChainHash) -> Result<$return_type, Bolt12SemanticError> { + if !$self.offer.supports_chain(chain) { return Err(Bolt12SemanticError::UnsupportedChain); } - self.invoice_request.chain = Some(chain); - Ok(self) + $self.invoice_request.chain = Some(chain); + Ok($return_value) } /// Sets the [`InvoiceRequest::amount_msats`] for paying an invoice. Errors if `amount_msats` is @@ -203,156 +276,208 @@ impl<'a, 'b, P: PayerIdStrategy, T: secp256k1::Signing> InvoiceRequestBuilder<'a /// Successive calls to this method will override the previous setting. /// /// [`quantity`]: Self::quantity - pub fn amount_msats(mut self, amount_msats: u64) -> Result { - self.invoice_request.offer.check_amount_msats_for_quantity( - Some(amount_msats), self.invoice_request.quantity + pub fn amount_msats($($self_mut)* $self: $self_type, amount_msats: u64) -> Result<$return_type, Bolt12SemanticError> { + $self.invoice_request.offer.check_amount_msats_for_quantity( + Some(amount_msats), $self.invoice_request.quantity )?; - self.invoice_request.amount_msats = Some(amount_msats); - Ok(self) + $self.invoice_request.amount_msats = Some(amount_msats); + Ok($return_value) } /// Sets [`InvoiceRequest::quantity`] of items. If not set, `1` is assumed. Errors if `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.invoice_request.offer.check_quantity(Some(quantity))?; - self.invoice_request.quantity = Some(quantity); - Ok(self) + pub fn quantity($($self_mut)* $self: $self_type, quantity: u64) -> Result<$return_type, Bolt12SemanticError> { + $self.invoice_request.offer.check_quantity(Some(quantity))?; + $self.invoice_request.quantity = Some(quantity); + Ok($return_value) } /// Sets the [`InvoiceRequest::payer_note`]. /// /// Successive calls to this method will override the previous setting. - pub fn payer_note(mut self, payer_note: String) -> Self { - self.invoice_request.payer_note = Some(payer_note); - self + pub fn payer_note($($self_mut)* $self: $self_type, payer_note: String) -> $return_type { + $self.invoice_request.payer_note = Some(payer_note); + $return_value } - fn build_with_checks(mut self) -> Result< - (UnsignedInvoiceRequest, Option, Option<&'b Secp256k1>), + fn build_with_checks($($self_mut)* $self: $self_type) -> Result< + (UnsignedInvoiceRequest, Option, Option<&'b Secp256k1<$secp_context>>), Bolt12SemanticError > { #[cfg(feature = "std")] { - if self.offer.is_expired() { + if $self.offer.is_expired() { return Err(Bolt12SemanticError::AlreadyExpired); } } - let chain = self.invoice_request.chain(); - if !self.offer.supports_chain(chain) { + let chain = $self.invoice_request.chain(); + if !$self.offer.supports_chain(chain) { return Err(Bolt12SemanticError::UnsupportedChain); } - if chain == self.offer.implied_chain() { - self.invoice_request.chain = None; + if chain == $self.offer.implied_chain() { + $self.invoice_request.chain = None; } - if self.offer.amount().is_none() && self.invoice_request.amount_msats.is_none() { + if $self.offer.amount().is_none() && $self.invoice_request.amount_msats.is_none() { return Err(Bolt12SemanticError::MissingAmount); } - self.invoice_request.offer.check_quantity(self.invoice_request.quantity)?; - self.invoice_request.offer.check_amount_msats_for_quantity( - self.invoice_request.amount_msats, self.invoice_request.quantity + $self.invoice_request.offer.check_quantity($self.invoice_request.quantity)?; + $self.invoice_request.offer.check_amount_msats_for_quantity( + $self.invoice_request.amount_msats, $self.invoice_request.quantity )?; - Ok(self.build_without_checks()) + Ok($self.build_without_checks()) } - fn build_without_checks(mut self) -> - (UnsignedInvoiceRequest, Option, Option<&'b Secp256k1>) + fn build_without_checks($($self_mut)* $self: $self_type) -> + (UnsignedInvoiceRequest, Option, Option<&'b Secp256k1<$secp_context>>) { // 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 metadata = core::mem::take(&mut self.invoice_request.payer.0); + let secp_ctx = $self.secp_ctx.clone(); + if $self.invoice_request.payer.0.has_derivation_material() { + let mut metadata = core::mem::take(&mut $self.invoice_request.payer.0); - let mut tlv_stream = self.invoice_request.as_tlv_stream(); + 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_payer_keys() { - tlv_stream.2.payer_id = self.payer_id.as_ref(); + tlv_stream.2.payer_id = $self.payer_id.as_ref(); } - let (derived_metadata, derived_keys) = metadata.derive_from(tlv_stream, self.secp_ctx); + let (derived_metadata, derived_keys) = metadata.derive_from(tlv_stream, $self.secp_ctx); metadata = derived_metadata; keys = derived_keys; if let Some(keys) = keys { - debug_assert!(self.payer_id.is_none()); - self.payer_id = Some(keys.public_key()); + debug_assert!($self.payer_id.is_none()); + $self.payer_id = Some(keys.public_key()); } - self.invoice_request.payer.0 = metadata; + $self.invoice_request.payer.0 = metadata; } - debug_assert!(self.invoice_request.payer.0.as_bytes().is_some()); - debug_assert!(self.payer_id.is_some()); - let payer_id = self.payer_id.unwrap(); + debug_assert!($self.invoice_request.payer.0.as_bytes().is_some()); + debug_assert!($self.payer_id.is_some()); + let payer_id = $self.payer_id.unwrap(); let invoice_request = InvoiceRequestContents { - inner: self.invoice_request, + #[cfg(not(c_bindings))] + inner: $self.invoice_request, + #[cfg(c_bindings)] + inner: $self.invoice_request.clone(), payer_id, }; - let unsigned_invoice_request = UnsignedInvoiceRequest::new(self.offer, invoice_request); + let unsigned_invoice_request = UnsignedInvoiceRequest::new($self.offer, invoice_request); (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 { - let (unsigned_invoice_request, keys, _) = self.build_with_checks()?; - debug_assert!(keys.is_none()); - Ok(unsigned_invoice_request) +#[cfg(test)] +macro_rules! invoice_request_builder_test_methods { ( + $self: ident, $self_type: ty, $return_type: ty, $return_value: expr $(, $self_mut: tt)? +) => { + #[cfg_attr(c_bindings, allow(dead_code))] + fn chain_unchecked($($self_mut)* $self: $self_type, network: Network) -> $return_type { + let chain = ChainHash::using_genesis_block(network); + $self.invoice_request.chain = Some(chain); + $return_value } -} -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 { - 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>( - |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) - ) - .unwrap(); - Ok(invoice_request) + #[cfg_attr(c_bindings, allow(dead_code))] + fn amount_msats_unchecked($($self_mut)* $self: $self_type, amount_msats: u64) -> $return_type { + $self.invoice_request.amount_msats = Some(amount_msats); + $return_value } -} -#[cfg(test)] -impl<'a, 'b, P: PayerIdStrategy, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, P, T> { - fn chain_unchecked(mut self, network: Network) -> Self { - let chain = ChainHash::using_genesis_block(network); - self.invoice_request.chain = Some(chain); - self + #[cfg_attr(c_bindings, allow(dead_code))] + fn features_unchecked($($self_mut)* $self: $self_type, features: InvoiceRequestFeatures) -> $return_type { + $self.invoice_request.features = features; + $return_value } - fn amount_msats_unchecked(mut self, amount_msats: u64) -> Self { - self.invoice_request.amount_msats = Some(amount_msats); - self + #[cfg_attr(c_bindings, allow(dead_code))] + fn quantity_unchecked($($self_mut)* $self: $self_type, quantity: u64) -> $return_type { + $self.invoice_request.quantity = Some(quantity); + $return_value } - fn features_unchecked(mut self, features: InvoiceRequestFeatures) -> Self { - self.invoice_request.features = features; - self + #[cfg_attr(c_bindings, allow(dead_code))] + pub(super) fn build_unchecked($self: $self_type) -> UnsignedInvoiceRequest { + $self.build_without_checks().0 } +} } + +impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, ExplicitPayerId, T> { + invoice_request_explicit_payer_id_builder_methods!(self, Self); +} + +impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, DerivedPayerId, T> { + invoice_request_derived_payer_id_builder_methods!(self, Self, T); +} + +impl<'a, 'b, P: PayerIdStrategy, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, P, T> { + invoice_request_builder_methods!(self, Self, Self, self, T, mut); + + #[cfg(test)] + invoice_request_builder_test_methods!(self, Self, Self, self, mut); +} + +#[cfg(all(c_bindings, not(test)))] +impl<'a, 'b> InvoiceRequestWithExplicitPayerIdBuilder<'a, 'b> { + invoice_request_explicit_payer_id_builder_methods!(self, &mut Self); + invoice_request_builder_methods!(self, &mut Self, (), (), secp256k1::All); +} + +#[cfg(all(c_bindings, test))] +impl<'a, 'b> InvoiceRequestWithExplicitPayerIdBuilder<'a, 'b> { + invoice_request_explicit_payer_id_builder_methods!(self, &mut Self); + invoice_request_builder_methods!(self, &mut Self, &mut Self, self, secp256k1::All); + invoice_request_builder_test_methods!(self, &mut Self, &mut Self, self); +} + +#[cfg(all(c_bindings, not(test)))] +impl<'a, 'b> InvoiceRequestWithDerivedPayerIdBuilder<'a, 'b> { + invoice_request_derived_payer_id_builder_methods!(self, &mut Self, secp256k1::All); + invoice_request_builder_methods!(self, &mut Self, (), (), secp256k1::All); +} + +#[cfg(all(c_bindings, test))] +impl<'a, 'b> InvoiceRequestWithDerivedPayerIdBuilder<'a, 'b> { + invoice_request_derived_payer_id_builder_methods!(self, &mut Self, secp256k1::All); + invoice_request_builder_methods!(self, &mut Self, &mut Self, self, secp256k1::All); + invoice_request_builder_test_methods!(self, &mut Self, &mut Self, self); +} - fn quantity_unchecked(mut self, quantity: u64) -> Self { - self.invoice_request.quantity = Some(quantity); - self +#[cfg(c_bindings)] +impl<'a, 'b> From> +for InvoiceRequestBuilder<'a, 'b, ExplicitPayerId, secp256k1::All> { + fn from(builder: InvoiceRequestWithExplicitPayerIdBuilder<'a, 'b>) -> Self { + let InvoiceRequestWithExplicitPayerIdBuilder { + offer, invoice_request, payer_id, payer_id_strategy, secp_ctx, + } = builder; + + Self { + offer, invoice_request, payer_id, payer_id_strategy, secp_ctx, + } } +} + +#[cfg(c_bindings)] +impl<'a, 'b> From> +for InvoiceRequestBuilder<'a, 'b, DerivedPayerId, secp256k1::All> { + fn from(builder: InvoiceRequestWithDerivedPayerIdBuilder<'a, 'b>) -> Self { + let InvoiceRequestWithDerivedPayerIdBuilder { + offer, invoice_request, payer_id, payer_id_strategy, secp_ctx, + } = builder; - pub(super) fn build_unchecked(self) -> UnsignedInvoiceRequest { - self.build_without_checks().0 + Self { + offer, invoice_request, payer_id, payer_id_strategy, secp_ctx, + } } } @@ -368,6 +493,30 @@ pub struct UnsignedInvoiceRequest { tagged_hash: TaggedHash, } +/// A function for signing an [`UnsignedInvoiceRequest`]. +pub trait SignInvoiceRequestFn { + /// Signs a [`TaggedHash`] computed over the merkle root of `message`'s TLV stream. + fn sign_invoice_request(&self, message: &UnsignedInvoiceRequest) -> Result; +} + +impl SignInvoiceRequestFn for F +where + F: Fn(&UnsignedInvoiceRequest) -> Result, +{ + fn sign_invoice_request(&self, message: &UnsignedInvoiceRequest) -> Result { + self(message) + } +} + +impl SignFn for F +where + F: SignInvoiceRequestFn, +{ + fn sign(&self, message: &UnsignedInvoiceRequest) -> Result { + self.sign_invoice_request(message) + } +} + 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 @@ -389,31 +538,48 @@ impl UnsignedInvoiceRequest { pub fn tagged_hash(&self) -> &TaggedHash { &self.tagged_hash } +} +macro_rules! unsigned_invoice_request_sign_method { ( + $self: ident, $self_type: ty $(, $self_mut: tt)? +) => { /// 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(mut self, sign: F) -> Result> - where - F: FnOnce(&Self) -> Result - { - let pubkey = self.contents.payer_id; - let signature = merkle::sign_message(sign, &self, pubkey)?; + pub fn sign( + $($self_mut)* $self: $self_type, sign: F + ) -> Result { + 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 self.bytes).unwrap(); + signature_tlv_stream.write(&mut $self.bytes).unwrap(); Ok(InvoiceRequest { - bytes: self.bytes, - contents: self.contents, + #[cfg(not(c_bindings))] + bytes: $self.bytes, + #[cfg(c_bindings)] + bytes: $self.bytes.clone(), + #[cfg(not(c_bindings))] + contents: $self.contents, + #[cfg(c_bindings)] + contents: $self.contents.clone(), signature, }) } +} } + +#[cfg(not(c_bindings))] +impl UnsignedInvoiceRequest { + unsigned_invoice_request_sign_method!(self, Self, mut); +} + +#[cfg(c_bindings)] +impl UnsignedInvoiceRequest { + unsigned_invoice_request_sign_method!(self, &mut Self); } impl AsRef for UnsignedInvoiceRequest { @@ -526,35 +692,25 @@ impl UnsignedInvoiceRequest { 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`]. - /// - /// [`payer_id`]: Self::payer_id - pub fn signature(&self) -> Signature { - self.signature - } - +macro_rules! invoice_request_respond_with_explicit_signing_pubkey_methods { ( + $self: ident, $contents: expr, $builder: ty +) => { /// 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 where the aforementioned /// creation time is used for the `created_at` parameter. /// - /// 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, Bolt12SemanticError> { + &$self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash + ) -> Result<$builder, 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.respond_with_no_std(payment_paths, payment_hash, created_at) + $contents.respond_with_no_std(payment_paths, payment_hash, created_at) } /// Creates an [`InvoiceBuilder`] for the request with the given required fields. @@ -578,36 +734,72 @@ impl InvoiceRequest { /// 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. - /// /// [`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, + &$self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash, created_at: core::time::Duration - ) -> Result, Bolt12SemanticError> { - if self.invoice_request_features().requires_unknown_bits() { + ) -> Result<$builder, Bolt12SemanticError> { + if $contents.invoice_request_features().requires_unknown_bits() { return Err(Bolt12SemanticError::UnknownRequiredFeatures); } - InvoiceBuilder::for_offer(self, payment_paths, created_at, payment_hash) + <$builder>::for_offer(&$contents, payment_paths, created_at, payment_hash) } +} } +macro_rules! invoice_request_verify_method { ($self: ident, $self_type: ty) => { /// 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( - self, key: &ExpandedKey, secp_ctx: &Secp256k1 + pub fn verify< + #[cfg(not(c_bindings))] + T: secp256k1::Signing + >( + $self: $self_type, key: &ExpandedKey, + #[cfg(not(c_bindings))] + secp_ctx: &Secp256k1, + #[cfg(c_bindings)] + secp_ctx: &Secp256k1, ) -> Result { - let keys = self.contents.inner.offer.verify(&self.bytes, key, secp_ctx)?; + let keys = $self.contents.inner.offer.verify(&$self.bytes, key, secp_ctx)?; Ok(VerifiedInvoiceRequest { - inner: self, + #[cfg(not(c_bindings))] + inner: $self, + #[cfg(c_bindings)] + inner: $self.clone(), keys, }) } +} } + +#[cfg(not(c_bindings))] +impl InvoiceRequest { + offer_accessors!(self, self.contents.inner.offer); + invoice_request_accessors!(self, self.contents); + invoice_request_respond_with_explicit_signing_pubkey_methods!(self, self, InvoiceBuilder); + invoice_request_verify_method!(self, Self); +} + +#[cfg(c_bindings)] +impl InvoiceRequest { + offer_accessors!(self, self.contents.inner.offer); + invoice_request_accessors!(self, self.contents); + invoice_request_respond_with_explicit_signing_pubkey_methods!(self, self, InvoiceWithExplicitSigningPubkeyBuilder); + invoice_request_verify_method!(self, &Self); +} + +impl InvoiceRequest { + /// Signature of the invoice request using [`payer_id`]. + /// + /// [`payer_id`]: Self::payer_id + pub fn signature(&self) -> Signature { + self.signature + } + pub(crate) fn as_tlv_stream(&self) -> FullInvoiceRequestTlvStreamRef { let (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream) = self.contents.as_tlv_stream(); @@ -618,55 +810,25 @@ impl InvoiceRequest { } } -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, 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, Bolt12SemanticError> { - self.inner.respond_with_no_std(payment_paths, payment_hash, created_at) - } - +macro_rules! invoice_request_respond_with_derived_signing_pubkey_methods { ( + $self: ident, $contents: expr, $builder: ty +) => { /// Creates an [`InvoiceBuilder`] for the request using the given required fields and that uses /// 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. - /// /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice #[cfg(feature = "std")] pub fn respond_using_derived_keys( - &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash - ) -> Result, Bolt12SemanticError> { + &$self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash + ) -> Result<$builder, 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.respond_using_derived_keys_no_std(payment_paths, payment_hash, created_at) + $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 @@ -675,26 +837,37 @@ impl VerifiedInvoiceRequest { /// /// 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. - /// /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice pub fn respond_using_derived_keys_no_std( - &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash, + &$self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash, created_at: core::time::Duration - ) -> Result, Bolt12SemanticError> { - if self.inner.invoice_request_features().requires_unknown_bits() { + ) -> Result<$builder, Bolt12SemanticError> { + if $self.inner.invoice_request_features().requires_unknown_bits() { return Err(Bolt12SemanticError::UnknownRequiredFeatures); } - let keys = match self.keys { + let keys = match $self.keys { None => return Err(Bolt12SemanticError::InvalidMetadata), Some(keys) => keys, }; - InvoiceBuilder::for_offer_using_keys( - &self.inner, payment_paths, created_at, payment_hash, keys + <$builder>::for_offer_using_keys( + &$self.inner, payment_paths, created_at, payment_hash, keys ) } +} } + +impl VerifiedInvoiceRequest { + offer_accessors!(self, self.inner.contents.inner.offer); + invoice_request_accessors!(self, self.inner.contents); + #[cfg(not(c_bindings))] + invoice_request_respond_with_explicit_signing_pubkey_methods!(self, self.inner, InvoiceBuilder); + #[cfg(c_bindings)] + invoice_request_respond_with_explicit_signing_pubkey_methods!(self, self.inner, InvoiceWithExplicitSigningPubkeyBuilder); + #[cfg(not(c_bindings))] + invoice_request_respond_with_derived_signing_pubkey_methods!(self, self.inner, InvoiceBuilder); + #[cfg(c_bindings)] + invoice_request_respond_with_derived_signing_pubkey_methods!(self, self.inner, InvoiceWithDerivedSigningPubkeyBuilder); } impl InvoiceRequestContents { @@ -930,7 +1103,7 @@ mod tests { use bitcoin::blockdata::constants::ChainHash; use bitcoin::network::constants::Network; use bitcoin::secp256k1::{KeyPair, Secp256k1, SecretKey, self}; - use core::convert::{Infallible, TryFrom}; + use core::convert::TryFrom; use core::num::NonZeroU64; #[cfg(feature = "std")] use core::time::Duration; @@ -941,7 +1114,15 @@ mod tests { use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT}; 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::offer::{Amount, OfferTlvStreamRef, Quantity}; + #[cfg(not(c_bindings))] + use { + crate::offers::offer::OfferBuilder, + }; + #[cfg(c_bindings)] + use { + crate::offers::offer::OfferWithExplicitMetadataBuilder as OfferBuilder, + }; use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError}; use crate::offers::payer::PayerTlvStreamRef; use crate::offers::test_utils::*; @@ -955,6 +1136,8 @@ mod tests { .build().unwrap() .request_invoice(vec![1; 32], payer_pubkey()).unwrap() .build().unwrap(); + #[cfg(c_bindings)] + let mut unsigned_invoice_request = unsigned_invoice_request; let mut buffer = Vec::new(); unsigned_invoice_request.write(&mut buffer).unwrap(); @@ -1550,10 +1733,10 @@ mod tests { .build().unwrap() .request_invoice(vec![1; 32], payer_pubkey()).unwrap() .build().unwrap() - .sign(|_| Err(())) + .sign(fail_sign) { Ok(_) => panic!("expected error"), - Err(e) => assert_eq!(e, SignError::Signing(())), + Err(e) => assert_eq!(e, SignError::Signing), } match OfferBuilder::new("foo".into(), recipient_pubkey()) @@ -1964,8 +2147,8 @@ mod tests { .build().unwrap() .request_invoice(vec![1; 32], keys.public_key()).unwrap() .build().unwrap() - .sign::<_, Infallible>( - |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) + .sign(|message: &UnsignedInvoiceRequest| + Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) ) .unwrap(); diff --git a/lightning/src/offers/merkle.rs b/lightning/src/offers/merkle.rs index ced3b08b7c2..941bf196716 100644 --- a/lightning/src/offers/merkle.rs +++ b/lightning/src/offers/merkle.rs @@ -76,13 +76,28 @@ impl AsRef for TaggedHash { /// Error when signing messages. #[derive(Debug, PartialEq)] -pub enum SignError { +pub enum SignError { /// User-defined error when signing the message. - Signing(E), + Signing, /// Error when verifying the produced signature using the given pubkey. Verification(secp256k1::Error), } +/// A function for signing a [`TaggedHash`]. +pub(super) trait SignFn> { + /// Signs a [`TaggedHash`] computed over the merkle root of `message`'s TLV stream. + fn sign(&self, message: &T) -> Result; +} + +impl SignFn for F +where + F: Fn(&TaggedHash) -> Result, +{ + fn sign(&self, message: &TaggedHash) -> Result { + self(message) + } +} + /// Signs a [`TaggedHash`] computed over the merkle root of `message`'s TLV stream, checking if it /// can be verified with the supplied `pubkey`. /// @@ -92,14 +107,14 @@ pub enum SignError { /// /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest -pub(super) fn sign_message( - sign: F, message: &T, pubkey: PublicKey, -) -> Result> +pub(super) fn sign_message( + f: F, message: &T, pubkey: PublicKey, +) -> Result where - F: FnOnce(&T) -> Result, + F: SignFn, T: AsRef, { - let signature = sign(message).map_err(|e| SignError::Signing(e))?; + let signature = f.sign(message).map_err(|()| SignError::Signing)?; let digest = message.as_ref().as_digest(); let pubkey = pubkey.into(); @@ -276,9 +291,8 @@ mod tests { use bitcoin::hashes::hex::FromHex; use bitcoin::secp256k1::{KeyPair, Message, Secp256k1, SecretKey}; use bitcoin::secp256k1::schnorr::Signature; - use core::convert::Infallible; use crate::offers::offer::{Amount, OfferBuilder}; - use crate::offers::invoice_request::InvoiceRequest; + use crate::offers::invoice_request::{InvoiceRequest, UnsignedInvoiceRequest}; use crate::offers::parse::Bech32Encode; use crate::offers::test_utils::{payer_pubkey, recipient_pubkey}; use crate::util::ser::Writeable; @@ -321,8 +335,8 @@ mod tests { .build_unchecked() .request_invoice(vec![0; 8], payer_keys.public_key()).unwrap() .build_unchecked() - .sign::<_, Infallible>( - |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &payer_keys)) + .sign(|message: &UnsignedInvoiceRequest| + Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &payer_keys)) ) .unwrap(); assert_eq!( @@ -375,8 +389,8 @@ mod tests { .build_unchecked() .request_invoice(vec![0; 8], payer_keys.public_key()).unwrap() .build_unchecked() - .sign::<_, Infallible>( - |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &payer_keys)) + .sign(|message: &UnsignedInvoiceRequest| + Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &payer_keys)) ) .unwrap(); @@ -407,8 +421,8 @@ mod tests { .build_unchecked() .request_invoice(vec![0; 8], payer_keys.public_key()).unwrap() .build_unchecked() - .sign::<_, Infallible>( - |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &payer_keys)) + .sign(|message: &UnsignedInvoiceRequest| + Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &payer_keys)) ) .unwrap(); diff --git a/lightning/src/offers/offer.rs b/lightning/src/offers/offer.rs index ab7fe62cb50..e9e35836a96 100644 --- a/lightning/src/offers/offer.rs +++ b/lightning/src/offers/offer.rs @@ -80,6 +80,7 @@ use bitcoin::blockdata::constants::ChainHash; use bitcoin::network::constants::Network; use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, self}; use core::convert::TryFrom; +use core::hash::{Hash, Hasher}; use core::num::NonZeroU64; use core::ops::Deref; use core::str::FromStr; @@ -91,13 +92,21 @@ 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, Bolt12ParseError, Bolt12SemanticError, ParsedMessage}; use crate::offers::signer::{Metadata, MetadataMaterial, self}; use crate::util::ser::{HighZeroBytesDroppedBigSize, WithoutLength, Writeable, Writer}; use crate::util::string::PrintableString; +#[cfg(not(c_bindings))] +use { + crate::offers::invoice_request::{DerivedPayerId, ExplicitPayerId, InvoiceRequestBuilder}, +}; +#[cfg(c_bindings)] +use { + crate::offers::invoice_request::{InvoiceRequestWithDerivedPayerIdBuilder, InvoiceRequestWithExplicitPayerIdBuilder}, +}; + use crate::prelude::*; #[cfg(feature = "std")] @@ -118,6 +127,34 @@ pub struct OfferBuilder<'a, M: MetadataStrategy, T: secp256k1::Signing> { secp_ctx: Option<&'a Secp256k1>, } +/// Builds an [`Offer`] for the "offer to be paid" flow. +/// +/// See [module-level documentation] for usage. +/// +/// This is not exported to bindings users as builder patterns don't map outside of move semantics. +/// +/// [module-level documentation]: self +#[cfg(c_bindings)] +pub struct OfferWithExplicitMetadataBuilder<'a> { + offer: OfferContents, + metadata_strategy: core::marker::PhantomData, + secp_ctx: Option<&'a Secp256k1>, +} + +/// Builds an [`Offer`] for the "offer to be paid" flow. +/// +/// See [module-level documentation] for usage. +/// +/// This is not exported to bindings users as builder patterns don't map outside of move semantics. +/// +/// [module-level documentation]: self +#[cfg(c_bindings)] +pub struct OfferWithDerivedMetadataBuilder<'a> { + offer: OfferContents, + metadata_strategy: core::marker::PhantomData, + secp_ctx: Option<&'a Secp256k1>, +} + /// Indicates how [`Offer::metadata`] may be set. /// /// This is not exported to bindings users as builder patterns don't map outside of move semantics. @@ -134,9 +171,12 @@ pub struct ExplicitMetadata {} pub struct DerivedMetadata {} impl MetadataStrategy for ExplicitMetadata {} + impl MetadataStrategy for DerivedMetadata {} -impl<'a> OfferBuilder<'a, ExplicitMetadata, secp256k1::SignOnly> { +macro_rules! offer_explicit_metadata_builder_methods { ( + $self: ident, $self_type: ty, $return_type: ty, $return_value: expr +) => { /// Creates a new builder for an offer setting the [`Offer::description`] and using the /// [`Offer::signing_pubkey`] for signing invoices. The associated secret key must be remembered /// while the offer is valid. @@ -151,7 +191,7 @@ impl<'a> OfferBuilder<'a, ExplicitMetadata, secp256k1::SignOnly> { /// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager /// [`ChannelManager::create_offer_builder`]: crate::ln::channelmanager::ChannelManager::create_offer_builder pub fn new(description: String, signing_pubkey: PublicKey) -> Self { - OfferBuilder { + Self { offer: OfferContents { chains: None, metadata: None, amount: None, description, features: OfferFeatures::empty(), absolute_expiry: None, issuer: None, paths: None, @@ -165,13 +205,13 @@ impl<'a> OfferBuilder<'a, ExplicitMetadata, secp256k1::SignOnly> { /// 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) -> Result { - self.offer.metadata = Some(Metadata::Bytes(metadata)); - Ok(self) + pub fn metadata(mut $self: $self_type, metadata: Vec) -> Result<$return_type, Bolt12SemanticError> { + $self.offer.metadata = Some(Metadata::Bytes(metadata)); + Ok($return_value) } -} +} } -impl<'a, T: secp256k1::Signing> OfferBuilder<'a, DerivedMetadata, T> { +macro_rules! offer_derived_metadata_builder_methods { ($secp_context: ty) => { /// Similar to [`OfferBuilder::new`] except, if [`OfferBuilder::path`] is called, the signing /// pubkey is derived from the given [`ExpandedKey`] and [`EntropySource`]. This provides /// recipient privacy by using a different signing pubkey for each offer. Otherwise, the @@ -185,12 +225,12 @@ impl<'a, T: secp256k1::Signing> OfferBuilder<'a, DerivedMetadata, T> { /// [`ExpandedKey`]: crate::ln::inbound_payment::ExpandedKey pub fn deriving_signing_pubkey( description: String, node_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES, - secp_ctx: &'a Secp256k1 + secp_ctx: &'a Secp256k1<$secp_context> ) -> Self where ES::Target: EntropySource { let nonce = Nonce::from_entropy_source(entropy_source); let derivation_material = MetadataMaterial::new(nonce, expanded_key, IV_BYTES, None); let metadata = Metadata::DerivedSigningPubkey(derivation_material); - OfferBuilder { + Self { offer: OfferContents { chains: None, metadata: Some(metadata), amount: None, description, features: OfferFeatures::empty(), absolute_expiry: None, issuer: None, paths: None, @@ -200,17 +240,19 @@ impl<'a, T: secp256k1::Signing> OfferBuilder<'a, DerivedMetadata, T> { secp_ctx: Some(secp_ctx), } } -} +} } -impl<'a, M: MetadataStrategy, T: secp256k1::Signing> OfferBuilder<'a, M, T> { +macro_rules! offer_builder_methods { ( + $self: ident, $self_type: ty, $return_type: ty, $return_value: expr $(, $self_mut: tt)? +) => { /// Adds the chain hash of the given [`Network`] to [`Offer::chains`]. If not called, /// the chain hash of [`Network::Bitcoin`] is assumed to be the only one supported. /// /// See [`Offer::chains`] on how this relates to the payment currency. /// /// Successive calls to this method will add another chain hash. - pub fn chain(self, network: Network) -> Self { - self.chain_hash(ChainHash::using_genesis_block(network)) + pub fn chain($self: $self_type, network: Network) -> $return_type { + $self.chain_hash(ChainHash::using_genesis_block(network)) } /// Adds the [`ChainHash`] to [`Offer::chains`]. If not called, the chain hash of @@ -219,45 +261,45 @@ impl<'a, M: MetadataStrategy, T: secp256k1::Signing> OfferBuilder<'a, M, T> { /// See [`Offer::chains`] on how this relates to the payment currency. /// /// Successive calls to this method will add another chain hash. - pub(crate) fn chain_hash(mut self, chain: ChainHash) -> Self { - let chains = self.offer.chains.get_or_insert_with(Vec::new); + pub(crate) fn chain_hash($($self_mut)* $self: $self_type, chain: ChainHash) -> $return_type { + let chains = $self.offer.chains.get_or_insert_with(Vec::new); if !chains.contains(&chain) { chains.push(chain); } - self + $return_value } /// Sets the [`Offer::amount`] as an [`Amount::Bitcoin`]. /// /// Successive calls to this method will override the previous setting. - pub fn amount_msats(self, amount_msats: u64) -> Self { - self.amount(Amount::Bitcoin { amount_msats }) + pub fn amount_msats($self: $self_type, amount_msats: u64) -> $return_type { + $self.amount(Amount::Bitcoin { amount_msats }) } /// Sets the [`Offer::amount`]. /// /// Successive calls to this method will override the previous setting. - pub(super) fn amount(mut self, amount: Amount) -> Self { - self.offer.amount = Some(amount); - self + pub(super) fn amount($($self_mut)* $self: $self_type, amount: Amount) -> $return_type { + $self.offer.amount = Some(amount); + $return_value } /// Sets the [`Offer::absolute_expiry`] as seconds since the Unix epoch. Any expiry that has /// already passed is valid and can be checked for using [`Offer::is_expired`]. /// /// Successive calls to this method will override the previous setting. - pub fn absolute_expiry(mut self, absolute_expiry: Duration) -> Self { - self.offer.absolute_expiry = Some(absolute_expiry); - self + pub fn absolute_expiry($($self_mut)* $self: $self_type, absolute_expiry: Duration) -> $return_type { + $self.offer.absolute_expiry = Some(absolute_expiry); + $return_value } /// Sets the [`Offer::issuer`]. /// /// Successive calls to this method will override the previous setting. - pub fn issuer(mut self, issuer: String) -> Self { - self.offer.issuer = Some(issuer); - self + pub fn issuer($($self_mut)* $self: $self_type, issuer: String) -> $return_type { + $self.offer.issuer = Some(issuer); + $return_value } /// Adds a blinded path to [`Offer::paths`]. Must include at least one path if only connected by @@ -265,23 +307,23 @@ impl<'a, M: MetadataStrategy, T: secp256k1::Signing> OfferBuilder<'a, M, T> { /// /// Successive calls to this method will add another blinded path. Caller is responsible for not /// adding duplicate paths. - pub fn path(mut self, path: BlindedPath) -> Self { - self.offer.paths.get_or_insert_with(Vec::new).push(path); - self + pub fn path($($self_mut)* $self: $self_type, path: BlindedPath) -> $return_type { + $self.offer.paths.get_or_insert_with(Vec::new).push(path); + $return_value } /// Sets the quantity of items for [`Offer::supported_quantity`]. If not called, defaults to /// [`Quantity::One`]. /// /// Successive calls to this method will override the previous setting. - pub fn supported_quantity(mut self, quantity: Quantity) -> Self { - self.offer.supported_quantity = quantity; - self + pub fn supported_quantity($($self_mut)* $self: $self_type, quantity: Quantity) -> $return_type { + $self.offer.supported_quantity = quantity; + $return_value } /// Builds an [`Offer`] from the builder's settings. - pub fn build(mut self) -> Result { - match self.offer.amount { + pub fn build($($self_mut)* $self: $self_type) -> Result { + match $self.offer.amount { Some(Amount::Bitcoin { amount_msats }) => { if amount_msats > MAX_VALUE_MSAT { return Err(Bolt12SemanticError::InvalidAmount); @@ -291,56 +333,123 @@ impl<'a, M: MetadataStrategy, T: secp256k1::Signing> OfferBuilder<'a, M, T> { None => {}, } - if let Some(chains) = &self.offer.chains { - if chains.len() == 1 && chains[0] == self.offer.implied_chain() { - self.offer.chains = None; + if let Some(chains) = &$self.offer.chains { + if chains.len() == 1 && chains[0] == $self.offer.implied_chain() { + $self.offer.chains = None; } } - Ok(self.build_without_checks()) + Ok($self.build_without_checks()) } - fn build_without_checks(mut self) -> Offer { + fn build_without_checks($($self_mut)* $self: $self_type) -> Offer { // Create the metadata for stateless verification of an InvoiceRequest. - if let Some(mut metadata) = self.offer.metadata.take() { + if let Some(mut metadata) = $self.offer.metadata.take() { if metadata.has_derivation_material() { - if self.offer.paths.is_none() { + if $self.offer.paths.is_none() { metadata = metadata.without_keys(); } - let mut tlv_stream = self.offer.as_tlv_stream(); + let mut tlv_stream = $self.offer.as_tlv_stream(); debug_assert_eq!(tlv_stream.metadata, None); tlv_stream.metadata = None; if metadata.derives_recipient_keys() { tlv_stream.node_id = None; } - let (derived_metadata, keys) = metadata.derive_from(tlv_stream, self.secp_ctx); + let (derived_metadata, keys) = metadata.derive_from(tlv_stream, $self.secp_ctx); metadata = derived_metadata; if let Some(keys) = keys { - self.offer.signing_pubkey = keys.public_key(); + $self.offer.signing_pubkey = keys.public_key(); } } - self.offer.metadata = Some(metadata); + $self.offer.metadata = Some(metadata); } let mut bytes = Vec::new(); - self.offer.write(&mut bytes).unwrap(); - - Offer { bytes, contents: self.offer } + $self.offer.write(&mut bytes).unwrap(); + + Offer { + bytes, + #[cfg(not(c_bindings))] + contents: $self.offer, + #[cfg(c_bindings)] + contents: $self.offer.clone() + } } -} +} } #[cfg(test)] -impl<'a, M: MetadataStrategy, T: secp256k1::Signing> OfferBuilder<'a, M, T> { - fn features_unchecked(mut self, features: OfferFeatures) -> Self { - self.offer.features = features; - self +macro_rules! offer_builder_test_methods { ( + $self: ident, $self_type: ty, $return_type: ty, $return_value: expr $(, $self_mut: tt)? +) => { + #[cfg_attr(c_bindings, allow(dead_code))] + fn features_unchecked($($self_mut)* $self: $self_type, features: OfferFeatures) -> $return_type { + $self.offer.features = features; + $return_value } - pub(super) fn build_unchecked(self) -> Offer { - self.build_without_checks() + #[cfg_attr(c_bindings, allow(dead_code))] + pub(crate) fn clear_paths($($self_mut)* $self: $self_type) -> $return_type { + $self.offer.paths = None; + $return_value + } + + #[cfg_attr(c_bindings, allow(dead_code))] + pub(super) fn build_unchecked($self: $self_type) -> Offer { + $self.build_without_checks() + } +} } + +impl<'a, M: MetadataStrategy, T: secp256k1::Signing> OfferBuilder<'a, M, T> { + offer_builder_methods!(self, Self, Self, self, mut); + + #[cfg(test)] + offer_builder_test_methods!(self, Self, Self, self, mut); +} + +impl<'a> OfferBuilder<'a, ExplicitMetadata, secp256k1::SignOnly> { + offer_explicit_metadata_builder_methods!(self, Self, Self, self); +} + +impl<'a, T: secp256k1::Signing> OfferBuilder<'a, DerivedMetadata, T> { + offer_derived_metadata_builder_methods!(T); +} + +#[cfg(all(c_bindings, not(test)))] +impl<'a> OfferWithExplicitMetadataBuilder<'a> { + offer_explicit_metadata_builder_methods!(self, &mut Self, (), ()); + offer_builder_methods!(self, &mut Self, (), ()); +} + +#[cfg(all(c_bindings, test))] +impl<'a> OfferWithExplicitMetadataBuilder<'a> { + offer_explicit_metadata_builder_methods!(self, &mut Self, &mut Self, self); + offer_builder_methods!(self, &mut Self, &mut Self, self); + offer_builder_test_methods!(self, &mut Self, &mut Self, self); +} + +#[cfg(all(c_bindings, not(test)))] +impl<'a> OfferWithDerivedMetadataBuilder<'a> { + offer_derived_metadata_builder_methods!(secp256k1::All); + offer_builder_methods!(self, &mut Self, (), ()); +} + +#[cfg(all(c_bindings, test))] +impl<'a> OfferWithDerivedMetadataBuilder<'a> { + offer_derived_metadata_builder_methods!(secp256k1::All); + offer_builder_methods!(self, &mut Self, &mut Self, self); + offer_builder_test_methods!(self, &mut Self, &mut Self, self); +} + +#[cfg(c_bindings)] +impl<'a> From> +for OfferWithDerivedMetadataBuilder<'a> { + fn from(builder: OfferBuilder<'a, DerivedMetadata, secp256k1::All>) -> Self { + let OfferBuilder { offer, metadata_strategy, secp_ctx } = builder; + + Self { offer, metadata_strategy, secp_ctx } } } @@ -358,7 +467,6 @@ impl<'a, M: MetadataStrategy, T: secp256k1::Signing> OfferBuilder<'a, M, T> { /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice #[derive(Clone, Debug)] -#[cfg_attr(test, derive(PartialEq))] pub struct Offer { // The serialized offer. Needed when creating an `InvoiceRequest` if the offer contains unknown // fields. @@ -484,7 +592,9 @@ impl Offer { pub fn expects_quantity(&self) -> bool { self.contents.expects_quantity() } +} +macro_rules! request_invoice_derived_payer_id { ($self: ident, $builder: ty) => { /// Similar to [`Offer::request_invoice`] except it: /// - derives the [`InvoiceRequest::payer_id`] such that a different key can be used for each /// request, @@ -496,50 +606,52 @@ impl Offer { /// /// 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::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, + pub fn request_invoice_deriving_payer_id< + 'a, 'b, ES: Deref, + #[cfg(not(c_bindings))] + T: secp256k1::Signing + >( + &'a $self, expanded_key: &ExpandedKey, entropy_source: ES, + #[cfg(not(c_bindings))] + secp_ctx: &'b Secp256k1, + #[cfg(c_bindings)] + secp_ctx: &'b Secp256k1, payment_id: PaymentId - ) -> Result, Bolt12SemanticError> + ) -> Result<$builder, Bolt12SemanticError> where ES::Target: EntropySource, { - if self.offer_features().requires_unknown_bits() { + if $self.offer_features().requires_unknown_bits() { return Err(Bolt12SemanticError::UnknownRequiredFeatures); } - Ok(InvoiceRequestBuilder::deriving_payer_id( - self, expanded_key, entropy_source, secp_ctx, payment_id - )) + Ok(<$builder>::deriving_payer_id($self, expanded_key, entropy_source, secp_ctx, payment_id)) } +} } +macro_rules! request_invoice_explicit_payer_id { ($self: ident, $builder: ty) => { /// Similar to [`Offer::request_invoice_deriving_payer_id`] except uses `payer_id` for the /// [`InvoiceRequest::payer_id`] instead of deriving a different key for each request. /// /// Useful for recurring payments using the same `payer_id` with different invoices. /// - /// 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 pub fn request_invoice_deriving_metadata( - &self, payer_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES, + &$self, payer_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES, payment_id: PaymentId - ) -> Result, Bolt12SemanticError> + ) -> Result<$builder, Bolt12SemanticError> where ES::Target: EntropySource, { - if self.offer_features().requires_unknown_bits() { + if $self.offer_features().requires_unknown_bits() { return Err(Bolt12SemanticError::UnknownRequiredFeatures); } - Ok(InvoiceRequestBuilder::deriving_metadata( - self, payer_id, expanded_key, entropy_source, payment_id - )) + Ok(<$builder>::deriving_metadata($self, payer_id, expanded_key, entropy_source, payment_id)) } /// Creates an [`InvoiceRequestBuilder`] for the offer with the given `metadata` and `payer_id`, @@ -554,20 +666,32 @@ impl Offer { /// /// Errors if the offer contains unknown required features. /// - /// This is not exported to bindings users as builder patterns don't map outside of move semantics. - /// /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest pub fn request_invoice( - &self, metadata: Vec, payer_id: PublicKey - ) -> Result, Bolt12SemanticError> { - if self.offer_features().requires_unknown_bits() { + &$self, metadata: Vec, payer_id: PublicKey + ) -> Result<$builder, Bolt12SemanticError> { + if $self.offer_features().requires_unknown_bits() { return Err(Bolt12SemanticError::UnknownRequiredFeatures); } - Ok(InvoiceRequestBuilder::new(self, metadata, payer_id)) + Ok(<$builder>::new($self, metadata, payer_id)) } +} } - #[cfg(test)] +#[cfg(not(c_bindings))] +impl Offer { + request_invoice_derived_payer_id!(self, InvoiceRequestBuilder<'a, 'b, DerivedPayerId, T>); + request_invoice_explicit_payer_id!(self, InvoiceRequestBuilder); +} + +#[cfg(c_bindings)] +impl Offer { + request_invoice_derived_payer_id!(self, InvoiceRequestWithDerivedPayerIdBuilder<'a, 'b>); + request_invoice_explicit_payer_id!(self, InvoiceRequestWithExplicitPayerIdBuilder); +} + +#[cfg(test)] +impl Offer { pub(super) fn as_tlv_stream(&self) -> OfferTlvStreamRef { self.contents.as_tlv_stream() } @@ -579,6 +703,20 @@ impl AsRef<[u8]> for Offer { } } +impl PartialEq for Offer { + fn eq(&self, other: &Self) -> bool { + self.bytes.eq(&other.bytes) + } +} + +impl Eq for Offer {} + +impl Hash for Offer { + fn hash(&self, state: &mut H) { + self.bytes.hash(state); + } +} + impl OfferContents { pub fn chains(&self) -> Vec { self.chains.as_ref().cloned().unwrap_or_else(|| vec![self.implied_chain()]) @@ -909,7 +1047,15 @@ impl core::fmt::Display for Offer { #[cfg(test)] mod tests { - use super::{Amount, Offer, OfferBuilder, OfferTlvStreamRef, Quantity}; + use super::{Amount, Offer, OfferTlvStreamRef, Quantity}; + #[cfg(not(c_bindings))] + use { + super::OfferBuilder, + }; + #[cfg(c_bindings)] + use { + super::OfferWithExplicitMetadataBuilder as OfferBuilder, + }; use bitcoin::blockdata::constants::ChainHash; use bitcoin::network::constants::Network; @@ -1038,6 +1184,8 @@ mod tests { let entropy = FixedEntropy {}; let secp_ctx = Secp256k1::new(); + #[cfg(c_bindings)] + use super::OfferWithDerivedMetadataBuilder as OfferBuilder; let offer = OfferBuilder ::deriving_signing_pubkey(desc, node_id, &expanded_key, &entropy, &secp_ctx) .amount_msats(1000) @@ -1094,6 +1242,8 @@ mod tests { ], }; + #[cfg(c_bindings)] + use super::OfferWithDerivedMetadataBuilder as OfferBuilder; let offer = OfferBuilder ::deriving_signing_pubkey(desc, node_id, &expanded_key, &entropy, &secp_ctx) .amount_msats(1000) @@ -1148,8 +1298,13 @@ mod tests { assert_eq!(tlv_stream.amount, Some(1000)); assert_eq!(tlv_stream.currency, None); + #[cfg(not(c_bindings))] let builder = OfferBuilder::new("foo".into(), pubkey(42)) .amount(currency_amount.clone()); + #[cfg(c_bindings)] + let mut builder = OfferBuilder::new("foo".into(), pubkey(42)); + #[cfg(c_bindings)] + builder.amount(currency_amount.clone()); let tlv_stream = builder.offer.as_tlv_stream(); assert_eq!(builder.offer.amount, Some(currency_amount.clone())); assert_eq!(tlv_stream.amount, Some(10)); diff --git a/lightning/src/offers/refund.rs b/lightning/src/offers/refund.rs index ce39841df8c..42177960868 100644 --- a/lightning/src/offers/refund.rs +++ b/lightning/src/offers/refund.rs @@ -85,6 +85,7 @@ use bitcoin::blockdata::constants::ChainHash; use bitcoin::network::constants::Network; use bitcoin::secp256k1::{PublicKey, Secp256k1, self}; use core::convert::TryFrom; +use core::hash::{Hash, Hasher}; use core::ops::Deref; use core::str::FromStr; use core::time::Duration; @@ -96,7 +97,7 @@ 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::BlindedPayInfo; use crate::offers::invoice_request::{InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef}; use crate::offers::offer::{OfferTlvStream, OfferTlvStreamRef}; use crate::offers::parse::{Bech32Encode, Bolt12ParseError, Bolt12SemanticError, ParsedMessage}; @@ -105,6 +106,15 @@ use crate::offers::signer::{Metadata, MetadataMaterial, self}; use crate::util::ser::{SeekReadable, WithoutLength, Writeable, Writer}; use crate::util::string::PrintableString; +#[cfg(not(c_bindings))] +use { + crate::offers::invoice::{DerivedSigningPubkey, ExplicitSigningPubkey, InvoiceBuilder}, +}; +#[cfg(c_bindings)] +use { + crate::offers::invoice::{InvoiceWithDerivedSigningPubkeyBuilder, InvoiceWithExplicitSigningPubkeyBuilder}, +}; + use crate::prelude::*; #[cfg(feature = "std")] @@ -124,7 +134,18 @@ pub struct RefundBuilder<'a, T: secp256k1::Signing> { secp_ctx: Option<&'a Secp256k1>, } -impl<'a> RefundBuilder<'a, secp256k1::SignOnly> { +/// Builds a [`Refund`] for the "offer for money" flow. +/// +/// See [module-level documentation] for usage. +/// +/// [module-level documentation]: self +#[cfg(c_bindings)] +pub struct RefundMaybeWithDerivedMetadataBuilder<'a> { + refund: RefundContents, + secp_ctx: Option<&'a Secp256k1>, +} + +macro_rules! refund_explicit_metadata_builder_methods { () => { /// 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. /// @@ -155,9 +176,11 @@ impl<'a> RefundBuilder<'a, secp256k1::SignOnly> { secp_ctx: None, }) } -} +} } -impl<'a, T: secp256k1::Signing> RefundBuilder<'a, T> { +macro_rules! refund_builder_methods { ( + $self: ident, $self_type: ty, $return_type: ty, $return_value: expr, $secp_context: ty $(, $self_mut: tt)? +) => { /// Similar to [`RefundBuilder::new`] except, if [`RefundBuilder::path`] is called, the payer id /// is derived from the given [`ExpandedKey`] and nonce. This provides sender privacy by using a /// different payer id for each refund, assuming a different nonce is used. Otherwise, the @@ -173,7 +196,7 @@ impl<'a, T: secp256k1::Signing> RefundBuilder<'a, T> { /// [`ExpandedKey`]: crate::ln::inbound_payment::ExpandedKey pub fn deriving_payer_id( description: String, node_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES, - secp_ctx: &'a Secp256k1, amount_msats: u64, payment_id: PaymentId + secp_ctx: &'a Secp256k1<$secp_context>, amount_msats: u64, payment_id: PaymentId ) -> Result where ES::Target: EntropySource { if amount_msats > MAX_VALUE_MSAT { return Err(Bolt12SemanticError::InvalidAmount); @@ -197,17 +220,17 @@ impl<'a, T: secp256k1::Signing> RefundBuilder<'a, T> { /// already passed is valid and can be checked for using [`Refund::is_expired`]. /// /// Successive calls to this method will override the previous setting. - pub fn absolute_expiry(mut self, absolute_expiry: Duration) -> Self { - self.refund.absolute_expiry = Some(absolute_expiry); - self + pub fn absolute_expiry($($self_mut)* $self: $self_type, absolute_expiry: Duration) -> $return_type { + $self.refund.absolute_expiry = Some(absolute_expiry); + $return_value } /// Sets the [`Refund::issuer`]. /// /// Successive calls to this method will override the previous setting. - pub fn issuer(mut self, issuer: String) -> Self { - self.refund.issuer = Some(issuer); - self + pub fn issuer($($self_mut)* $self: $self_type, issuer: String) -> $return_type { + $self.refund.issuer = Some(issuer); + $return_value } /// Adds a blinded path to [`Refund::paths`]. Must include at least one path if only connected @@ -215,26 +238,26 @@ impl<'a, T: secp256k1::Signing> RefundBuilder<'a, T> { /// /// Successive calls to this method will add another blinded path. Caller is responsible for not /// adding duplicate paths. - pub fn path(mut self, path: BlindedPath) -> Self { - self.refund.paths.get_or_insert_with(Vec::new).push(path); - self + pub fn path($($self_mut)* $self: $self_type, path: BlindedPath) -> $return_type { + $self.refund.paths.get_or_insert_with(Vec::new).push(path); + $return_value } /// Sets the [`Refund::chain`] of the given [`Network`] for paying an invoice. If not /// called, [`Network::Bitcoin`] is assumed. /// /// Successive calls to this method will override the previous setting. - pub fn chain(self, network: Network) -> Self { - self.chain_hash(ChainHash::using_genesis_block(network)) + pub fn chain($self: $self_type, network: Network) -> $return_type { + $self.chain_hash(ChainHash::using_genesis_block(network)) } /// Sets the [`Refund::chain`] of the given [`ChainHash`] for paying an invoice. If not called, /// [`Network::Bitcoin`] is assumed. /// /// Successive calls to this method will override the previous setting. - pub(crate) fn chain_hash(mut self, chain: ChainHash) -> Self { - self.refund.chain = Some(chain); - self + pub(crate) fn chain_hash($($self_mut)* $self: $self_type, chain: ChainHash) -> $return_type { + $self.refund.chain = Some(chain); + $return_value } /// Sets [`Refund::quantity`] of items. This is purely for informational purposes. It is useful @@ -246,60 +269,109 @@ impl<'a, T: secp256k1::Signing> RefundBuilder<'a, T> { /// [`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 { - self.refund.quantity = Some(quantity); - self + pub fn quantity($($self_mut)* $self: $self_type, quantity: u64) -> $return_type { + $self.refund.quantity = Some(quantity); + $return_value } /// Sets the [`Refund::payer_note`]. /// /// Successive calls to this method will override the previous setting. - pub fn payer_note(mut self, payer_note: String) -> Self { - self.refund.payer_note = Some(payer_note); - self + pub fn payer_note($($self_mut)* $self: $self_type, payer_note: String) -> $return_type { + $self.refund.payer_note = Some(payer_note); + $return_value } /// Builds a [`Refund`] after checking for valid semantics. - pub fn build(mut self) -> Result { - if self.refund.chain() == self.refund.implied_chain() { - self.refund.chain = None; + pub fn build($($self_mut)* $self: $self_type) -> Result { + if $self.refund.chain() == $self.refund.implied_chain() { + $self.refund.chain = None; } // 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); + if $self.refund.payer.0.has_derivation_material() { + let mut metadata = core::mem::take(&mut $self.refund.payer.0); - if self.refund.paths.is_none() { + if $self.refund.paths.is_none() { metadata = metadata.without_keys(); } - let mut tlv_stream = self.refund.as_tlv_stream(); + let mut tlv_stream = $self.refund.as_tlv_stream(); tlv_stream.0.metadata = None; if metadata.derives_payer_keys() { tlv_stream.2.payer_id = None; } - let (derived_metadata, keys) = metadata.derive_from(tlv_stream, self.secp_ctx); + let (derived_metadata, keys) = metadata.derive_from(tlv_stream, $self.secp_ctx); metadata = derived_metadata; if let Some(keys) = keys { - self.refund.payer_id = keys.public_key(); + $self.refund.payer_id = keys.public_key(); } - self.refund.payer.0 = metadata; + $self.refund.payer.0 = metadata; } let mut bytes = Vec::new(); - self.refund.write(&mut bytes).unwrap(); + $self.refund.write(&mut bytes).unwrap(); + + Ok(Refund { + bytes, + #[cfg(not(c_bindings))] + contents: $self.refund, + #[cfg(c_bindings)] + contents: $self.refund.clone(), + }) + } +} } + +#[cfg(test)] +macro_rules! refund_builder_test_methods { ( + $self: ident, $self_type: ty, $return_type: ty, $return_value: expr $(, $self_mut: tt)? +) => { + #[cfg_attr(c_bindings, allow(dead_code))] + pub(crate) fn clear_paths($($self_mut)* $self: $self_type) -> $return_type { + $self.refund.paths = None; + $return_value + } - Ok(Refund { bytes, contents: self.refund }) + #[cfg_attr(c_bindings, allow(dead_code))] + fn features_unchecked($($self_mut)* $self: $self_type, features: InvoiceRequestFeatures) -> $return_type { + $self.refund.features = features; + $return_value } +} } + +impl<'a> RefundBuilder<'a, secp256k1::SignOnly> { + refund_explicit_metadata_builder_methods!(); } -#[cfg(test)] impl<'a, T: secp256k1::Signing> RefundBuilder<'a, T> { - fn features_unchecked(mut self, features: InvoiceRequestFeatures) -> Self { - self.refund.features = features; - self + refund_builder_methods!(self, Self, Self, self, T, mut); + + #[cfg(test)] + refund_builder_test_methods!(self, Self, Self, self, mut); +} + +#[cfg(all(c_bindings, not(test)))] +impl<'a> RefundMaybeWithDerivedMetadataBuilder<'a> { + refund_explicit_metadata_builder_methods!(); + refund_builder_methods!(self, &mut Self, (), (), secp256k1::All); +} + +#[cfg(all(c_bindings, test))] +impl<'a> RefundMaybeWithDerivedMetadataBuilder<'a> { + refund_explicit_metadata_builder_methods!(); + refund_builder_methods!(self, &mut Self, &mut Self, self, secp256k1::All); + refund_builder_test_methods!(self, &mut Self, &mut Self, self); +} + +#[cfg(c_bindings)] +impl<'a> From> +for RefundMaybeWithDerivedMetadataBuilder<'a> { + fn from(builder: RefundBuilder<'a, secp256k1::All>) -> Self { + let RefundBuilder { refund, secp_ctx } = builder; + + Self { refund, secp_ctx } } } @@ -312,7 +384,6 @@ impl<'a, T: secp256k1::Signing> RefundBuilder<'a, T> { /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice /// [`Offer`]: crate::offers::offer::Offer #[derive(Clone, Debug)] -#[cfg_attr(test, derive(PartialEq))] pub struct Refund { pub(super) bytes: Vec, pub(super) contents: RefundContents, @@ -418,7 +489,9 @@ impl Refund { pub fn payer_note(&self) -> Option { self.contents.payer_note() } +} +macro_rules! respond_with_explicit_signing_pubkey_methods { ($self: ident, $builder: ty) => { /// Creates an [`InvoiceBuilder`] for the refund with the given required fields and using the /// [`Duration`] since [`std::time::SystemTime::UNIX_EPOCH`] as the creation time. /// @@ -430,14 +503,14 @@ impl Refund { /// [`Duration`]: core::time::Duration #[cfg(feature = "std")] pub fn respond_with( - &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash, + &$self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash, signing_pubkey: PublicKey, - ) -> Result, Bolt12SemanticError> { + ) -> Result<$builder, 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.respond_with_no_std(payment_paths, payment_hash, signing_pubkey, created_at) + $self.respond_with_no_std(payment_paths, payment_hash, signing_pubkey, created_at) } /// Creates an [`InvoiceBuilder`] for the refund with the given required fields. @@ -463,16 +536,18 @@ impl Refund { /// /// [`Bolt12Invoice::created_at`]: crate::offers::invoice::Bolt12Invoice::created_at pub fn respond_with_no_std( - &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash, + &$self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash, signing_pubkey: PublicKey, created_at: Duration - ) -> Result, Bolt12SemanticError> { - if self.features().requires_unknown_bits() { + ) -> Result<$builder, Bolt12SemanticError> { + if $self.features().requires_unknown_bits() { return Err(Bolt12SemanticError::UnknownRequiredFeatures); } - InvoiceBuilder::for_refund(self, payment_paths, created_at, payment_hash, signing_pubkey) + <$builder>::for_refund($self, payment_paths, created_at, payment_hash, signing_pubkey) } +} } +macro_rules! respond_with_derived_signing_pubkey_methods { ($self: ident, $builder: ty) => { /// Creates an [`InvoiceBuilder`] for the refund using the given required fields and that uses /// derived signing keys to sign the [`Bolt12Invoice`]. /// @@ -483,9 +558,9 @@ impl Refund { /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice #[cfg(feature = "std")] pub fn respond_using_derived_keys( - &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash, + &$self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash, expanded_key: &ExpandedKey, entropy_source: ES - ) -> Result, Bolt12SemanticError> + ) -> Result<$builder, Bolt12SemanticError> where ES::Target: EntropySource, { @@ -493,7 +568,7 @@ impl Refund { .duration_since(std::time::SystemTime::UNIX_EPOCH) .expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH"); - self.respond_using_derived_keys_no_std( + $self.respond_using_derived_keys_no_std( payment_paths, payment_hash, created_at, expanded_key, entropy_source ) } @@ -507,22 +582,36 @@ impl Refund { /// /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice pub fn respond_using_derived_keys_no_std( - &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash, + &$self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash, created_at: core::time::Duration, expanded_key: &ExpandedKey, entropy_source: ES - ) -> Result, Bolt12SemanticError> + ) -> Result<$builder, Bolt12SemanticError> where ES::Target: EntropySource, { - if self.features().requires_unknown_bits() { + if $self.features().requires_unknown_bits() { return Err(Bolt12SemanticError::UnknownRequiredFeatures); } let nonce = Nonce::from_entropy_source(entropy_source); let keys = signer::derive_keys(nonce, expanded_key); - InvoiceBuilder::for_refund_using_keys(self, payment_paths, created_at, payment_hash, keys) + <$builder>::for_refund_using_keys($self, payment_paths, created_at, payment_hash, keys) } +} } - #[cfg(test)] +#[cfg(not(c_bindings))] +impl Refund { + respond_with_explicit_signing_pubkey_methods!(self, InvoiceBuilder); + respond_with_derived_signing_pubkey_methods!(self, InvoiceBuilder); +} + +#[cfg(c_bindings)] +impl Refund { + respond_with_explicit_signing_pubkey_methods!(self, InvoiceWithExplicitSigningPubkeyBuilder); + respond_with_derived_signing_pubkey_methods!(self, InvoiceWithDerivedSigningPubkeyBuilder); +} + +#[cfg(test)] +impl Refund { fn as_tlv_stream(&self) -> RefundTlvStreamRef { self.contents.as_tlv_stream() } @@ -534,6 +623,20 @@ impl AsRef<[u8]> for Refund { } } +impl PartialEq for Refund { + fn eq(&self, other: &Self) -> bool { + self.bytes.eq(&other.bytes) + } +} + +impl Eq for Refund {} + +impl Hash for Refund { + fn hash(&self, state: &mut H) { + self.bytes.hash(state); + } +} + impl RefundContents { pub fn description(&self) -> PrintableString { PrintableString(&self.description) @@ -778,7 +881,15 @@ impl core::fmt::Display for Refund { #[cfg(test)] mod tests { - use super::{Refund, RefundBuilder, RefundTlvStreamRef}; + use super::{Refund, RefundTlvStreamRef}; + #[cfg(not(c_bindings))] + use { + super::RefundBuilder, + }; + #[cfg(c_bindings)] + use { + super::RefundMaybeWithDerivedMetadataBuilder as RefundBuilder, + }; use bitcoin::blockdata::constants::ChainHash; use bitcoin::network::constants::Network; diff --git a/lightning/src/offers/test_utils.rs b/lightning/src/offers/test_utils.rs index 39122472eac..29bed53d83f 100644 --- a/lightning/src/offers/test_utils.rs +++ b/lightning/src/offers/test_utils.rs @@ -11,7 +11,7 @@ use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey}; use bitcoin::secp256k1::schnorr::Signature; -use core::convert::{AsRef, Infallible}; +use core::convert::AsRef; use core::time::Duration; use crate::blinded_path::{BlindedHop, BlindedPath}; use crate::sign::EntropySource; @@ -20,12 +20,16 @@ use crate::ln::features::BlindedHopFeatures; use crate::offers::invoice::BlindedPayInfo; use crate::offers::merkle::TaggedHash; +pub(crate) fn fail_sign>(_message: &T) -> Result { + Err(()) +} + pub(crate) fn payer_keys() -> KeyPair { let secp_ctx = Secp256k1::new(); KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap()) } -pub(crate) fn payer_sign>(message: &T) -> Result { +pub(crate) fn payer_sign>(message: &T) -> Result { 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(message.as_ref().as_digest(), &keys)) @@ -40,7 +44,7 @@ pub(crate) fn recipient_keys() -> KeyPair { KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[43; 32]).unwrap()) } -pub(crate) fn recipient_sign>(message: &T) -> Result { +pub(crate) fn recipient_sign>(message: &T) -> Result { 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(message.as_ref().as_digest(), &keys)) diff --git a/lightning/src/onion_message/functional_tests.rs b/lightning/src/onion_message/functional_tests.rs index 9323be5fff2..271b56dc0c9 100644 --- a/lightning/src/onion_message/functional_tests.rs +++ b/lightning/src/onion_message/functional_tests.rs @@ -16,11 +16,13 @@ use crate::ln::msgs::{self, DecodeError, OnionMessageHandler, SocketAddress}; use crate::sign::{NodeSigner, Recipient}; use crate::util::ser::{FixedLengthReader, LengthReadable, Writeable, Writer}; use crate::util::test_utils; -use super::{CustomOnionMessageHandler, Destination, MessageRouter, OffersMessage, OffersMessageHandler, OnionMessageContents, OnionMessagePath, OnionMessenger, PendingOnionMessage, SendError}; +use super::messenger::{CustomOnionMessageHandler, Destination, MessageRouter, OnionMessagePath, OnionMessenger, PendingOnionMessage, SendError}; +use super::offers::{OffersMessage, OffersMessageHandler}; +use super::packet::{OnionMessageContents, Packet}; use bitcoin::network::constants::Network; use bitcoin::hashes::hex::FromHex; -use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; +use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey, self}; use crate::io; use crate::io_extras::read_to_end; @@ -55,6 +57,14 @@ impl MessageRouter for TestMessageRouter { Some(vec![SocketAddress::TcpIpV4 { addr: [127, 0, 0, 1], port: 1000 }]), }) } + + fn create_blinded_paths< + T: secp256k1::Signing + secp256k1::Verification + >( + &self, _recipient: PublicKey, _peers: Vec, _secp_ctx: &Secp256k1, + ) -> Result, ()> { + unreachable!() + } } struct TestOffersMessageHandler {} @@ -562,8 +572,8 @@ fn spec_test_vector() { 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 = - ::read(&mut packet_reader).unwrap(); + let sender_to_alice_packet: Packet = + ::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(&>::from_hex("6363636363636363636363636363636363636363636363636363636363636363").unwrap()).unwrap()), diff --git a/lightning/src/onion_message/messenger.rs b/lightning/src/onion_message/messenger.rs index 21a1b302da0..37de5504532 100644 --- a/lightning/src/onion_message/messenger.rs +++ b/lightning/src/onion_message/messenger.rs @@ -7,8 +7,8 @@ // You may not use this file except in accordance with one or both of these // licenses. -//! LDK sends, receives, and forwards onion messages via the [`OnionMessenger`]. See its docs for -//! more information. +//! LDK sends, receives, and forwards onion messages via this [`OnionMessenger`], which lives here, +//! as well as various types, traits, and utilities that it uses. use bitcoin::hashes::{Hash, HashEngine}; use bitcoin::hashes::hmac::{Hmac, HmacEngine}; @@ -19,15 +19,12 @@ use crate::blinded_path::BlindedPath; use crate::blinded_path::message::{advance_path_by_one, ForwardTlvs, ReceiveTlvs}; use crate::blinded_path::utils; use crate::events::{Event, EventHandler, EventsProvider}; -use crate::sign::{EntropySource, KeysManager, NodeSigner, Recipient}; -#[cfg(not(c_bindings))] -use crate::ln::channelmanager::{SimpleArcChannelManager, SimpleRefChannelManager}; +use crate::sign::{EntropySource, NodeSigner, Recipient}; use crate::ln::features::{InitFeatures, NodeFeatures}; use crate::ln::msgs::{self, OnionMessage, OnionMessageHandler, SocketAddress}; use crate::ln::onion_utils; -use crate::ln::peer_handler::IgnoringMessageHandler; use crate::routing::gossip::{NetworkGraph, NodeId}; -pub use super::packet::OnionMessageContents; +use super::packet::OnionMessageContents; use super::packet::ParsedOnionMessageContents; use super::offers::OffersMessageHandler; use super::packet::{BIG_PACKET_HOP_DATA_LEN, ForwardControlTlvs, Packet, Payload, ReceiveControlTlvs, SMALL_PACKET_HOP_DATA_LEN}; @@ -37,9 +34,17 @@ use crate::util::ser::Writeable; use core::fmt; use core::ops::Deref; use crate::io; -use crate::sync::{Arc, Mutex}; +use crate::sync::Mutex; use crate::prelude::*; +#[cfg(not(c_bindings))] +use { + crate::sign::KeysManager, + crate::ln::channelmanager::{SimpleArcChannelManager, SimpleRefChannelManager}, + crate::ln::peer_handler::IgnoringMessageHandler, + crate::sync::Arc, +}; + pub(super) const MAX_TIMER_TICKS: usize = 2; /// A sender, receiver and forwarder of [`OnionMessage`]s. @@ -64,11 +69,12 @@ pub(super) const MAX_TIMER_TICKS: usize = 2; /// # extern crate bitcoin; /// # use bitcoin::hashes::_export::_core::time::Duration; /// # use bitcoin::hashes::hex::FromHex; -/// # use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; +/// # use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey, self}; /// # use lightning::blinded_path::BlindedPath; -/// # use lightning::sign::KeysManager; +/// # use lightning::sign::{EntropySource, KeysManager}; /// # use lightning::ln::peer_handler::IgnoringMessageHandler; -/// # use lightning::onion_message::{OnionMessageContents, Destination, MessageRouter, OnionMessagePath, OnionMessenger}; +/// # use lightning::onion_message::messenger::{Destination, MessageRouter, OnionMessagePath, OnionMessenger}; +/// # use lightning::onion_message::packet::OnionMessageContents; /// # use lightning::util::logger::{Logger, Record}; /// # use lightning::util::ser::{Writeable, Writer}; /// # use lightning::io; @@ -90,6 +96,11 @@ pub(super) const MAX_TIMER_TICKS: usize = 2; /// # first_node_addresses: None, /// # }) /// # } +/// # fn create_blinded_paths( +/// # &self, _recipient: PublicKey, _peers: Vec, _secp_ctx: &Secp256k1 +/// # ) -> Result, ()> { +/// # unreachable!() +/// # } /// # } /// # let seed = [42u8; 32]; /// # let time = Duration::from_secs(123456); @@ -253,7 +264,7 @@ pub struct PendingOnionMessage { /// /// These are obtained when released from [`OnionMessenger`]'s handlers after which they are /// enqueued for sending. -pub type PendingOnionMessage = (T, Destination, Option); +pub type PendingOnionMessage = (T, Destination, Option); pub(crate) fn new_pending_onion_message( contents: T, destination: Destination, reply_path: Option @@ -270,29 +281,41 @@ pub trait MessageRouter { fn find_path( &self, sender: PublicKey, peers: Vec, destination: Destination ) -> Result; + + /// Creates [`BlindedPath`]s to the `recipient` node. The nodes in `peers` are assumed to be + /// direct peers with the `recipient`. + fn create_blinded_paths< + T: secp256k1::Signing + secp256k1::Verification + >( + &self, recipient: PublicKey, peers: Vec, secp_ctx: &Secp256k1, + ) -> Result, ()>; } /// A [`MessageRouter`] that can only route to a directly connected [`Destination`]. -pub struct DefaultMessageRouter>, L: Deref> +pub struct DefaultMessageRouter>, L: Deref, ES: Deref> where L::Target: Logger, + ES::Target: EntropySource, { network_graph: G, + entropy_source: ES, } -impl>, L: Deref> DefaultMessageRouter +impl>, L: Deref, ES: Deref> DefaultMessageRouter where L::Target: Logger, + ES::Target: EntropySource, { /// Creates a [`DefaultMessageRouter`] using the given [`NetworkGraph`]. - pub fn new(network_graph: G) -> Self { - Self { network_graph } + pub fn new(network_graph: G, entropy_source: ES) -> Self { + Self { network_graph, entropy_source } } } -impl>, L: Deref> MessageRouter for DefaultMessageRouter +impl>, L: Deref, ES: Deref> MessageRouter for DefaultMessageRouter where L::Target: Logger, + ES::Target: EntropySource, { fn find_path( &self, _sender: PublicKey, peers: Vec, destination: Destination @@ -321,6 +344,58 @@ where } } } + + fn create_blinded_paths< + T: secp256k1::Signing + secp256k1::Verification + >( + &self, recipient: PublicKey, peers: Vec, secp_ctx: &Secp256k1, + ) -> Result, ()> { + // Limit the number of blinded paths that are computed. + const MAX_PATHS: usize = 3; + + // Ensure peers have at least three channels so that it is more difficult to infer the + // recipient's node_id. + const MIN_PEER_CHANNELS: usize = 3; + + let network_graph = self.network_graph.deref().read_only(); + let is_recipient_announced = + network_graph.nodes().contains_key(&NodeId::from_pubkey(&recipient)); + + let mut peer_info = peers.iter() + // Limit to peers with announced channels + .filter_map(|pubkey| + network_graph + .node(&NodeId::from_pubkey(pubkey)) + .filter(|info| info.channels.len() >= MIN_PEER_CHANNELS) + .map(|info| (*pubkey, info.is_tor_only(), info.channels.len())) + ) + // Exclude Tor-only nodes when the recipient is announced. + .filter(|(_, is_tor_only, _)| !(*is_tor_only && is_recipient_announced)) + .collect::>(); + + // Prefer using non-Tor nodes with the most channels as the introduction node. + peer_info.sort_unstable_by(|(_, a_tor_only, a_channels), (_, b_tor_only, b_channels)| { + a_tor_only.cmp(b_tor_only).then(a_channels.cmp(b_channels).reverse()) + }); + + let paths = peer_info.into_iter() + .map(|(pubkey, _, _)| vec![pubkey, recipient]) + .map(|node_pks| BlindedPath::new_for_message(&node_pks, &*self.entropy_source, secp_ctx)) + .take(MAX_PATHS) + .collect::, _>>(); + + match paths { + Ok(paths) if !paths.is_empty() => Ok(paths), + _ => { + if is_recipient_announced { + BlindedPath::one_hop_for_message(recipient, &*self.entropy_source, secp_ctx) + .map(|path| vec![path]) + } else { + Err(()) + } + }, + } + } } /// A path for sending an [`OnionMessage`]. @@ -350,7 +425,7 @@ impl OnionMessagePath { } /// The destination of an onion message. -#[derive(Clone)] +#[derive(Clone, Hash, Debug, PartialEq, Eq)] pub enum Destination { /// We're sending this onion message to a node. Node(PublicKey), @@ -377,7 +452,7 @@ impl Destination { /// Result of successfully [sending an onion message]. /// /// [sending an onion message]: OnionMessenger::send_onion_message -#[derive(Debug, PartialEq, Eq)] +#[derive(Clone, Hash, Debug, PartialEq, Eq)] pub enum SendSuccess { /// The message was buffered and will be sent once it is processed by /// [`OnionMessageHandler::next_onion_message_for_peer`]. @@ -390,7 +465,7 @@ pub enum SendSuccess { /// Errors that may occur when [sending an onion message]. /// /// [sending an onion message]: OnionMessenger::send_onion_message -#[derive(Debug, PartialEq, Eq)] +#[derive(Clone, Hash, Debug, PartialEq, Eq)] pub enum SendError { /// Errored computing onion message packet keys. Secp256k1(secp256k1::Error), @@ -460,6 +535,7 @@ pub trait CustomOnionMessageHandler { /// A processed incoming onion message, containing either a Forward (another onion message) /// or a Receive payload with decrypted contents. +#[derive(Debug)] pub enum PeeledOnion { /// Forwarded onion, with the next node id and a new onion Forward(PublicKey, OnionMessage), @@ -643,7 +719,7 @@ where OnionMessenger { entropy_source, node_signer, - message_recipients: Mutex::new(HashMap::new()), + message_recipients: Mutex::new(new_hash_map()), secp_ctx, logger, message_router, @@ -652,6 +728,11 @@ where } } + #[cfg(test)] + pub(crate) fn set_offers_handler(&mut self, offers_handler: OMH) { + self.offers_handler = offers_handler; + } + /// Sends an [`OnionMessage`] with the given `contents` to `destination`. /// /// See [`OnionMessenger`] for example usage. @@ -745,13 +826,21 @@ where } } - #[cfg(test)] - pub(super) fn send_onion_message_using_path( + #[cfg(any(test, feature = "_test_utils"))] + pub fn send_onion_message_using_path( &self, path: OnionMessagePath, contents: T, reply_path: Option ) -> Result { self.enqueue_onion_message(path, contents, reply_path, format_args!("")) } + pub(crate) fn peel_onion_message( + &self, msg: &OnionMessage + ) -> Result::Target as CustomOnionMessageHandler>::CustomMessage>, ()> { + peel_onion_message( + msg, &self.secp_ctx, &*self.node_signer, &*self.logger, &*self.custom_handler + ) + } + fn handle_onion_message_response( &self, response: Option, reply_path: Option, log_suffix: fmt::Arguments ) { @@ -772,7 +861,7 @@ where #[cfg(test)] pub(super) fn release_pending_msgs(&self) -> HashMap> { let mut message_recipients = self.message_recipients.lock().unwrap(); - let mut msgs = HashMap::new(); + let mut msgs = new_hash_map(); // We don't want to disconnect the peers by removing them entirely from the original map, so we // release the pending message buffers individually. for (node_id, recipient) in &mut *message_recipients { @@ -837,9 +926,7 @@ where CMH::Target: CustomOnionMessageHandler, { fn handle_onion_message(&self, _peer_node_id: &PublicKey, msg: &OnionMessage) { - match peel_onion_message( - msg, &self.secp_ctx, &*self.node_signer, &*self.logger, &*self.custom_handler - ) { + match self.peel_onion_message(msg) { Ok(PeeledOnion::Receive(message, path_id, reply_path)) => { log_trace!( self.logger, @@ -914,7 +1001,8 @@ where fn peer_disconnected(&self, their_node_id: &PublicKey) { match self.message_recipients.lock().unwrap().remove(their_node_id) { Some(OnionMessageRecipient::ConnectedPeer(..)) => {}, - _ => debug_assert!(false), + Some(_) => debug_assert!(false), + None => {}, } } @@ -996,7 +1084,7 @@ pub type SimpleArcOnionMessenger = OnionMessenger< Arc, Arc, Arc, - Arc>>, Arc>>, + Arc>>, Arc, Arc>>, Arc>, IgnoringMessageHandler >; @@ -1015,7 +1103,7 @@ pub type SimpleRefOnionMessenger< &'a KeysManager, &'a KeysManager, &'b L, - &'i DefaultMessageRouter<&'g NetworkGraph<&'b L>, &'b L>, + &'i DefaultMessageRouter<&'g NetworkGraph<&'b L>, &'b L, &'a KeysManager>, &'j SimpleRefChannelManager<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, M, T, F, L>, IgnoringMessageHandler >; diff --git a/lightning/src/onion_message/mod.rs b/lightning/src/onion_message/mod.rs index d106a542fd3..05a8b7d6fbd 100644 --- a/lightning/src/onion_message/mod.rs +++ b/lightning/src/onion_message/mod.rs @@ -19,19 +19,10 @@ //! //! [offers]: //! [blinded paths]: crate::blinded_path::BlindedPath +//! [`OnionMessenger`]: self::messenger::OnionMessenger -mod messenger; -mod offers; -mod packet; +pub mod messenger; +pub mod offers; +pub mod packet; #[cfg(test)] mod functional_tests; - -// Re-export structs so they can be imported with just the `onion_message::` module prefix. -pub use self::messenger::{CustomOnionMessageHandler, DefaultMessageRouter, Destination, MessageRouter, OnionMessageContents, OnionMessagePath, OnionMessenger, PeeledOnion, PendingOnionMessage, SendError}; -pub use self::messenger::{create_onion_message, peel_onion_message}; -#[cfg(not(c_bindings))] -pub use self::messenger::{SimpleArcOnionMessenger, SimpleRefOnionMessenger}; -pub use self::offers::{OffersMessage, OffersMessageHandler}; -pub use self::packet::{Packet, ParsedOnionMessageContents}; -pub(crate) use self::packet::ControlTlvs; -pub(crate) use self::messenger::new_pending_onion_message; diff --git a/lightning/src/onion_message/offers.rs b/lightning/src/onion_message/offers.rs index fb6ff746717..6672b5dfcdd 100644 --- a/lightning/src/onion_message/offers.rs +++ b/lightning/src/onion_message/offers.rs @@ -17,10 +17,11 @@ use crate::offers::invoice_error::InvoiceError; use crate::offers::invoice_request::InvoiceRequest; use crate::offers::invoice::Bolt12Invoice; use crate::offers::parse::Bolt12ParseError; -use crate::onion_message::OnionMessageContents; -use crate::onion_message::messenger::PendingOnionMessage; +use crate::onion_message::packet::OnionMessageContents; use crate::util::logger::Logger; use crate::util::ser::{Readable, ReadableArgs, Writeable, Writer}; +#[cfg(not(c_bindings))] +use crate::onion_message::messenger::PendingOnionMessage; use crate::prelude::*; @@ -38,7 +39,7 @@ pub trait OffersMessageHandler { /// /// The returned [`OffersMessage`], if any, is enqueued to be sent by [`OnionMessenger`]. /// - /// [`OnionMessenger`]: crate::onion_message::OnionMessenger + /// [`OnionMessenger`]: crate::onion_message::messenger::OnionMessenger fn handle_message(&self, message: OffersMessage) -> Option; /// Releases any [`OffersMessage`]s that need to be sent. @@ -53,7 +54,7 @@ pub trait OffersMessageHandler { /// Typically, this is used for messages initiating a payment flow rather than in response to /// another message. The latter should use the return value of [`Self::handle_message`]. #[cfg(c_bindings)] - fn release_pending_messages(&self) -> Vec<(OffersMessage, crate::onion_message::Destination, Option)> { vec![] } + fn release_pending_messages(&self) -> Vec<(OffersMessage, crate::onion_message::messenger::Destination, Option)> { vec![] } } /// Possible BOLT 12 Offers messages sent and received via an [`OnionMessage`]. diff --git a/lightning/src/onion_message/packet.rs b/lightning/src/onion_message/packet.rs index 5ce02c54d08..d9349fdadbf 100644 --- a/lightning/src/onion_message/packet.rs +++ b/lightning/src/onion_message/packet.rs @@ -19,7 +19,7 @@ use crate::ln::msgs::DecodeError; use crate::ln::onion_utils; use super::messenger::CustomOnionMessageHandler; use super::offers::OffersMessage; -use crate::util::chacha20poly1305rfc::{ChaChaPolyReadAdapter, ChaChaPolyWriteAdapter}; +use crate::crypto::streams::{ChaChaPolyReadAdapter, ChaChaPolyWriteAdapter}; use crate::util::logger::Logger; use crate::util::ser::{BigSize, FixedLengthReader, LengthRead, LengthReadable, LengthReadableArgs, Readable, ReadableArgs, Writeable, Writer}; diff --git a/lightning/src/routing/gossip.rs b/lightning/src/routing/gossip.rs index 9b4e41ae174..806aadd583c 100644 --- a/lightning/src/routing/gossip.rs +++ b/lightning/src/routing/gossip.rs @@ -698,7 +698,7 @@ where U::Target: UtxoLookup, L::Target: Logger // Prior replies should use the number of blocks that fit into the reply. Overflow // safe since first_blocknum is always <= last SCID's block. else { - (false, block_from_scid(batch.last().unwrap()) - first_blocknum) + (false, block_from_scid(*batch.last().unwrap()) - first_blocknum) }; prev_batch_endblock = first_blocknum + number_of_blocks; @@ -1032,13 +1032,13 @@ impl<'a> DirectedChannelInfo<'a> { /// /// Refers to the `node_id` forwarding the payment to the next hop. #[inline] - pub(super) fn source(&self) -> &'a NodeId { if self.from_node_one { &self.channel.node_one } else { &self.channel.node_two } } + pub fn source(&self) -> &'a NodeId { if self.from_node_one { &self.channel.node_one } else { &self.channel.node_two } } /// Returns the `node_id` of the target hop. /// /// Refers to the `node_id` receiving the payment from the previous hop. #[inline] - pub(super) fn target(&self) -> &'a NodeId { if self.from_node_one { &self.channel.node_two } else { &self.channel.node_one } } + pub fn target(&self) -> &'a NodeId { if self.from_node_one { &self.channel.node_two } else { &self.channel.node_one } } } impl<'a> fmt::Debug for DirectedChannelInfo<'a> { @@ -1231,6 +1231,18 @@ pub struct NodeInfo { pub announcement_info: Option } +impl NodeInfo { + /// Returns whether the node has only announced Tor addresses. + pub fn is_tor_only(&self) -> bool { + self.announcement_info + .as_ref() + .map(|info| info.addresses()) + .and_then(|addresses| (!addresses.is_empty()).then(|| addresses)) + .map(|addresses| addresses.iter().all(|address| address.is_tor())) + .unwrap_or(false) + } +} + impl fmt::Display for NodeInfo { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { write!(f, " channels: {:?}, announcement_info: {:?}", @@ -1353,8 +1365,8 @@ impl ReadableArgs for NetworkGraph where L::Target: Logger { channels: RwLock::new(channels), nodes: RwLock::new(nodes), last_rapid_gossip_sync_timestamp: Mutex::new(last_rapid_gossip_sync_timestamp), - removed_nodes: Mutex::new(HashMap::new()), - removed_channels: Mutex::new(HashMap::new()), + removed_nodes: Mutex::new(new_hash_map()), + removed_channels: Mutex::new(new_hash_map()), pending_checks: utxo::PendingChecks::new(), }) } @@ -1398,8 +1410,8 @@ impl NetworkGraph where L::Target: Logger { channels: RwLock::new(IndexedMap::new()), nodes: RwLock::new(IndexedMap::new()), last_rapid_gossip_sync_timestamp: Mutex::new(None), - removed_channels: Mutex::new(HashMap::new()), - removed_nodes: Mutex::new(HashMap::new()), + removed_channels: Mutex::new(new_hash_map()), + removed_nodes: Mutex::new(new_hash_map()), pending_checks: utxo::PendingChecks::new(), } } @@ -1845,7 +1857,7 @@ impl NetworkGraph where L::Target: Logger { // NOTE: In the case of no-std, we won't have access to the current UNIX time at the time of removal, // so we'll just set the removal time here to the current UNIX time on the very next invocation // of this function. - #[cfg(feature = "no-std")] + #[cfg(not(feature = "std"))] { let mut tracked_time = Some(current_time_unix); core::mem::swap(time, &mut tracked_time); @@ -1925,7 +1937,10 @@ impl NetworkGraph where L::Target: Logger { None => { core::mem::drop(channels); self.pending_checks.check_hold_pending_channel_update(msg, full_msg)?; - return Err(LightningError{err: "Couldn't find channel for update".to_owned(), action: ErrorAction::IgnoreError}); + return Err(LightningError { + err: "Couldn't find channel for update".to_owned(), + action: ErrorAction::IgnoreAndLog(Level::Gossip), + }); }, Some(channel) => { if msg.htlc_maximum_msat > MAX_VALUE_MSAT { @@ -2086,6 +2101,7 @@ pub(crate) mod tests { use crate::ln::chan_utils::make_funding_redeemscript; #[cfg(feature = "std")] use crate::ln::features::InitFeatures; + use crate::ln::msgs::SocketAddress; use crate::routing::gossip::{P2PGossipSync, NetworkGraph, NetworkUpdate, NodeAlias, MAX_EXCESS_BYTES_FOR_RELAY, NodeId, RoutingFees, ChannelUpdateInfo, ChannelInfo, NodeAnnouncementInfo, NodeInfo}; use crate::routing::utxo::{UtxoLookupError, UtxoResult}; use crate::ln::msgs::{RoutingMessageHandler, UnsignedNodeAnnouncement, NodeAnnouncement, @@ -2093,7 +2109,7 @@ pub(crate) mod tests { ReplyChannelRange, QueryChannelRange, QueryShortChannelIds, MAX_VALUE_MSAT}; use crate::util::config::UserConfig; use crate::util::test_utils; - use crate::util::ser::{ReadableArgs, Readable, Writeable}; + use crate::util::ser::{Hostname, ReadableArgs, Readable, Writeable}; use crate::util::scid_utils::scid_from_parts; use crate::routing::gossip::REMOVED_ENTRIES_TRACKING_AGE_LIMIT_SECS; @@ -3471,6 +3487,112 @@ pub(crate) mod tests { let node_id = NodeId([42; 33]); assert_eq!(format!("{}", &node_id), "2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a"); } + + #[test] + fn is_tor_only_node() { + let network_graph = create_network_graph(); + let (secp_ctx, gossip_sync) = create_gossip_sync(&network_graph); + + let node_1_privkey = &SecretKey::from_slice(&[42; 32]).unwrap(); + let node_2_privkey = &SecretKey::from_slice(&[41; 32]).unwrap(); + let node_1_id = NodeId::from_pubkey(&PublicKey::from_secret_key(&secp_ctx, node_1_privkey)); + + let announcement = get_signed_channel_announcement(|_| {}, node_1_privkey, node_2_privkey, &secp_ctx); + gossip_sync.handle_channel_announcement(&announcement).unwrap(); + + let tcp_ip_v4 = SocketAddress::TcpIpV4 { + addr: [255, 254, 253, 252], + port: 9735 + }; + let tcp_ip_v6 = SocketAddress::TcpIpV6 { + addr: [255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 245, 244, 243, 242, 241, 240], + port: 9735 + }; + let onion_v2 = SocketAddress::OnionV2([255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 38, 7]); + let onion_v3 = 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, + port: 9735 + }; + let hostname = SocketAddress::Hostname { + hostname: Hostname::try_from(String::from("host")).unwrap(), + port: 9735, + }; + + assert!(!network_graph.read_only().node(&node_1_id).unwrap().is_tor_only()); + + let announcement = get_signed_node_announcement(|_| {}, node_1_privkey, &secp_ctx); + gossip_sync.handle_node_announcement(&announcement).unwrap(); + assert!(!network_graph.read_only().node(&node_1_id).unwrap().is_tor_only()); + + let announcement = get_signed_node_announcement( + |announcement| { + announcement.addresses = vec![ + tcp_ip_v4.clone(), tcp_ip_v6.clone(), onion_v2.clone(), onion_v3.clone(), + hostname.clone() + ]; + announcement.timestamp += 1000; + }, + node_1_privkey, &secp_ctx + ); + gossip_sync.handle_node_announcement(&announcement).unwrap(); + assert!(!network_graph.read_only().node(&node_1_id).unwrap().is_tor_only()); + + let announcement = get_signed_node_announcement( + |announcement| { + announcement.addresses = vec![ + tcp_ip_v4.clone(), tcp_ip_v6.clone(), onion_v2.clone(), onion_v3.clone() + ]; + announcement.timestamp += 2000; + }, + node_1_privkey, &secp_ctx + ); + gossip_sync.handle_node_announcement(&announcement).unwrap(); + assert!(!network_graph.read_only().node(&node_1_id).unwrap().is_tor_only()); + + let announcement = get_signed_node_announcement( + |announcement| { + announcement.addresses = vec![ + tcp_ip_v6.clone(), onion_v2.clone(), onion_v3.clone() + ]; + announcement.timestamp += 3000; + }, + node_1_privkey, &secp_ctx + ); + gossip_sync.handle_node_announcement(&announcement).unwrap(); + assert!(!network_graph.read_only().node(&node_1_id).unwrap().is_tor_only()); + + let announcement = get_signed_node_announcement( + |announcement| { + announcement.addresses = vec![onion_v2.clone(), onion_v3.clone()]; + announcement.timestamp += 4000; + }, + node_1_privkey, &secp_ctx + ); + gossip_sync.handle_node_announcement(&announcement).unwrap(); + assert!(network_graph.read_only().node(&node_1_id).unwrap().is_tor_only()); + + let announcement = get_signed_node_announcement( + |announcement| { + announcement.addresses = vec![onion_v2.clone()]; + announcement.timestamp += 5000; + }, + node_1_privkey, &secp_ctx + ); + gossip_sync.handle_node_announcement(&announcement).unwrap(); + assert!(network_graph.read_only().node(&node_1_id).unwrap().is_tor_only()); + + let announcement = get_signed_node_announcement( + |announcement| { + announcement.addresses = vec![tcp_ip_v4.clone()]; + announcement.timestamp += 6000; + }, + node_1_privkey, &secp_ctx + ); + gossip_sync.handle_node_announcement(&announcement).unwrap(); + assert!(!network_graph.read_only().node(&node_1_id).unwrap().is_tor_only()); + } } #[cfg(ldk_bench)] diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs index 9423078749d..8d37182266b 100644 --- a/lightning/src/routing/router.rs +++ b/lightning/src/routing/router.rs @@ -9,55 +9,59 @@ //! The router finds paths within a [`NetworkGraph`] for a payment. -use bitcoin::secp256k1::PublicKey; -use bitcoin::hashes::Hash; -use bitcoin::hashes::sha256::Hash as Sha256; +use bitcoin::secp256k1::{PublicKey, Secp256k1, self}; use crate::blinded_path::{BlindedHop, BlindedPath}; +use crate::blinded_path::payment::{ForwardNode, ForwardTlvs, PaymentConstraints, PaymentRelay, ReceiveTlvs}; use crate::ln::PaymentHash; -use crate::ln::channelmanager::{ChannelDetails, PaymentId}; -use crate::ln::features::{Bolt11InvoiceFeatures, Bolt12InvoiceFeatures, ChannelFeatures, NodeFeatures}; +use crate::ln::channelmanager::{ChannelDetails, PaymentId, MIN_FINAL_CLTV_EXPIRY_DELTA}; +use crate::ln::features::{BlindedHopFeatures, Bolt11InvoiceFeatures, Bolt12InvoiceFeatures, ChannelFeatures, NodeFeatures}; use crate::ln::msgs::{DecodeError, ErrorAction, LightningError, MAX_VALUE_MSAT}; use crate::offers::invoice::{BlindedPayInfo, Bolt12Invoice}; +use crate::onion_message::messenger::{DefaultMessageRouter, Destination, MessageRouter, OnionMessagePath}; use crate::routing::gossip::{DirectedChannelInfo, EffectiveCapacity, ReadOnlyNetworkGraph, NetworkGraph, NodeId, RoutingFees}; use crate::routing::scoring::{ChannelUsage, LockableScore, ScoreLookUp}; +use crate::sign::EntropySource; use crate::util::ser::{Writeable, Readable, ReadableArgs, Writer}; use crate::util::logger::{Level, Logger}; -use crate::util::chacha20::ChaCha20; +use crate::crypto::chacha20::ChaCha20; use crate::io; use crate::prelude::*; -use crate::sync::Mutex; use alloc::collections::BinaryHeap; use core::{cmp, fmt}; use core::ops::Deref; /// A [`Router`] implemented using [`find_route`]. -pub struct DefaultRouter>, L: Deref, S: Deref, SP: Sized, Sc: ScoreLookUp> where +pub struct DefaultRouter> + Clone, L: Deref, ES: Deref, S: Deref, SP: Sized, Sc: ScoreLookUp> where L::Target: Logger, S::Target: for <'a> LockableScore<'a, ScoreLookUp = Sc>, + ES::Target: EntropySource, { network_graph: G, logger: L, - random_seed_bytes: Mutex<[u8; 32]>, + entropy_source: ES, scorer: S, - score_params: SP + score_params: SP, + message_router: DefaultMessageRouter, } -impl>, L: Deref, S: Deref, SP: Sized, Sc: ScoreLookUp> DefaultRouter where +impl> + Clone, L: Deref, ES: Deref + Clone, S: Deref, SP: Sized, Sc: ScoreLookUp> DefaultRouter where L::Target: Logger, S::Target: for <'a> LockableScore<'a, ScoreLookUp = Sc>, + ES::Target: EntropySource, { /// Creates a new router. - pub fn new(network_graph: G, logger: L, random_seed_bytes: [u8; 32], scorer: S, score_params: SP) -> Self { - let random_seed_bytes = Mutex::new(random_seed_bytes); - Self { network_graph, logger, random_seed_bytes, scorer, score_params } + pub fn new(network_graph: G, logger: L, entropy_source: ES, scorer: S, score_params: SP) -> Self { + let message_router = DefaultMessageRouter::new(network_graph.clone(), entropy_source.clone()); + Self { network_graph, logger, entropy_source, scorer, score_params, message_router } } } -impl< G: Deref>, L: Deref, S: Deref, SP: Sized, Sc: ScoreLookUp> Router for DefaultRouter where +impl> + Clone, L: Deref, ES: Deref, S: Deref, SP: Sized, Sc: ScoreLookUp> Router for DefaultRouter where L::Target: Logger, S::Target: for <'a> LockableScore<'a, ScoreLookUp = Sc>, + ES::Target: EntropySource, { fn find_route( &self, @@ -66,11 +70,7 @@ impl< G: Deref>, L: Deref, S: Deref, SP: Sized, Sc: Sco first_hops: Option<&[&ChannelDetails]>, inflight_htlcs: InFlightHtlcs ) -> Result { - let random_seed_bytes = { - let mut locked_random_seed_bytes = self.random_seed_bytes.lock().unwrap(); - *locked_random_seed_bytes = Sha256::hash(&*locked_random_seed_bytes).to_byte_array(); - *locked_random_seed_bytes - }; + let random_seed_bytes = self.entropy_source.get_secure_random_bytes(); find_route( payer, params, &self.network_graph, first_hops, &*self.logger, &ScorerAccountingForInFlightHtlcs::new(self.scorer.read_lock(), &inflight_htlcs), @@ -78,10 +78,106 @@ impl< G: Deref>, L: Deref, S: Deref, SP: Sized, Sc: Sco &random_seed_bytes ) } + + fn create_blinded_payment_paths< + T: secp256k1::Signing + secp256k1::Verification + > ( + &self, recipient: PublicKey, first_hops: Vec, tlvs: ReceiveTlvs, + amount_msats: u64, secp_ctx: &Secp256k1 + ) -> Result, ()> { + // Limit the number of blinded paths that are computed. + const MAX_PAYMENT_PATHS: usize = 3; + + // Ensure peers have at least three channels so that it is more difficult to infer the + // recipient's node_id. + const MIN_PEER_CHANNELS: usize = 3; + + let network_graph = self.network_graph.deref().read_only(); + let paths = first_hops.into_iter() + .filter(|details| details.counterparty.features.supports_route_blinding()) + .filter(|details| amount_msats <= details.inbound_capacity_msat) + .filter(|details| amount_msats >= details.inbound_htlc_minimum_msat.unwrap_or(0)) + .filter(|details| amount_msats <= details.inbound_htlc_maximum_msat.unwrap_or(u64::MAX)) + .filter(|details| network_graph + .node(&NodeId::from_pubkey(&details.counterparty.node_id)) + .map(|node_info| node_info.channels.len() >= MIN_PEER_CHANNELS) + .unwrap_or(false) + ) + .filter_map(|details| { + let short_channel_id = match details.get_inbound_payment_scid() { + Some(short_channel_id) => short_channel_id, + None => return None, + }; + let payment_relay: PaymentRelay = match details.counterparty.forwarding_info { + Some(forwarding_info) => match forwarding_info.try_into() { + Ok(payment_relay) => payment_relay, + Err(()) => return None, + }, + None => return None, + }; + + let cltv_expiry_delta = payment_relay.cltv_expiry_delta as u32; + let payment_constraints = PaymentConstraints { + max_cltv_expiry: tlvs.payment_constraints.max_cltv_expiry + cltv_expiry_delta, + htlc_minimum_msat: details.inbound_htlc_minimum_msat.unwrap_or(0), + }; + Some(ForwardNode { + tlvs: ForwardTlvs { + short_channel_id, + payment_relay, + payment_constraints, + features: BlindedHopFeatures::empty(), + }, + node_id: details.counterparty.node_id, + htlc_maximum_msat: details.inbound_htlc_maximum_msat.unwrap_or(u64::MAX), + }) + }) + .map(|forward_node| { + BlindedPath::new_for_payment( + &[forward_node], recipient, tlvs.clone(), u64::MAX, MIN_FINAL_CLTV_EXPIRY_DELTA, + &*self.entropy_source, secp_ctx + ) + }) + .take(MAX_PAYMENT_PATHS) + .collect::, _>>(); + + match paths { + Ok(paths) if !paths.is_empty() => Ok(paths), + _ => { + if network_graph.nodes().contains_key(&NodeId::from_pubkey(&recipient)) { + BlindedPath::one_hop_for_payment( + recipient, tlvs, MIN_FINAL_CLTV_EXPIRY_DELTA, &*self.entropy_source, secp_ctx + ).map(|path| vec![path]) + } else { + Err(()) + } + }, + } + } +} + +impl< G: Deref> + Clone, L: Deref, ES: Deref, S: Deref, SP: Sized, Sc: ScoreLookUp> MessageRouter for DefaultRouter where + L::Target: Logger, + S::Target: for <'a> LockableScore<'a, ScoreLookUp = Sc>, + ES::Target: EntropySource, +{ + fn find_path( + &self, sender: PublicKey, peers: Vec, destination: Destination + ) -> Result { + self.message_router.find_path(sender, peers, destination) + } + + fn create_blinded_paths< + T: secp256k1::Signing + secp256k1::Verification + > ( + &self, recipient: PublicKey, peers: Vec, secp_ctx: &Secp256k1, + ) -> Result, ()> { + self.message_router.create_blinded_paths(recipient, peers, secp_ctx) + } } /// A trait defining behavior for routing a payment. -pub trait Router { +pub trait Router: MessageRouter { /// 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`] @@ -105,6 +201,16 @@ pub trait Router { ) -> Result { self.find_route(payer, route_params, first_hops, inflight_htlcs) } + + /// Creates [`BlindedPath`]s for payment to the `recipient` node. The channels in `first_hops` + /// are assumed to be with the `recipient`'s peers. The payment secret and any constraints are + /// given in `tlvs`. + fn create_blinded_payment_paths< + T: secp256k1::Signing + secp256k1::Verification + > ( + &self, recipient: PublicKey, first_hops: Vec, tlvs: ReceiveTlvs, + amount_msats: u64, secp_ctx: &Secp256k1 + ) -> Result, ()>; } /// [`ScoreLookUp`] implementation that factors in in-flight HTLC liquidity. @@ -168,7 +274,7 @@ pub struct InFlightHtlcs( impl InFlightHtlcs { /// Constructs an empty `InFlightHtlcs`. - pub fn new() -> Self { InFlightHtlcs(HashMap::new()) } + pub fn new() -> Self { InFlightHtlcs(new_hash_map()) } /// Takes in a path with payer's node id and adds the path's details to `InFlightHtlcs`. pub fn process_path(&mut self, path: &Path, payer_node_id: PublicKey) { @@ -399,20 +505,20 @@ impl Writeable for Route { write_ver_prefix!(writer, SERIALIZATION_VERSION, MIN_SERIALIZATION_VERSION); (self.paths.len() as u64).write(writer)?; let mut blinded_tails = Vec::new(); - for path in self.paths.iter() { + for (idx, path) in self.paths.iter().enumerate() { (path.hops.len() as u8).write(writer)?; - for (idx, hop) in path.hops.iter().enumerate() { + for hop in path.hops.iter() { hop.write(writer)?; - if let Some(blinded_tail) = &path.blinded_tail { - if blinded_tails.is_empty() { - blinded_tails = Vec::with_capacity(path.hops.len()); - for _ in 0..idx { - blinded_tails.push(None); - } - } - blinded_tails.push(Some(blinded_tail)); - } else if !blinded_tails.is_empty() { blinded_tails.push(None); } } + if let Some(blinded_tail) = &path.blinded_tail { + if blinded_tails.is_empty() { + blinded_tails = Vec::with_capacity(path.hops.len()); + for _ in 0..idx { + blinded_tails.push(None); + } + } + blinded_tails.push(Some(blinded_tail)); + } else if !blinded_tails.is_empty() { blinded_tails.push(None); } } write_tlv_fields!(writer, { // For compatibility with LDK versions prior to 0.0.117, we take the individual @@ -420,7 +526,7 @@ impl Writeable for Route { (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), + (5, self.route_params.as_ref().and_then(|p| p.max_total_routing_fee_msat), option), }); Ok(()) } @@ -597,6 +703,11 @@ pub struct PaymentParameters { /// payment to fail. Future attempts for the same payment shouldn't be relayed through any of /// these SCIDs. pub previously_failed_channels: Vec, + + /// A list of indices corresponding to blinded paths in [`Payee::Blinded::route_hints`] which this + /// payment was previously attempted over and which caused the payment to fail. Future attempts + /// for the same payment shouldn't be relayed through any of these blinded paths. + pub previously_failed_blinded_path_idxs: Vec, } impl Writeable for PaymentParameters { @@ -618,6 +729,7 @@ impl Writeable for PaymentParameters { (7, self.previously_failed_channels, required_vec), (8, *blinded_hints, optional_vec), (9, self.payee.final_cltv_expiry_delta(), option), + (11, self.previously_failed_blinded_path_idxs, required_vec), }); Ok(()) } @@ -636,6 +748,7 @@ impl ReadableArgs for PaymentParameters { (7, previously_failed_channels, optional_vec), (8, blinded_route_hints, optional_vec), (9, final_cltv_expiry_delta, (default_value, default_final_cltv_expiry_delta)), + (11, previously_failed_blinded_path_idxs, optional_vec), }); let blinded_route_hints = blinded_route_hints.unwrap_or(vec![]); let payee = if blinded_route_hints.len() != 0 { @@ -659,6 +772,7 @@ impl ReadableArgs for PaymentParameters { max_channel_saturation_power_of_half: _init_tlv_based_struct_field!(max_channel_saturation_power_of_half, (default_value, unused)), expiry_time, previously_failed_channels: previously_failed_channels.unwrap_or(Vec::new()), + previously_failed_blinded_path_idxs: previously_failed_blinded_path_idxs.unwrap_or(Vec::new()), }) } } @@ -677,6 +791,7 @@ impl PaymentParameters { max_path_count: DEFAULT_MAX_PATH_COUNT, max_channel_saturation_power_of_half: DEFAULT_MAX_CHANNEL_SATURATION_POW_HALF, previously_failed_channels: Vec::new(), + previously_failed_blinded_path_idxs: Vec::new(), } } @@ -715,6 +830,7 @@ impl PaymentParameters { max_path_count: DEFAULT_MAX_PATH_COUNT, max_channel_saturation_power_of_half: DEFAULT_MAX_CHANNEL_SATURATION_POW_HALF, previously_failed_channels: Vec::new(), + previously_failed_blinded_path_idxs: Vec::new(), } } @@ -790,6 +906,19 @@ impl PaymentParameters { pub fn with_max_channel_saturation_power_of_half(self, max_channel_saturation_power_of_half: u8) -> Self { Self { max_channel_saturation_power_of_half, ..self } } + + pub(crate) fn insert_previously_failed_blinded_path(&mut self, failed_blinded_tail: &BlindedTail) { + let mut found_blinded_tail = false; + for (idx, (_, path)) in self.payee.blinded_route_hints().iter().enumerate() { + if failed_blinded_tail.hops == path.blinded_hops && + failed_blinded_tail.blinding_point == path.blinding_point + { + self.previously_failed_blinded_path_idxs.push(idx as u64); + found_blinded_tail = true; + } + } + debug_assert!(found_blinded_tail); + } } /// The recipient of a payment, differing based on whether they've hidden their identity with route @@ -857,7 +986,7 @@ impl Payee { _ => None, } } - fn blinded_route_hints(&self) -> &[(BlindedPayInfo, BlindedPath)] { + pub(crate) fn blinded_route_hints(&self) -> &[(BlindedPayInfo, BlindedPath)] { match self { Self::Blinded { route_hints, .. } => &route_hints[..], Self::Clear { .. } => &[] @@ -1005,6 +1134,82 @@ const _GRAPH_NODE_SMALL: usize = 64 - core::mem::size_of::(); #[cfg(any(ldk_bench, not(any(test, fuzzing))))] const _GRAPH_NODE_FIXED_SIZE: usize = core::mem::size_of::() - 64; +/// A [`CandidateRouteHop::FirstHop`] entry. +#[derive(Clone, Debug)] +pub struct FirstHopCandidate<'a> { + /// Channel details of the first hop + /// + /// [`ChannelDetails::get_outbound_payment_scid`] MUST be `Some` (indicating the channel + /// has been funded and is able to pay), and accessor methods may panic otherwise. + /// + /// [`find_route`] validates this prior to constructing a [`CandidateRouteHop`]. + /// + /// This is not exported to bindings users as lifetimes are not expressable in most languages. + pub details: &'a ChannelDetails, + /// The node id of the payer, which is also the source side of this candidate route hop. + /// + /// This is not exported to bindings users as lifetimes are not expressable in most languages. + pub payer_node_id: &'a NodeId, +} + +/// A [`CandidateRouteHop::PublicHop`] entry. +#[derive(Clone, Debug)] +pub struct PublicHopCandidate<'a> { + /// Information about the channel, including potentially its capacity and + /// direction-specific information. + /// + /// This is not exported to bindings users as lifetimes are not expressable in most languages. + pub info: DirectedChannelInfo<'a>, + /// The short channel ID of the channel, i.e. the identifier by which we refer to this + /// channel. + pub short_channel_id: u64, +} + +/// A [`CandidateRouteHop::PrivateHop`] entry. +#[derive(Clone, Debug)] +pub struct PrivateHopCandidate<'a> { + /// Information about the private hop communicated via BOLT 11. + /// + /// This is not exported to bindings users as lifetimes are not expressable in most languages. + pub hint: &'a RouteHintHop, + /// Node id of the next hop in BOLT 11 route hint. + /// + /// This is not exported to bindings users as lifetimes are not expressable in most languages. + pub target_node_id: &'a NodeId +} + +/// A [`CandidateRouteHop::Blinded`] entry. +#[derive(Clone, Debug)] +pub struct BlindedPathCandidate<'a> { + /// Information about the blinded path including the fee, HTLC amount limits, and + /// cryptographic material required to build an HTLC through the given path. + /// + /// This is not exported to bindings users as lifetimes are not expressable in most languages. + pub hint: &'a (BlindedPayInfo, BlindedPath), + /// Index of the hint in the original list of blinded hints. + /// + /// This is used to cheaply uniquely identify this blinded path, even though we don't have + /// a short channel ID for this hop. + hint_idx: usize, +} + +/// A [`CandidateRouteHop::OneHopBlinded`] entry. +#[derive(Clone, Debug)] +pub struct OneHopBlindedPathCandidate<'a> { + /// Information about the blinded path including the fee, HTLC amount limits, and + /// cryptographic material required to build an HTLC terminating with the given path. + /// + /// Note that the [`BlindedPayInfo`] is ignored here. + /// + /// This is not exported to bindings users as lifetimes are not expressable in most languages. + pub hint: &'a (BlindedPayInfo, BlindedPath), + /// Index of the hint in the original list of blinded hints. + /// + /// This is used to cheaply uniquely identify this blinded path, even though we don't have + /// a short channel ID for this hop. + hint_idx: usize, +} + /// A wrapper around the various hop representations. /// /// Can be used to examine the properties of a hop, @@ -1012,36 +1217,14 @@ const _GRAPH_NODE_FIXED_SIZE: usize = core::mem::size_of::() - 6 #[derive(Clone, Debug)] pub enum CandidateRouteHop<'a> { /// A hop from the payer, where the outbound liquidity is known. - FirstHop { - /// Channel details of the first hop - /// - /// [`ChannelDetails::get_outbound_payment_scid`] MUST be `Some` (indicating the channel - /// has been funded and is able to pay), and accessor methods may panic otherwise. - /// - /// [`find_route`] validates this prior to constructing a [`CandidateRouteHop`]. - details: &'a ChannelDetails, - /// The node id of the payer, which is also the source side of this candidate route hop. - payer_node_id: &'a NodeId, - }, + FirstHop(FirstHopCandidate<'a>), /// A hop found in the [`ReadOnlyNetworkGraph`]. - PublicHop { - /// Information about the channel, including potentially its capacity and - /// direction-specific information. - info: DirectedChannelInfo<'a>, - /// The short channel ID of the channel, i.e. the identifier by which we refer to this - /// channel. - short_channel_id: u64, - }, + PublicHop(PublicHopCandidate<'a>), /// A private hop communicated by the payee, generally via a BOLT 11 invoice. /// /// Because BOLT 11 route hints can take multiple hops to get to the destination, this may not /// terminate at the payee. - PrivateHop { - /// Information about the private hop communicated via BOLT 11. - hint: &'a RouteHintHop, - /// Node id of the next hop in BOLT 11 route hint. - target_node_id: &'a NodeId - }, + PrivateHop(PrivateHopCandidate<'a>), /// A blinded path which starts with an introduction point and ultimately terminates with the /// payee. /// @@ -1050,16 +1233,7 @@ pub enum CandidateRouteHop<'a> { /// /// Because blinded paths are "all or nothing", and we cannot use just one part of a blinded /// path, the full path is treated as a single [`CandidateRouteHop`]. - Blinded { - /// Information about the blinded path including the fee, HTLC amount limits, and - /// cryptographic material required to build an HTLC through the given path. - hint: &'a (BlindedPayInfo, BlindedPath), - /// Index of the hint in the original list of blinded hints. - /// - /// This is used to cheaply uniquely identify this blinded path, even though we don't have - /// a short channel ID for this hop. - hint_idx: usize, - }, + Blinded(BlindedPathCandidate<'a>), /// Similar to [`Self::Blinded`], but the path here only has one hop. /// /// While we treat this similarly to [`CandidateRouteHop::Blinded`] in many respects (e.g. @@ -1071,18 +1245,7 @@ pub enum CandidateRouteHop<'a> { /// /// This primarily exists to track that we need to included a blinded path at the end of our /// [`Route`], even though it doesn't actually add an additional hop in the payment. - OneHopBlinded { - /// Information about the blinded path including the fee, HTLC amount limits, and - /// cryptographic material required to build an HTLC terminating with the given path. - /// - /// Note that the [`BlindedPayInfo`] is ignored here. - hint: &'a (BlindedPayInfo, BlindedPath), - /// Index of the hint in the original list of blinded hints. - /// - /// This is used to cheaply uniquely identify this blinded path, even though we don't have - /// a short channel ID for this hop. - hint_idx: usize, - }, + OneHopBlinded(OneHopBlindedPathCandidate<'a>), } impl<'a> CandidateRouteHop<'a> { @@ -1104,11 +1267,11 @@ impl<'a> CandidateRouteHop<'a> { #[inline] fn short_channel_id(&self) -> Option { match self { - CandidateRouteHop::FirstHop { details, .. } => details.get_outbound_payment_scid(), - CandidateRouteHop::PublicHop { short_channel_id, .. } => Some(*short_channel_id), - CandidateRouteHop::PrivateHop { hint, .. } => Some(hint.short_channel_id), - CandidateRouteHop::Blinded { .. } => None, - CandidateRouteHop::OneHopBlinded { .. } => None, + CandidateRouteHop::FirstHop(hop) => hop.details.get_outbound_payment_scid(), + CandidateRouteHop::PublicHop(hop) => Some(hop.short_channel_id), + CandidateRouteHop::PrivateHop(hop) => Some(hop.hint.short_channel_id), + CandidateRouteHop::Blinded(_) => None, + CandidateRouteHop::OneHopBlinded(_) => None, } } @@ -1120,22 +1283,22 @@ impl<'a> CandidateRouteHop<'a> { #[inline] pub fn globally_unique_short_channel_id(&self) -> Option { match self { - CandidateRouteHop::FirstHop { details, .. } => if details.is_public { details.short_channel_id } else { None }, - CandidateRouteHop::PublicHop { short_channel_id, .. } => Some(*short_channel_id), - CandidateRouteHop::PrivateHop { .. } => None, - CandidateRouteHop::Blinded { .. } => None, - CandidateRouteHop::OneHopBlinded { .. } => None, + CandidateRouteHop::FirstHop(hop) => if hop.details.is_public { hop.details.short_channel_id } else { None }, + CandidateRouteHop::PublicHop(hop) => Some(hop.short_channel_id), + CandidateRouteHop::PrivateHop(_) => None, + CandidateRouteHop::Blinded(_) => None, + CandidateRouteHop::OneHopBlinded(_) => None, } } // NOTE: This may alloc memory so avoid calling it in a hot code path. fn features(&self) -> ChannelFeatures { match self { - CandidateRouteHop::FirstHop { details, .. } => details.counterparty.features.to_context(), - CandidateRouteHop::PublicHop { info, .. } => info.channel().features.clone(), - CandidateRouteHop::PrivateHop { .. } => ChannelFeatures::empty(), - CandidateRouteHop::Blinded { .. } => ChannelFeatures::empty(), - CandidateRouteHop::OneHopBlinded { .. } => ChannelFeatures::empty(), + CandidateRouteHop::FirstHop(hop) => hop.details.counterparty.features.to_context(), + CandidateRouteHop::PublicHop(hop) => hop.info.channel().features.clone(), + CandidateRouteHop::PrivateHop(_) => ChannelFeatures::empty(), + CandidateRouteHop::Blinded(_) => ChannelFeatures::empty(), + CandidateRouteHop::OneHopBlinded(_) => ChannelFeatures::empty(), } } @@ -1147,11 +1310,11 @@ impl<'a> CandidateRouteHop<'a> { #[inline] pub fn cltv_expiry_delta(&self) -> u32 { match self { - CandidateRouteHop::FirstHop { .. } => 0, - CandidateRouteHop::PublicHop { info, .. } => info.direction().cltv_expiry_delta as u32, - CandidateRouteHop::PrivateHop { hint, .. } => hint.cltv_expiry_delta as u32, - CandidateRouteHop::Blinded { hint, .. } => hint.0.cltv_expiry_delta as u32, - CandidateRouteHop::OneHopBlinded { .. } => 0, + CandidateRouteHop::FirstHop(_) => 0, + CandidateRouteHop::PublicHop(hop) => hop.info.direction().cltv_expiry_delta as u32, + CandidateRouteHop::PrivateHop(hop) => hop.hint.cltv_expiry_delta as u32, + CandidateRouteHop::Blinded(hop) => hop.hint.0.cltv_expiry_delta as u32, + CandidateRouteHop::OneHopBlinded(_) => 0, } } @@ -1159,10 +1322,10 @@ impl<'a> CandidateRouteHop<'a> { #[inline] pub fn htlc_minimum_msat(&self) -> u64 { match self { - CandidateRouteHop::FirstHop { details, .. } => details.next_outbound_htlc_minimum_msat, - CandidateRouteHop::PublicHop { info, .. } => info.direction().htlc_minimum_msat, - CandidateRouteHop::PrivateHop { hint, .. } => hint.htlc_minimum_msat.unwrap_or(0), - CandidateRouteHop::Blinded { hint, .. } => hint.0.htlc_minimum_msat, + CandidateRouteHop::FirstHop(hop) => hop.details.next_outbound_htlc_minimum_msat, + CandidateRouteHop::PublicHop(hop) => hop.info.direction().htlc_minimum_msat, + CandidateRouteHop::PrivateHop(hop) => hop.hint.htlc_minimum_msat.unwrap_or(0), + CandidateRouteHop::Blinded(hop) => hop.hint.0.htlc_minimum_msat, CandidateRouteHop::OneHopBlinded { .. } => 0, } } @@ -1171,18 +1334,18 @@ impl<'a> CandidateRouteHop<'a> { #[inline] pub fn fees(&self) -> RoutingFees { match self { - CandidateRouteHop::FirstHop { .. } => RoutingFees { + CandidateRouteHop::FirstHop(_) => RoutingFees { base_msat: 0, proportional_millionths: 0, }, - CandidateRouteHop::PublicHop { info, .. } => info.direction().fees, - CandidateRouteHop::PrivateHop { hint, .. } => hint.fees, - CandidateRouteHop::Blinded { hint, .. } => { + CandidateRouteHop::PublicHop(hop) => hop.info.direction().fees, + CandidateRouteHop::PrivateHop(hop) => hop.hint.fees, + CandidateRouteHop::Blinded(hop) => { RoutingFees { - base_msat: hint.0.fee_base_msat, - proportional_millionths: hint.0.fee_proportional_millionths + base_msat: hop.hint.0.fee_base_msat, + proportional_millionths: hop.hint.0.fee_proportional_millionths } }, - CandidateRouteHop::OneHopBlinded { .. } => + CandidateRouteHop::OneHopBlinded(_) => RoutingFees { base_msat: 0, proportional_millionths: 0 }, } } @@ -1193,17 +1356,17 @@ impl<'a> CandidateRouteHop<'a> { /// cached! fn effective_capacity(&self) -> EffectiveCapacity { match self { - CandidateRouteHop::FirstHop { details, .. } => EffectiveCapacity::ExactLiquidity { - liquidity_msat: details.next_outbound_htlc_limit_msat, + CandidateRouteHop::FirstHop(hop) => EffectiveCapacity::ExactLiquidity { + liquidity_msat: hop.details.next_outbound_htlc_limit_msat, }, - CandidateRouteHop::PublicHop { info, .. } => info.effective_capacity(), - CandidateRouteHop::PrivateHop { hint: RouteHintHop { htlc_maximum_msat: Some(max), .. }, .. } => + CandidateRouteHop::PublicHop(hop) => hop.info.effective_capacity(), + CandidateRouteHop::PrivateHop(PrivateHopCandidate { hint: RouteHintHop { htlc_maximum_msat: Some(max), .. }, .. }) => EffectiveCapacity::HintMaxHTLC { amount_msat: *max }, - CandidateRouteHop::PrivateHop { hint: RouteHintHop { htlc_maximum_msat: None, .. }, .. } => + CandidateRouteHop::PrivateHop(PrivateHopCandidate { hint: RouteHintHop { htlc_maximum_msat: None, .. }, .. }) => EffectiveCapacity::Infinite, - CandidateRouteHop::Blinded { hint, .. } => - EffectiveCapacity::HintMaxHTLC { amount_msat: hint.0.htlc_maximum_msat }, - CandidateRouteHop::OneHopBlinded { .. } => EffectiveCapacity::Infinite, + CandidateRouteHop::Blinded(hop) => + EffectiveCapacity::HintMaxHTLC { amount_msat: hop.hint.0.htlc_maximum_msat }, + CandidateRouteHop::OneHopBlinded(_) => EffectiveCapacity::Infinite, } } @@ -1213,19 +1376,28 @@ impl<'a> CandidateRouteHop<'a> { #[inline] fn id(&self) -> CandidateHopId { match self { - CandidateRouteHop::Blinded { hint_idx, .. } => CandidateHopId::Blinded(*hint_idx), - CandidateRouteHop::OneHopBlinded { hint_idx, .. } => CandidateHopId::Blinded(*hint_idx), + CandidateRouteHop::Blinded(hop) => CandidateHopId::Blinded(hop.hint_idx), + CandidateRouteHop::OneHopBlinded(hop) => CandidateHopId::Blinded(hop.hint_idx), _ => CandidateHopId::Clear((self.short_channel_id().unwrap(), self.source() < self.target().unwrap())), } } fn blinded_path(&self) -> Option<&'a BlindedPath> { match self { - CandidateRouteHop::Blinded { hint, .. } | CandidateRouteHop::OneHopBlinded { hint, .. } => { + CandidateRouteHop::Blinded(BlindedPathCandidate { hint, .. }) | CandidateRouteHop::OneHopBlinded(OneHopBlindedPathCandidate { hint, .. }) => { Some(&hint.1) }, _ => None, } } + fn blinded_hint_idx(&self) -> Option { + match self { + Self::Blinded(BlindedPathCandidate { hint_idx, .. }) | + Self::OneHopBlinded(OneHopBlindedPathCandidate { hint_idx, .. }) => { + Some(*hint_idx) + }, + _ => None, + } + } /// Returns the source node id of current hop. /// /// Source node id refers to the node forwarding the HTLC through this hop. @@ -1234,11 +1406,11 @@ impl<'a> CandidateRouteHop<'a> { #[inline] pub fn source(&self) -> NodeId { match self { - CandidateRouteHop::FirstHop { payer_node_id, .. } => **payer_node_id, - CandidateRouteHop::PublicHop { info, .. } => *info.source(), - CandidateRouteHop::PrivateHop { hint, .. } => hint.src_node_id.into(), - CandidateRouteHop::Blinded { hint, .. } => hint.1.introduction_node_id.into(), - CandidateRouteHop::OneHopBlinded { hint, .. } => hint.1.introduction_node_id.into(), + CandidateRouteHop::FirstHop(hop) => *hop.payer_node_id, + CandidateRouteHop::PublicHop(hop) => *hop.info.source(), + CandidateRouteHop::PrivateHop(hop) => hop.hint.src_node_id.into(), + CandidateRouteHop::Blinded(hop) => hop.hint.1.introduction_node_id.into(), + CandidateRouteHop::OneHopBlinded(hop) => hop.hint.1.introduction_node_id.into(), } } /// Returns the target node id of this hop, if known. @@ -1253,11 +1425,11 @@ impl<'a> CandidateRouteHop<'a> { #[inline] pub fn target(&self) -> Option { match self { - CandidateRouteHop::FirstHop { details, .. } => Some(details.counterparty.node_id.into()), - CandidateRouteHop::PublicHop { info, .. } => Some(*info.target()), - CandidateRouteHop::PrivateHop { target_node_id, .. } => Some(**target_node_id), - CandidateRouteHop::Blinded { .. } => None, - CandidateRouteHop::OneHopBlinded { .. } => None, + CandidateRouteHop::FirstHop(hop) => Some(hop.details.counterparty.node_id.into()), + CandidateRouteHop::PublicHop(hop) => Some(*hop.info.target()), + CandidateRouteHop::PrivateHop(hop) => Some(*hop.target_node_id), + CandidateRouteHop::Blinded(_) => None, + CandidateRouteHop::OneHopBlinded(_) => None, } } } @@ -1552,17 +1724,17 @@ struct LoggedCandidateHop<'a>(&'a CandidateRouteHop<'a>); impl<'a> fmt::Display for LoggedCandidateHop<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self.0 { - CandidateRouteHop::Blinded { hint, .. } | CandidateRouteHop::OneHopBlinded { hint, .. } => { + CandidateRouteHop::Blinded(BlindedPathCandidate { hint, .. }) | CandidateRouteHop::OneHopBlinded(OneHopBlindedPathCandidate { hint, .. }) => { "blinded route hint with introduction node id ".fmt(f)?; hint.1.introduction_node_id.fmt(f)?; " and blinding point ".fmt(f)?; hint.1.blinding_point.fmt(f) }, - CandidateRouteHop::FirstHop { .. } => { + CandidateRouteHop::FirstHop(_) => { "first hop with SCID ".fmt(f)?; self.0.short_channel_id().unwrap().fmt(f) }, - CandidateRouteHop::PrivateHop { .. } => { + CandidateRouteHop::PrivateHop(_) => { "route hint with SCID ".fmt(f)?; self.0.short_channel_id().unwrap().fmt(f) }, @@ -1801,7 +1973,7 @@ where L::Target: Logger { // inserting first hops suggested by the caller as targets. // Our search will then attempt to reach them while traversing from the payee node. let mut first_hop_targets: HashMap<_, Vec<&ChannelDetails>> = - HashMap::with_capacity(if first_hops.is_some() { first_hops.as_ref().unwrap().len() } else { 0 }); + hash_map_with_capacity(if first_hops.is_some() { first_hops.as_ref().unwrap().len() } else { 0 }); if let Some(hops) = first_hops { for chan in hops { if chan.get_outbound_payment_scid().is_none() { @@ -1820,7 +1992,7 @@ where L::Target: Logger { } } - let mut private_hop_key_cache = HashMap::with_capacity( + let mut private_hop_key_cache = hash_map_with_capacity( payment_params.payee.unblinded_route_hints().iter().map(|path| path.0.len()).sum() ); @@ -1841,7 +2013,7 @@ where L::Target: Logger { // Map from node_id to information about the best current path to that node, including feerate // information. - let mut dist: HashMap = HashMap::with_capacity(network_nodes.len()); + let mut dist: HashMap = hash_map_with_capacity(network_nodes.len()); // During routing, if we ignore a path due to an htlc_minimum_msat limit, we set this, // indicating that we may wish to try again with a higher value, potentially paying to meet an @@ -1882,7 +2054,7 @@ where L::Target: Logger { // is used. Hence, liquidity used in one direction will not offset any used in the opposite // direction. let mut used_liquidities: HashMap = - HashMap::with_capacity(network_nodes.len()); + hash_map_with_capacity(network_nodes.len()); // Keeping track of how much value we already collected across other paths. Helps to decide // when we want to stop looking for new paths. @@ -1977,14 +2149,21 @@ where L::Target: Logger { (amount_to_transfer_over_msat < $next_hops_path_htlc_minimum_msat && recommended_value_msat >= $next_hops_path_htlc_minimum_msat)); - let payment_failed_on_this_channel = scid_opt.map_or(false, - |scid| payment_params.previously_failed_channels.contains(&scid)); + let payment_failed_on_this_channel = match scid_opt { + Some(scid) => payment_params.previously_failed_channels.contains(&scid), + None => match $candidate.blinded_hint_idx() { + Some(idx) => { + payment_params.previously_failed_blinded_path_idxs.contains(&(idx as u64)) + }, + None => false, + }, + }; let (should_log_candidate, first_hop_details) = match $candidate { - CandidateRouteHop::FirstHop { details, .. } => (true, Some(details)), - CandidateRouteHop::PrivateHop { .. } => (true, None), - CandidateRouteHop::Blinded { .. } => (true, None), - CandidateRouteHop::OneHopBlinded { .. } => (true, None), + CandidateRouteHop::FirstHop(hop) => (true, Some(hop.details)), + CandidateRouteHop::PrivateHop(_) => (true, None), + CandidateRouteHop::Blinded(_) => (true, None), + CandidateRouteHop::OneHopBlinded(_) => (true, None), _ => (false, None), }; @@ -2251,9 +2430,9 @@ where L::Target: Logger { if !skip_node { if let Some(first_channels) = first_hop_targets.get(&$node_id) { for details in first_channels { - let candidate = CandidateRouteHop::FirstHop { + let candidate = CandidateRouteHop::FirstHop(FirstHopCandidate { details, payer_node_id: &our_node_id, - }; + }); add_entry!(&candidate, fee_to_target_msat, $next_hops_value_contribution, next_hops_path_htlc_minimum_msat, next_hops_path_penalty_msat, @@ -2274,10 +2453,10 @@ where L::Target: Logger { if let Some((directed_channel, source)) = chan.as_directed_to(&$node_id) { if first_hops.is_none() || *source != our_node_id { if directed_channel.direction().enabled { - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info: directed_channel, short_channel_id: *chan_id, - }; + }); add_entry!(&candidate, fee_to_target_msat, $next_hops_value_contribution, @@ -2308,9 +2487,9 @@ where L::Target: Logger { // place where it could be added. payee_node_id_opt.map(|payee| first_hop_targets.get(&payee).map(|first_channels| { for details in first_channels { - let candidate = CandidateRouteHop::FirstHop { + let candidate = CandidateRouteHop::FirstHop(FirstHopCandidate { details, payer_node_id: &our_node_id, - }; + }); let added = add_entry!(&candidate, 0, path_value_msat, 0, 0u64, 0, 0).is_some(); log_trace!(logger, "{} direct route to payee via {}", @@ -2345,8 +2524,8 @@ where L::Target: Logger { network_nodes.get(&intro_node_id).is_some(); 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 } }; + CandidateRouteHop::OneHopBlinded(OneHopBlindedPathCandidate { hint, hint_idx }) + } else { CandidateRouteHop::Blinded(BlindedPathCandidate { hint, hint_idx }) }; let mut path_contribution_msat = path_value_msat; if let Some(hop_used_msat) = add_entry!(&candidate, 0, path_contribution_msat, 0, 0_u64, 0, 0) @@ -2357,9 +2536,9 @@ where L::Target: Logger { sort_first_hop_channels(first_channels, &used_liquidities, recommended_value_msat, our_node_pubkey); for details in first_channels { - let first_hop_candidate = CandidateRouteHop::FirstHop { + let first_hop_candidate = CandidateRouteHop::FirstHop(FirstHopCandidate { details, payer_node_id: &our_node_id, - }; + }); let blinded_path_fee = match compute_fees(path_contribution_msat, candidate.fees()) { Some(fee) => fee, None => continue @@ -2398,9 +2577,9 @@ where L::Target: Logger { let mut aggregate_path_contribution_msat = path_value_msat; for (idx, (hop, prev_hop_id)) in hop_iter.zip(prev_hop_iter).enumerate() { - let target = private_hop_key_cache.get(&prev_hop_id).unwrap(); + let target = private_hop_key_cache.get(prev_hop_id).unwrap(); - if let Some(first_channels) = first_hop_targets.get(&target) { + if let Some(first_channels) = first_hop_targets.get(target) { if first_channels.iter().any(|d| d.outbound_scid_alias == Some(hop.short_channel_id)) { log_trace!(logger, "Ignoring route hint with SCID {} (and any previous) due to it being a direct channel of ours.", hop.short_channel_id); @@ -2410,12 +2589,12 @@ where L::Target: Logger { let candidate = network_channels .get(&hop.short_channel_id) - .and_then(|channel| channel.as_directed_to(&target)) - .map(|(info, _)| CandidateRouteHop::PublicHop { + .and_then(|channel| channel.as_directed_to(target)) + .map(|(info, _)| CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: hop.short_channel_id, - }) - .unwrap_or_else(|| CandidateRouteHop::PrivateHop { hint: hop, target_node_id: target }); + })) + .unwrap_or_else(|| CandidateRouteHop::PrivateHop(PrivateHopCandidate { hint: hop, target_node_id: target })); if let Some(hop_used_msat) = add_entry!(&candidate, aggregate_next_hops_fee_msat, aggregate_path_contribution_msat, @@ -2451,13 +2630,13 @@ where L::Target: Logger { .saturating_add(1); // Searching for a direct channel between last checked hop and first_hop_targets - if let Some(first_channels) = first_hop_targets.get_mut(&target) { + if let Some(first_channels) = first_hop_targets.get_mut(target) { sort_first_hop_channels(first_channels, &used_liquidities, recommended_value_msat, our_node_pubkey); for details in first_channels { - let first_hop_candidate = CandidateRouteHop::FirstHop { + let first_hop_candidate = CandidateRouteHop::FirstHop(FirstHopCandidate { details, payer_node_id: &our_node_id, - }; + }); add_entry!(&first_hop_candidate, aggregate_next_hops_fee_msat, aggregate_path_contribution_msat, aggregate_next_hops_path_htlc_minimum_msat, aggregate_next_hops_path_penalty_msat, @@ -2502,9 +2681,9 @@ where L::Target: Logger { sort_first_hop_channels(first_channels, &used_liquidities, recommended_value_msat, our_node_pubkey); for details in first_channels { - let first_hop_candidate = CandidateRouteHop::FirstHop { + let first_hop_candidate = CandidateRouteHop::FirstHop(FirstHopCandidate { details, payer_node_id: &our_node_id, - }; + }); add_entry!(&first_hop_candidate, aggregate_next_hops_fee_msat, aggregate_path_contribution_msat, @@ -2547,7 +2726,7 @@ where L::Target: Logger { let target = ordered_hops.last().unwrap().0.candidate.target().unwrap_or(maybe_dummy_payee_node_id); if let Some(first_channels) = first_hop_targets.get(&target) { for details in first_channels { - if let CandidateRouteHop::FirstHop { details: last_hop_details, .. } + if let CandidateRouteHop::FirstHop(FirstHopCandidate { details: last_hop_details, .. }) = ordered_hops.last().unwrap().0.candidate { if details.get_outbound_payment_scid() == last_hop_details.get_outbound_payment_scid() { @@ -2572,7 +2751,7 @@ where L::Target: Logger { } } - // Means we succesfully traversed from the payer to the payee, now + // Means we successfully traversed from the payer to the payee, now // save this path for the payment route. Also, update the liquidity // remaining on the used hops, so that we take them into account // while looking for more paths. @@ -2814,12 +2993,12 @@ where L::Target: Logger { .filter(|(h, _)| h.candidate.short_channel_id().is_some()) { let target = hop.candidate.target().expect("target is defined when short_channel_id is defined"); - let maybe_announced_channel = if let CandidateRouteHop::PublicHop { .. } = hop.candidate { + 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 { + } else if let CandidateRouteHop::FirstHop(first_hop) = &hop.candidate { // If this is a first hop we also know if it's announced. - details.is_public + first_hop.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 @@ -3049,7 +3228,7 @@ mod tests { 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, RouteParameters, CandidateRouteHop}; + DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA, MAX_PATH_LENGTH_ESTIMATE, RouteParameters, CandidateRouteHop, PublicHopCandidate}; 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; @@ -3061,7 +3240,7 @@ mod tests { use crate::offers::invoice::BlindedPayInfo; use crate::util::config::UserConfig; use crate::util::test_utils as ln_test_utils; - use crate::util::chacha20::ChaCha20; + use crate::crypto::chacha20::ChaCha20; use crate::util::ser::{Readable, Writeable}; #[cfg(c_bindings)] use crate::util::ser::Writer; @@ -3117,6 +3296,8 @@ mod tests { config: None, feerate_sat_per_1000_weight: None, channel_shutdown_state: Some(channelmanager::ChannelShutdownState::NotShuttingDown), + pending_inbound_htlcs: Vec::new(), + pending_outbound_htlcs: Vec::new(), } } @@ -6768,7 +6949,7 @@ mod tests { (route.paths[1].hops[1].short_channel_id == 4 && route.paths[0].hops[1].short_channel_id == 13)); } - #[cfg(not(feature = "no-std"))] + #[cfg(feature = "std")] pub(super) fn random_init_seed() -> u64 { // Because the default HashMap in std pulls OS randomness, we can use it as a (bad) RNG. use core::hash::{BuildHasher, Hasher}; @@ -6778,7 +6959,7 @@ mod tests { } #[test] - #[cfg(not(feature = "no-std"))] + #[cfg(feature = "std")] fn generate_routes() { use crate::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringFeeParameters}; @@ -6799,7 +6980,7 @@ mod tests { } #[test] - #[cfg(not(feature = "no-std"))] + #[cfg(feature = "std")] fn generate_routes_mpp() { use crate::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringFeeParameters}; @@ -6820,7 +7001,7 @@ mod tests { } #[test] - #[cfg(not(feature = "no-std"))] + #[cfg(feature = "std")] fn generate_large_mpp_routes() { use crate::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringFeeParameters}; @@ -6863,10 +7044,10 @@ mod tests { let channels = network_graph.channels(); let channel = channels.get(&5).unwrap(); let info = channel.as_directed_from(&NodeId::from_pubkey(&nodes[3])).unwrap(); - let candidate: CandidateRouteHop = CandidateRouteHop::PublicHop { + let candidate: CandidateRouteHop = CandidateRouteHop::PublicHop(PublicHopCandidate { info: info.0, short_channel_id: 5, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, &scorer_params), 456); // Then check we can get a normal route @@ -7669,7 +7850,7 @@ mod tests { 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 + // to `targets` and build an invalid path with it, and subsequently 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()); @@ -8156,10 +8337,11 @@ mod tests { } } -#[cfg(all(any(test, ldk_bench), not(feature = "no-std")))] +#[cfg(all(any(test, ldk_bench), feature = "std"))] pub(crate) mod bench_utils { use super::*; use std::fs::File; + use std::time::Duration; use bitcoin::hashes::Hash; use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; @@ -8257,6 +8439,8 @@ pub(crate) mod bench_utils { config: None, feerate_sat_per_1000_weight: None, channel_shutdown_state: Some(channelmanager::ChannelShutdownState::NotShuttingDown), + pending_inbound_htlcs: Vec::new(), + pending_outbound_htlcs: Vec::new(), } } @@ -8308,16 +8492,16 @@ pub(crate) mod bench_utils { if let Ok(route) = route_res { for path in route.paths { if seed & 0x80 == 0 { - scorer.payment_path_successful(&path); + scorer.payment_path_successful(&path, Duration::ZERO); } else { let short_channel_id = path.hops[path.hops.len() / 2].short_channel_id; - scorer.payment_path_failed(&path, short_channel_id); + scorer.payment_path_failed(&path, short_channel_id, Duration::ZERO); } seed = seed.overflowing_mul(6364136223846793005).0.overflowing_add(1).0; } break; } - // If we couldn't find a path with a higer amount, reduce and try again. + // If we couldn't find a path with a higher amount, reduce and try again. score_amt /= 100; } diff --git a/lightning/src/routing/scoring.rs b/lightning/src/routing/scoring.rs index 337fd8eec7a..bb043164a0e 100644 --- a/lightning/src/routing/scoring.rs +++ b/lightning/src/routing/scoring.rs @@ -58,19 +58,22 @@ use crate::ln::msgs::DecodeError; use crate::routing::gossip::{EffectiveCapacity, NetworkGraph, NodeId}; -use crate::routing::router::{Path, CandidateRouteHop}; +use crate::routing::router::{Path, CandidateRouteHop, PublicHopCandidate}; use crate::util::ser::{Readable, ReadableArgs, Writeable, Writer}; use crate::util::logger::Logger; -use crate::util::time::Time; use crate::prelude::*; use core::{cmp, fmt}; -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, RwLock, RwLockReadGuard, RwLockWriteGuard}; +use crate::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; +#[cfg(not(c_bindings))] +use { + core::cell::{RefCell, RefMut, Ref}, + crate::sync::{Mutex, MutexGuard}, +}; /// 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 @@ -110,16 +113,22 @@ pub trait ScoreLookUp { /// `ScoreUpdate` is used to update the scorer's internal state after a payment attempt. pub trait ScoreUpdate { /// Handles updating channel penalties after failing to route through a channel. - fn payment_path_failed(&mut self, path: &Path, short_channel_id: u64); + fn payment_path_failed(&mut self, path: &Path, short_channel_id: u64, duration_since_epoch: Duration); /// Handles updating channel penalties after successfully routing along a path. - fn payment_path_successful(&mut self, path: &Path); + fn payment_path_successful(&mut self, path: &Path, duration_since_epoch: Duration); /// Handles updating channel penalties after a probe over the given path failed. - fn probe_failed(&mut self, path: &Path, short_channel_id: u64); + fn probe_failed(&mut self, path: &Path, short_channel_id: u64, duration_since_epoch: Duration); /// Handles updating channel penalties after a probe over the given path succeeded. - fn probe_successful(&mut self, path: &Path); + fn probe_successful(&mut self, path: &Path, duration_since_epoch: Duration); + + /// Scorers may wish to reduce their certainty of channel liquidity information over time. + /// Thus, this method is provided to allow scorers to observe the passage of time - the holder + /// of this object should call this method regularly (generally via the + /// `lightning-background-processor` crate). + fn time_passed(&mut self, duration_since_epoch: Duration); } /// A trait which can both lookup and update routing channel penalty scores. @@ -145,20 +154,24 @@ impl> ScoreLookUp for T { #[cfg(not(c_bindings))] impl> 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) + fn payment_path_failed(&mut self, path: &Path, short_channel_id: u64, duration_since_epoch: Duration) { + self.deref_mut().payment_path_failed(path, short_channel_id, duration_since_epoch) + } + + fn payment_path_successful(&mut self, path: &Path, duration_since_epoch: Duration) { + self.deref_mut().payment_path_successful(path, duration_since_epoch) } - fn payment_path_successful(&mut self, path: &Path) { - self.deref_mut().payment_path_successful(path) + fn probe_failed(&mut self, path: &Path, short_channel_id: u64, duration_since_epoch: Duration) { + self.deref_mut().probe_failed(path, short_channel_id, duration_since_epoch) } - fn probe_failed(&mut self, path: &Path, short_channel_id: u64) { - self.deref_mut().probe_failed(path, short_channel_id) + fn probe_successful(&mut self, path: &Path, duration_since_epoch: Duration) { + self.deref_mut().probe_successful(path, duration_since_epoch) } - fn probe_successful(&mut self, path: &Path) { - self.deref_mut().probe_successful(path) + fn time_passed(&mut self, duration_since_epoch: Duration) { + self.deref_mut().time_passed(duration_since_epoch) } } } } @@ -238,7 +251,7 @@ impl<'a, T: Score + 'a> LockableScore<'a> for RefCell { } } -#[cfg(not(c_bindings))] +#[cfg(any(not(c_bindings), feature = "_test_utils", test))] impl<'a, T: Score + 'a> LockableScore<'a> for RwLock { type ScoreUpdate = T; type ScoreLookUp = T; @@ -346,20 +359,24 @@ impl<'a, T: 'a + Score> DerefMut for MultiThreadedScoreLockWrite<'a, T> { #[cfg(c_bindings)] impl<'a, T: Score> ScoreUpdate for MultiThreadedScoreLockWrite<'a, T> { - fn payment_path_failed(&mut self, path: &Path, short_channel_id: u64) { - self.0.payment_path_failed(path, short_channel_id) + fn payment_path_failed(&mut self, path: &Path, short_channel_id: u64, duration_since_epoch: Duration) { + self.0.payment_path_failed(path, short_channel_id, duration_since_epoch) + } + + fn payment_path_successful(&mut self, path: &Path, duration_since_epoch: Duration) { + self.0.payment_path_successful(path, duration_since_epoch) } - fn payment_path_successful(&mut self, path: &Path) { - self.0.payment_path_successful(path) + fn probe_failed(&mut self, path: &Path, short_channel_id: u64, duration_since_epoch: Duration) { + self.0.probe_failed(path, short_channel_id, duration_since_epoch) } - fn probe_failed(&mut self, path: &Path, short_channel_id: u64) { - self.0.probe_failed(path, short_channel_id) + fn probe_successful(&mut self, path: &Path, duration_since_epoch: Duration) { + self.0.probe_successful(path, duration_since_epoch) } - fn probe_successful(&mut self, path: &Path) { - self.0.probe_successful(path) + fn time_passed(&mut self, duration_since_epoch: Duration) { + self.0.time_passed(duration_since_epoch) } } @@ -399,13 +416,15 @@ impl ScoreLookUp for FixedPenaltyScorer { } impl ScoreUpdate for FixedPenaltyScorer { - fn payment_path_failed(&mut self, _path: &Path, _short_channel_id: u64) {} + fn payment_path_failed(&mut self, _path: &Path, _short_channel_id: u64, _duration_since_epoch: Duration) {} - fn payment_path_successful(&mut self, _path: &Path) {} + fn payment_path_successful(&mut self, _path: &Path, _duration_since_epoch: Duration) {} - fn probe_failed(&mut self, _path: &Path, _short_channel_id: u64) {} + fn probe_failed(&mut self, _path: &Path, _short_channel_id: u64, _duration_since_epoch: Duration) {} - fn probe_successful(&mut self, _path: &Path) {} + fn probe_successful(&mut self, _path: &Path, _duration_since_epoch: Duration) {} + + fn time_passed(&mut self, _duration_since_epoch: Duration) {} } impl Writeable for FixedPenaltyScorer { @@ -424,13 +443,6 @@ impl ReadableArgs for FixedPenaltyScorer { } } -#[cfg(not(feature = "no-std"))] -type ConfiguredTime = crate::util::time::MonotonicTime; -#[cfg(feature = "no-std")] -use crate::util::time::Eternity; -#[cfg(feature = "no-std")] -type ConfiguredTime = Eternity; - /// [`ScoreLookUp`] implementation using channel success probability distributions. /// /// Channels are tracked with upper and lower liquidity bounds - when an HTLC fails at a channel, @@ -456,29 +468,18 @@ type ConfiguredTime = Eternity; /// formula, but using the history of a channel rather than our latest estimates for the liquidity /// bounds. /// -/// # Note -/// -/// Mixing the `no-std` feature between serialization and deserialization results in undefined -/// behavior. -/// /// [1]: https://arxiv.org/abs/2107.05322 /// [`liquidity_penalty_multiplier_msat`]: ProbabilisticScoringFeeParameters::liquidity_penalty_multiplier_msat /// [`liquidity_penalty_amount_multiplier_msat`]: ProbabilisticScoringFeeParameters::liquidity_penalty_amount_multiplier_msat /// [`liquidity_offset_half_life`]: ProbabilisticScoringDecayParameters::liquidity_offset_half_life /// [`historical_liquidity_penalty_multiplier_msat`]: ProbabilisticScoringFeeParameters::historical_liquidity_penalty_multiplier_msat /// [`historical_liquidity_penalty_amount_multiplier_msat`]: ProbabilisticScoringFeeParameters::historical_liquidity_penalty_amount_multiplier_msat -pub type ProbabilisticScorer = ProbabilisticScorerUsingTime::; - -/// Probabilistic [`ScoreLookUp`] implementation. -/// -/// This is not exported to bindings users generally all users should use the [`ProbabilisticScorer`] type alias. -pub struct ProbabilisticScorerUsingTime>, L: Deref, T: Time> +pub struct ProbabilisticScorer>, L: Deref> where L::Target: Logger { decay_params: ProbabilisticScoringDecayParameters, network_graph: G, logger: L, - // TODO: Remove entries of closed channels. - channel_liquidities: HashMap>, + channel_liquidities: HashMap, } /// Parameters for configuring [`ProbabilisticScorer`]. @@ -652,7 +653,7 @@ impl Default for ProbabilisticScoringFeeParameters { base_penalty_amount_multiplier_msat: 8192, liquidity_penalty_multiplier_msat: 30_000, liquidity_penalty_amount_multiplier_msat: 192, - manual_node_penalties: HashMap::new(), + manual_node_penalties: new_hash_map(), anti_probing_penalty_msat: 250, considered_impossible_penalty_msat: 1_0000_0000_000, historical_liquidity_penalty_multiplier_msat: 10_000, @@ -694,7 +695,7 @@ impl ProbabilisticScoringFeeParameters { /// Clears the list of manual penalties that are applied during path finding. pub fn clear_manual_penalties(&mut self) { - self.manual_node_penalties = HashMap::new(); + self.manual_node_penalties = new_hash_map(); } } @@ -708,7 +709,7 @@ impl ProbabilisticScoringFeeParameters { liquidity_penalty_amount_multiplier_msat: 0, historical_liquidity_penalty_multiplier_msat: 0, historical_liquidity_penalty_amount_multiplier_msat: 0, - manual_node_penalties: HashMap::new(), + manual_node_penalties: new_hash_map(), anti_probing_penalty_msat: 0, considered_impossible_penalty_msat: 0, linear_success_probability: true, @@ -733,7 +734,7 @@ pub struct ProbabilisticScoringDecayParameters { /// /// Default value: 14 days /// - /// [`historical_estimated_channel_liquidity_probabilities`]: ProbabilisticScorerUsingTime::historical_estimated_channel_liquidity_probabilities + /// [`historical_estimated_channel_liquidity_probabilities`]: ProbabilisticScorer::historical_estimated_channel_liquidity_probabilities pub historical_no_updates_half_life: Duration, /// Whenever this amount of time elapses since the last update to a channel's liquidity bounds, @@ -782,33 +783,35 @@ impl ProbabilisticScoringDecayParameters { /// Direction is defined in terms of [`NodeId`] partial ordering, where the source node is the /// first node in the ordering of the channel's counterparties. Thus, swapping the two liquidity /// offset fields gives the opposite direction. -struct ChannelLiquidity { +struct ChannelLiquidity { /// Lower channel liquidity bound in terms of an offset from zero. min_liquidity_offset_msat: u64, /// Upper channel liquidity bound in terms of an offset from the effective capacity. max_liquidity_offset_msat: u64, - /// Time when the liquidity bounds were last modified. - last_updated: T, - min_liquidity_offset_history: HistoricalBucketRangeTracker, max_liquidity_offset_history: HistoricalBucketRangeTracker, + + /// Time when either liquidity bound was last modified as an offset since the unix epoch. + last_updated: Duration, + + /// Time when the historical liquidity bounds were last modified as an offset against the unix + /// epoch. + offset_history_last_updated: Duration, } -/// A snapshot of [`ChannelLiquidity`] in one direction assuming a certain channel capacity and -/// decayed with a given half life. -struct DirectedChannelLiquidity, BRT: Deref, T: Time, U: Deref> { +/// A snapshot of [`ChannelLiquidity`] in one direction assuming a certain channel capacity. +struct DirectedChannelLiquidity, BRT: Deref, T: Deref> { min_liquidity_offset_msat: L, max_liquidity_offset_msat: L, liquidity_history: HistoricalMinMaxBuckets, capacity_msat: u64, - last_updated: U, - now: T, - decay_params: ProbabilisticScoringDecayParameters, + last_updated: T, + offset_history_last_updated: T, } -impl>, L: Deref, T: Time> ProbabilisticScorerUsingTime where L::Target: Logger { +impl>, L: Deref> ProbabilisticScorer where L::Target: Logger { /// Creates a new scorer using the given scoring parameters for sending payments from a node /// through a network graph. pub fn new(decay_params: ProbabilisticScoringDecayParameters, network_graph: G, logger: L) -> Self { @@ -816,12 +819,12 @@ impl>, L: Deref, T: Time> ProbabilisticScorerU decay_params, network_graph, logger, - channel_liquidities: HashMap::new(), + channel_liquidities: new_hash_map(), } } #[cfg(test)] - fn with_channel(mut self, short_channel_id: u64, liquidity: ChannelLiquidity) -> Self { + fn with_channel(mut self, short_channel_id: u64, liquidity: ChannelLiquidity) -> Self { assert!(self.channel_liquidities.insert(short_channel_id, liquidity).is_none()); self } @@ -831,20 +834,16 @@ impl>, L: Deref, T: Time> ProbabilisticScorerU /// Note that this writes roughly one line per channel for which we have a liquidity estimate, /// which may be a substantial amount of log output. pub fn debug_log_liquidity_stats(&self) { - let now = T::now(); - let graph = self.network_graph.read_only(); for (scid, liq) in self.channel_liquidities.iter() { if let Some(chan_debug) = graph.channels().get(scid) { 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, amt, self.decay_params); + let dir_liq = liq.as_directed(source, target, amt); - 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])); + let min_buckets = &dir_liq.liquidity_history.min_liquidity_offset_history.buckets; + let max_buckets = &dir_liq.liquidity_history.max_liquidity_offset_history.buckets; log_debug!(self.logger, core::concat!( "Liquidity from {} to {} via {} is in the range ({}, {}).\n", @@ -893,7 +892,7 @@ impl>, L: Deref, T: Time> ProbabilisticScorerU 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, amt, self.decay_params); + let dir_liq = liq.as_directed(source, target, amt); return Some((dir_liq.min_liquidity_msat(), dir_liq.max_liquidity_msat())); } } @@ -923,7 +922,7 @@ impl>, L: Deref, T: Time> ProbabilisticScorerU /// 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(([1; 32], [1; 32]))` and then to `None` once no datapoints remain. + /// `Some(([0; 32], [0; 32]))` or `None` if no data remains for a channel. /// /// 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`]. @@ -935,13 +934,10 @@ impl>, L: Deref, T: Time> ProbabilisticScorerU 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, amt, self.decay_params); + let dir_liq = liq.as_directed(source, target, amt); - 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 - )?; + let min_buckets = dir_liq.liquidity_history.min_liquidity_offset_history.buckets; + let mut max_buckets = dir_liq.liquidity_history.max_liquidity_offset_history.buckets; // Note that the liquidity buckets are an offset from the edge, so we inverse // the max order to get the probabilities from zero. @@ -969,12 +965,10 @@ impl>, L: Deref, T: Time> ProbabilisticScorerU 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); + let dir_liq = liq.as_directed(source, target, capacity_msat); 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 + ¶ms, amount_msat, capacity_msat ).map(|p| p as f64 / (1024 * 1024 * 1024) as f64); } } @@ -983,23 +977,23 @@ impl>, L: Deref, T: Time> ProbabilisticScorerU } } -impl ChannelLiquidity { - #[inline] - fn new() -> Self { +impl ChannelLiquidity { + fn new(last_updated: Duration) -> Self { Self { min_liquidity_offset_msat: 0, max_liquidity_offset_msat: 0, min_liquidity_offset_history: HistoricalBucketRangeTracker::new(), max_liquidity_offset_history: HistoricalBucketRangeTracker::new(), - last_updated: T::now(), + last_updated, + offset_history_last_updated: last_updated, } } /// Returns a view of the channel liquidity directed from `source` to `target` assuming /// `capacity_msat`. fn as_directed( - &self, source: &NodeId, target: &NodeId, capacity_msat: u64, decay_params: ProbabilisticScoringDecayParameters - ) -> DirectedChannelLiquidity<&u64, &HistoricalBucketRangeTracker, T, &T> { + &self, source: &NodeId, target: &NodeId, capacity_msat: u64, + ) -> DirectedChannelLiquidity<&u64, &HistoricalBucketRangeTracker, &Duration> { let (min_liquidity_offset_msat, max_liquidity_offset_msat, min_liquidity_offset_history, max_liquidity_offset_history) = if source < target { (&self.min_liquidity_offset_msat, &self.max_liquidity_offset_msat, @@ -1018,16 +1012,15 @@ impl ChannelLiquidity { }, capacity_msat, last_updated: &self.last_updated, - now: T::now(), - decay_params: decay_params, + offset_history_last_updated: &self.offset_history_last_updated, } } /// 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, capacity_msat: u64, decay_params: ProbabilisticScoringDecayParameters - ) -> DirectedChannelLiquidity<&mut u64, &mut HistoricalBucketRangeTracker, T, &mut T> { + &mut self, source: &NodeId, target: &NodeId, capacity_msat: u64, + ) -> DirectedChannelLiquidity<&mut u64, &mut HistoricalBucketRangeTracker, &mut Duration> { let (min_liquidity_offset_msat, max_liquidity_offset_msat, min_liquidity_offset_history, max_liquidity_offset_history) = if source < target { (&mut self.min_liquidity_offset_msat, &mut self.max_liquidity_offset_msat, @@ -1046,8 +1039,20 @@ impl ChannelLiquidity { }, capacity_msat, last_updated: &mut self.last_updated, - now: T::now(), - decay_params: decay_params, + offset_history_last_updated: &mut self.offset_history_last_updated, + } + } + + fn decayed_offset( + &self, offset: u64, duration_since_epoch: Duration, + decay_params: ProbabilisticScoringDecayParameters, + ) -> u64 { + let half_life = decay_params.liquidity_offset_half_life.as_secs_f64(); + if half_life != 0.0 { + let elapsed_time = duration_since_epoch.saturating_sub(self.last_updated).as_secs_f64(); + ((offset as f64) * powf64(0.5, elapsed_time / half_life)) as u64 + } else { + 0 } } } @@ -1133,7 +1138,8 @@ fn success_probability( (numerator, denominator) } -impl, BRT: Deref, T: Time, U: Deref> DirectedChannelLiquidity< L, BRT, T, U> { +impl, BRT: Deref, T: Deref> +DirectedChannelLiquidity< L, BRT, T> { /// 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 { @@ -1181,9 +1187,8 @@ impl, BRT: Deref, if score_params.historical_liquidity_penalty_multiplier_msat != 0 || score_params.historical_liquidity_penalty_amount_multiplier_msat != 0 { 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, score_params, amount_msat, - self.capacity_msat) + .calculate_success_probability_times_billion( + 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, @@ -1227,131 +1232,105 @@ impl, BRT: Deref, /// 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) + *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.capacity_msat - .saturating_sub(self.decayed_offset_msat(*self.max_liquidity_offset_msat)) - } - - fn decayed_offset_msat(&self, offset_msat: u64) -> u64 { - 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 - } + .saturating_sub(*self.max_liquidity_offset_msat) } } -impl, BRT: DerefMut, T: Time, U: DerefMut> DirectedChannelLiquidity { +impl, BRT: DerefMut, T: DerefMut> +DirectedChannelLiquidity { /// Adjusts the channel liquidity balance bounds when failing to route `amount_msat`. - fn failed_at_channel(&mut self, amount_msat: u64, chan_descr: fmt::Arguments, logger: &Log) where Log::Target: Logger { + fn failed_at_channel( + &mut self, amount_msat: u64, duration_since_epoch: Duration, chan_descr: fmt::Arguments, logger: &Log + ) where Log::Target: Logger { let existing_max_msat = self.max_liquidity_msat(); if amount_msat < existing_max_msat { log_debug!(logger, "Setting max liquidity of {} from {} to {}", chan_descr, existing_max_msat, amount_msat); - self.set_max_liquidity_msat(amount_msat); + self.set_max_liquidity_msat(amount_msat, duration_since_epoch); } else { log_trace!(logger, "Max liquidity of {} is {} (already less than or equal to {})", chan_descr, existing_max_msat, amount_msat); } - self.update_history_buckets(0); + self.update_history_buckets(0, duration_since_epoch); } /// Adjusts the channel liquidity balance bounds when failing to route `amount_msat` downstream. - fn failed_downstream(&mut self, amount_msat: u64, chan_descr: fmt::Arguments, logger: &Log) where Log::Target: Logger { + fn failed_downstream( + &mut self, amount_msat: u64, duration_since_epoch: Duration, chan_descr: fmt::Arguments, logger: &Log + ) where Log::Target: Logger { let existing_min_msat = self.min_liquidity_msat(); if amount_msat > existing_min_msat { log_debug!(logger, "Setting min liquidity of {} from {} to {}", existing_min_msat, chan_descr, amount_msat); - self.set_min_liquidity_msat(amount_msat); + self.set_min_liquidity_msat(amount_msat, duration_since_epoch); } else { log_trace!(logger, "Min liquidity of {} is {} (already greater than or equal to {})", chan_descr, existing_min_msat, amount_msat); } - self.update_history_buckets(0); + self.update_history_buckets(0, duration_since_epoch); } /// Adjusts the channel liquidity balance bounds when successfully routing `amount_msat`. - fn successful(&mut self, amount_msat: u64, chan_descr: fmt::Arguments, logger: &Log) where Log::Target: Logger { + fn successful(&mut self, + amount_msat: u64, duration_since_epoch: Duration, chan_descr: fmt::Arguments, logger: &Log + ) where Log::Target: Logger { 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(amount_msat); + self.set_max_liquidity_msat(max_liquidity_msat, duration_since_epoch); + self.update_history_buckets(amount_msat, duration_since_epoch); } /// 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.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); + fn update_history_buckets(&mut self, bucket_offset_msat: u64, duration_since_epoch: Duration) { self.liquidity_history.min_liquidity_offset_history.track_datapoint( - min_liquidity_offset_msat + bucket_offset_msat, self.capacity_msat + *self.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.liquidity_history.max_liquidity_offset_history.track_datapoint( - max_liquidity_offset_msat.saturating_sub(bucket_offset_msat), self.capacity_msat + self.max_liquidity_offset_msat.saturating_sub(bucket_offset_msat), self.capacity_msat ); + *self.offset_history_last_updated = duration_since_epoch; } /// Adjusts the lower bound of the channel liquidity balance in this direction. - fn set_min_liquidity_msat(&mut self, amount_msat: u64) { + fn set_min_liquidity_msat(&mut self, amount_msat: u64, duration_since_epoch: Duration) { *self.min_liquidity_offset_msat = amount_msat; - *self.max_liquidity_offset_msat = if amount_msat > self.max_liquidity_msat() { - 0 - } else { - self.decayed_offset_msat(*self.max_liquidity_offset_msat) - }; - *self.last_updated = self.now; + if amount_msat > self.max_liquidity_msat() { + *self.max_liquidity_offset_msat = 0; + } + *self.last_updated = duration_since_epoch; } /// Adjusts the upper bound of the channel liquidity balance in this direction. - fn set_max_liquidity_msat(&mut self, amount_msat: u64) { + fn set_max_liquidity_msat(&mut self, amount_msat: u64, duration_since_epoch: Duration) { *self.max_liquidity_offset_msat = self.capacity_msat.checked_sub(amount_msat).unwrap_or(0); - *self.min_liquidity_offset_msat = if amount_msat < self.min_liquidity_msat() { - 0 - } else { - self.decayed_offset_msat(*self.min_liquidity_offset_msat) - }; - *self.last_updated = self.now; + if amount_msat < *self.min_liquidity_offset_msat { + *self.min_liquidity_offset_msat = 0; + } + *self.last_updated = duration_since_epoch; } } -impl>, L: Deref, T: Time> ScoreLookUp for ProbabilisticScorerUsingTime where L::Target: Logger { +impl>, L: Deref> ScoreLookUp for ProbabilisticScorer where L::Target: Logger { type ScoreParams = ProbabilisticScoringFeeParameters; fn channel_penalty_msat( &self, candidate: &CandidateRouteHop, usage: ChannelUsage, score_params: &ProbabilisticScoringFeeParameters ) -> u64 { let (scid, target) = match candidate { - CandidateRouteHop::PublicHop { info, short_channel_id } => { + CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id }) => { (short_channel_id, info.target()) }, _ => return 0, }; let source = candidate.source(); - if let Some(penalty) = score_params.manual_node_penalties.get(&target) { + if let Some(penalty) = score_params.manual_node_penalties.get(target) { return *penalty; } @@ -1381,17 +1360,17 @@ impl>, L: Deref, T: Time> ScoreLookUp for Prob let amount_msat = usage.amount_msat.saturating_add(usage.inflight_htlc_msat); let capacity_msat = usage.effective_capacity.as_msat(); self.channel_liquidities - .get(&scid) - .unwrap_or(&ChannelLiquidity::new()) - .as_directed(&source, &target, capacity_msat, self.decay_params) + .get(scid) + .unwrap_or(&ChannelLiquidity::new(Duration::ZERO)) + .as_directed(&source, &target, capacity_msat) .penalty_msat(amount_msat, score_params) .saturating_add(anti_probing_penalty_msat) .saturating_add(base_penalty_msat) } } -impl>, L: Deref, T: Time> ScoreUpdate for ProbabilisticScorerUsingTime where L::Target: Logger { - fn payment_path_failed(&mut self, path: &Path, short_channel_id: u64) { +impl>, L: Deref> ScoreUpdate for ProbabilisticScorer where L::Target: Logger { + fn payment_path_failed(&mut self, path: &Path, short_channel_id: u64, duration_since_epoch: Duration) { 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); let network_graph = self.network_graph.read_only(); @@ -1412,15 +1391,17 @@ impl>, L: Deref, T: Time> ScoreUpdate for Prob if at_failed_channel { self.channel_liquidities .entry(hop.short_channel_id) - .or_insert_with(ChannelLiquidity::new) - .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); + .or_insert_with(|| ChannelLiquidity::new(duration_since_epoch)) + .as_directed_mut(source, &target, capacity_msat) + .failed_at_channel(amount_msat, duration_since_epoch, + 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, capacity_msat, self.decay_params) - .failed_downstream(amount_msat, format_args!("SCID {}, towards {:?}", hop.short_channel_id, target), &self.logger); + .or_insert_with(|| ChannelLiquidity::new(duration_since_epoch)) + .as_directed_mut(source, &target, capacity_msat) + .failed_downstream(amount_msat, duration_since_epoch, + format_args!("SCID {}, towards {:?}", hop.short_channel_id, target), &self.logger); } } else { log_debug!(self.logger, "Not able to penalize channel with SCID {} as we do not have graph info for it (likely a route-hint last-hop).", @@ -1430,7 +1411,7 @@ impl>, L: Deref, T: Time> ScoreUpdate for Prob } } - fn payment_path_successful(&mut self, path: &Path) { + fn payment_path_successful(&mut self, path: &Path, duration_since_epoch: Duration) { let amount_msat = path.final_value_msat(); log_trace!(self.logger, "Scoring path through SCID {} as having succeeded at {} msat.", path.hops.split_last().map(|(hop, _)| hop.short_channel_id).unwrap_or(0), amount_msat); @@ -1446,9 +1427,10 @@ impl>, L: Deref, T: Time> ScoreUpdate for Prob let capacity_msat = channel.effective_capacity().as_msat(); self.channel_liquidities .entry(hop.short_channel_id) - .or_insert_with(ChannelLiquidity::new) - .as_directed_mut(source, &target, capacity_msat, self.decay_params) - .successful(amount_msat, format_args!("SCID {}, towards {:?}", hop.short_channel_id, target), &self.logger); + .or_insert_with(|| ChannelLiquidity::new(duration_since_epoch)) + .as_directed_mut(source, &target, capacity_msat) + .successful(amount_msat, duration_since_epoch, + 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).", hop.short_channel_id); @@ -1456,19 +1438,59 @@ impl>, L: Deref, T: Time> ScoreUpdate for Prob } } - fn probe_failed(&mut self, path: &Path, short_channel_id: u64) { - self.payment_path_failed(path, short_channel_id) + fn probe_failed(&mut self, path: &Path, short_channel_id: u64, duration_since_epoch: Duration) { + self.payment_path_failed(path, short_channel_id, duration_since_epoch) } - fn probe_successful(&mut self, path: &Path) { - self.payment_path_failed(path, u64::max_value()) + fn probe_successful(&mut self, path: &Path, duration_since_epoch: Duration) { + self.payment_path_failed(path, u64::max_value(), duration_since_epoch) + } + + fn time_passed(&mut self, duration_since_epoch: Duration) { + let decay_params = self.decay_params; + self.channel_liquidities.retain(|_scid, liquidity| { + liquidity.min_liquidity_offset_msat = + liquidity.decayed_offset(liquidity.min_liquidity_offset_msat, duration_since_epoch, decay_params); + liquidity.max_liquidity_offset_msat = + liquidity.decayed_offset(liquidity.max_liquidity_offset_msat, duration_since_epoch, decay_params); + liquidity.last_updated = duration_since_epoch; + + let elapsed_time = + duration_since_epoch.saturating_sub(liquidity.offset_history_last_updated); + if elapsed_time > decay_params.historical_no_updates_half_life { + let half_life = decay_params.historical_no_updates_half_life.as_secs_f64(); + if half_life != 0.0 { + let divisor = powf64(2048.0, elapsed_time.as_secs_f64() / half_life) as u64; + for bucket in liquidity.min_liquidity_offset_history.buckets.iter_mut() { + *bucket = ((*bucket as u64) * 1024 / divisor) as u16; + } + for bucket in liquidity.max_liquidity_offset_history.buckets.iter_mut() { + *bucket = ((*bucket as u64) * 1024 / divisor) as u16; + } + liquidity.offset_history_last_updated = duration_since_epoch; + } + } + liquidity.min_liquidity_offset_msat != 0 || liquidity.max_liquidity_offset_msat != 0 || + liquidity.min_liquidity_offset_history.buckets != [0; 32] || + liquidity.max_liquidity_offset_history.buckets != [0; 32] + }); } } #[cfg(c_bindings)] -impl>, L: Deref, T: Time> Score for ProbabilisticScorerUsingTime +impl>, L: Deref> Score for ProbabilisticScorer where L::Target: Logger {} +#[cfg(feature = "std")] +#[inline] +fn powf64(n: f64, exp: f64) -> f64 { + n.powf(exp) +} +#[cfg(not(feature = "std"))] +fn powf64(n: f64, exp: f64) -> f64 { + libm::powf(n as f32, exp as f32) as f64 +} + mod approx { const BITS: u32 = 64; const HIGHEST_BIT: u32 = BITS - 1; @@ -1890,7 +1912,7 @@ mod bucketed_history { /// in each of 32 buckets. #[derive(Clone, Copy)] pub(super) struct HistoricalBucketRangeTracker { - buckets: [u16; 32], + pub(super) buckets: [u16; 32], } /// Buckets are stored in fixed point numbers with a 5 bit fractional part. Thus, the value @@ -1930,14 +1952,6 @@ mod bucketed_history { 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) }); @@ -1955,22 +1969,20 @@ mod bucketed_history { } impl> HistoricalMinMaxBuckets { - pub(super) fn get_decayed_buckets(&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(&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); + pub(super) fn calculate_success_probability_times_billion( + &self, params: &ProbabilisticScoringFeeParameters, amount_msat: u64, + capacity_msat: u64 + ) -> Option { + // 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; } let mut total_valid_points_tracked = 0; for (min_idx, min_bucket) in self.min_liquidity_offset_history.buckets.iter().enumerate() { @@ -1982,33 +1994,10 @@ mod bucketed_history { // 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() { + if total_valid_points_tracked < FULLY_DECAYED.into() { return None; } - Some((total_valid_points_tracked, required_decays)) - } - - #[inline] - pub(super) fn calculate_success_probability_times_billion( - &self, now: T, last_updated: T, half_life: Duration, - params: &ProbabilisticScoringFeeParameters, amount_msat: u64, capacity_msat: u64 - ) -> Option { - // 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 @@ -2067,7 +2056,7 @@ mod bucketed_history { } use bucketed_history::{LegacyHistoricalBucketRangeTracker, HistoricalBucketRangeTracker, HistoricalMinMaxBuckets}; -impl>, L: Deref, T: Time> Writeable for ProbabilisticScorerUsingTime where L::Target: Logger { +impl>, L: Deref> Writeable for ProbabilisticScorer where L::Target: Logger { #[inline] fn write(&self, w: &mut W) -> Result<(), io::Error> { write_tlv_fields!(w, { @@ -2077,14 +2066,14 @@ impl>, L: Deref, T: Time> Writeable for Probab } } -impl>, L: Deref, T: Time> -ReadableArgs<(ProbabilisticScoringDecayParameters, G, L)> for ProbabilisticScorerUsingTime where L::Target: Logger { +impl>, L: Deref> +ReadableArgs<(ProbabilisticScoringDecayParameters, G, L)> for ProbabilisticScorer where L::Target: Logger { #[inline] fn read( r: &mut R, args: (ProbabilisticScoringDecayParameters, G, L) ) -> Result { let (decay_params, network_graph, logger) = args; - let mut channel_liquidities = HashMap::new(); + let mut channel_liquidities = new_hash_map(); read_tlv_fields!(r, { (0, channel_liquidities, required), }); @@ -2097,24 +2086,24 @@ ReadableArgs<(ProbabilisticScoringDecayParameters, G, L)> for ProbabilisticScore } } -impl Writeable for ChannelLiquidity { +impl Writeable for ChannelLiquidity { #[inline] fn write(&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 was the min_liquidity_offset_history in octile form (2, self.max_liquidity_offset_msat, required), // 3 was the max_liquidity_offset_history in octile form - (4, duration_since_epoch, required), + (4, self.last_updated, required), (5, Some(self.min_liquidity_offset_history), option), (7, Some(self.max_liquidity_offset_history), option), + (9, self.offset_history_last_updated, required), }); Ok(()) } } -impl Readable for ChannelLiquidity { +impl Readable for ChannelLiquidity { #[inline] fn read(r: &mut R) -> Result { let mut min_liquidity_offset_msat = 0; @@ -2123,28 +2112,19 @@ impl Readable for ChannelLiquidity { let mut legacy_max_liq_offset_history: Option = None; let mut min_liquidity_offset_history: Option = None; let mut max_liquidity_offset_history: Option = None; - let mut duration_since_epoch = Duration::from_secs(0); + let mut last_updated = Duration::from_secs(0); + let mut offset_history_last_updated = None; read_tlv_fields!(r, { (0, min_liquidity_offset_msat, required), (1, legacy_min_liq_offset_history, option), (2, max_liquidity_offset_msat, required), (3, legacy_max_liq_offset_history, option), - (4, duration_since_epoch, required), + (4, last_updated, required), (5, min_liquidity_offset_history, option), (7, max_liquidity_offset_history, option), + (9, offset_history_last_updated, 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 - // is a time from a monotonic clock usually represented as an offset against boot time). - // Thus, we have to construct an `Instant` by subtracting the difference in wallclock time - // from the one that was written. However, because `Instant` can panic if we construct one - // in the future, we must handle wallclock time jumping backwards, which we do by simply - // using `Instant::now()` in that case. - let wall_clock_now = T::duration_since_epoch(); - let now = T::now(); - 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()); @@ -2165,22 +2145,21 @@ impl Readable for ChannelLiquidity { min_liquidity_offset_history: min_liquidity_offset_history.unwrap(), max_liquidity_offset_history: max_liquidity_offset_history.unwrap(), last_updated, + offset_history_last_updated: offset_history_last_updated.unwrap_or(last_updated), }) } } #[cfg(test)] mod tests { - use super::{ChannelLiquidity, HistoricalBucketRangeTracker, ProbabilisticScoringFeeParameters, ProbabilisticScoringDecayParameters, ProbabilisticScorerUsingTime}; + use super::{ChannelLiquidity, HistoricalBucketRangeTracker, ProbabilisticScoringFeeParameters, ProbabilisticScoringDecayParameters, ProbabilisticScorer}; use crate::blinded_path::{BlindedHop, BlindedPath}; use crate::util::config::UserConfig; - use crate::util::time::Time; - use crate::util::time::tests::SinceEpoch; use crate::ln::channelmanager; use crate::ln::msgs::{ChannelAnnouncement, ChannelUpdate, UnsignedChannelAnnouncement, UnsignedChannelUpdate}; use crate::routing::gossip::{EffectiveCapacity, NetworkGraph, NodeId}; - use crate::routing::router::{BlindedTail, Path, RouteHop, CandidateRouteHop}; + use crate::routing::router::{BlindedTail, Path, RouteHop, CandidateRouteHop, PublicHopCandidate}; use crate::routing::scoring::{ChannelUsage, ScoreLookUp, ScoreUpdate}; use crate::util::ser::{ReadableArgs, Writeable}; use crate::util::test_utils::{self, TestLogger}; @@ -2221,9 +2200,6 @@ mod tests { // `ProbabilisticScorer` tests - /// A probabilistic scorer for testing with time that can be manually advanced. - type ProbabilisticScorer<'a> = ProbabilisticScorerUsingTime::<&'a NetworkGraph<&'a TestLogger>, &'a TestLogger, SinceEpoch>; - fn sender_privkey() -> SecretKey { SecretKey::from_slice(&[41; 32]).unwrap() } @@ -2339,19 +2315,22 @@ mod tests { #[test] fn liquidity_bounds_directed_from_lowest_node_id() { let logger = TestLogger::new(); - let last_updated = SinceEpoch::now(); + let last_updated = Duration::ZERO; + let offset_history_last_updated = Duration::ZERO; let network_graph = network_graph(&logger); let decay_params = ProbabilisticScoringDecayParameters::default(); let mut scorer = ProbabilisticScorer::new(decay_params, &network_graph, &logger) .with_channel(42, ChannelLiquidity { - min_liquidity_offset_msat: 700, max_liquidity_offset_msat: 100, last_updated, + min_liquidity_offset_msat: 700, max_liquidity_offset_msat: 100, + last_updated, offset_history_last_updated, min_liquidity_offset_history: HistoricalBucketRangeTracker::new(), max_liquidity_offset_history: HistoricalBucketRangeTracker::new(), }) .with_channel(43, ChannelLiquidity { - min_liquidity_offset_msat: 700, max_liquidity_offset_msat: 100, last_updated, + min_liquidity_offset_msat: 700, max_liquidity_offset_msat: 100, + last_updated, offset_history_last_updated, min_liquidity_offset_history: HistoricalBucketRangeTracker::new(), max_liquidity_offset_history: HistoricalBucketRangeTracker::new(), }); @@ -2364,52 +2343,52 @@ mod tests { // Update minimum liquidity. let liquidity = scorer.channel_liquidities.get(&42).unwrap() - .as_directed(&source, &target, 1_000, decay_params); + .as_directed(&source, &target, 1_000); 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, 1_000, decay_params); + .as_directed(&target, &source, 1_000); 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, 1_000, decay_params) - .set_min_liquidity_msat(200); + .as_directed_mut(&source, &target, 1_000) + .set_min_liquidity_msat(200, Duration::ZERO); let liquidity = scorer.channel_liquidities.get(&42).unwrap() - .as_directed(&source, &target, 1_000, decay_params); + .as_directed(&source, &target, 1_000); 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, 1_000, decay_params); + .as_directed(&target, &source, 1_000); 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, 1_000, decay_params); + .as_directed(&target, &recipient, 1_000); 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, 1_000, decay_params); + .as_directed(&recipient, &target, 1_000); 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, 1_000, decay_params) - .set_max_liquidity_msat(200); + .as_directed_mut(&target, &recipient, 1_000) + .set_max_liquidity_msat(200, Duration::ZERO); let liquidity = scorer.channel_liquidities.get(&43).unwrap() - .as_directed(&target, &recipient, 1_000, decay_params); + .as_directed(&target, &recipient, 1_000); 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, 1_000, decay_params); + .as_directed(&recipient, &target, 1_000); assert_eq!(liquidity.min_liquidity_msat(), 800); assert_eq!(liquidity.max_liquidity_msat(), 1000); } @@ -2417,13 +2396,15 @@ mod tests { #[test] fn resets_liquidity_upper_bound_when_crossed_by_lower_bound() { let logger = TestLogger::new(); - let last_updated = SinceEpoch::now(); + let last_updated = Duration::ZERO; + let offset_history_last_updated = Duration::ZERO; let network_graph = network_graph(&logger); let decay_params = ProbabilisticScoringDecayParameters::default(); let mut scorer = ProbabilisticScorer::new(decay_params, &network_graph, &logger) .with_channel(42, ChannelLiquidity { - min_liquidity_offset_msat: 200, max_liquidity_offset_msat: 400, last_updated, + min_liquidity_offset_msat: 200, max_liquidity_offset_msat: 400, + last_updated, offset_history_last_updated, min_liquidity_offset_history: HistoricalBucketRangeTracker::new(), max_liquidity_offset_history: HistoricalBucketRangeTracker::new(), }); @@ -2433,42 +2414,42 @@ mod tests { // Check initial bounds. let liquidity = scorer.channel_liquidities.get(&42).unwrap() - .as_directed(&source, &target, 1_000, decay_params); + .as_directed(&source, &target, 1_000); 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, 1_000, decay_params); + .as_directed(&target, &source, 1_000); 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, 1_000, decay_params) - .set_min_liquidity_msat(900); + .as_directed_mut(&source, &target, 1_000) + .set_min_liquidity_msat(900, Duration::ZERO); let liquidity = scorer.channel_liquidities.get(&42).unwrap() - .as_directed(&source, &target, 1_000, decay_params); + .as_directed(&source, &target, 1_000); 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, 1_000, decay_params); + .as_directed(&target, &source, 1_000); 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, 1_000, decay_params) - .set_min_liquidity_msat(400); + .as_directed_mut(&target, &source, 1_000) + .set_min_liquidity_msat(400, Duration::ZERO); let liquidity = scorer.channel_liquidities.get(&42).unwrap() - .as_directed(&source, &target, 1_000, decay_params); + .as_directed(&source, &target, 1_000); 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, 1_000, decay_params); + .as_directed(&target, &source, 1_000); assert_eq!(liquidity.min_liquidity_msat(), 400); assert_eq!(liquidity.max_liquidity_msat(), 1_000); } @@ -2476,13 +2457,15 @@ mod tests { #[test] fn resets_liquidity_lower_bound_when_crossed_by_upper_bound() { let logger = TestLogger::new(); - let last_updated = SinceEpoch::now(); + let last_updated = Duration::ZERO; + let offset_history_last_updated = Duration::ZERO; let network_graph = network_graph(&logger); let decay_params = ProbabilisticScoringDecayParameters::default(); let mut scorer = ProbabilisticScorer::new(decay_params, &network_graph, &logger) .with_channel(42, ChannelLiquidity { - min_liquidity_offset_msat: 200, max_liquidity_offset_msat: 400, last_updated, + min_liquidity_offset_msat: 200, max_liquidity_offset_msat: 400, + last_updated, offset_history_last_updated, min_liquidity_offset_history: HistoricalBucketRangeTracker::new(), max_liquidity_offset_history: HistoricalBucketRangeTracker::new(), }); @@ -2492,42 +2475,42 @@ mod tests { // Check initial bounds. let liquidity = scorer.channel_liquidities.get(&42).unwrap() - .as_directed(&source, &target, 1_000, decay_params); + .as_directed(&source, &target, 1_000); 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, 1_000, decay_params); + .as_directed(&target, &source, 1_000); 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, 1_000, decay_params) - .set_max_liquidity_msat(300); + .as_directed_mut(&source, &target, 1_000) + .set_max_liquidity_msat(300, Duration::ZERO); let liquidity = scorer.channel_liquidities.get(&42).unwrap() - .as_directed(&source, &target, 1_000, decay_params); + .as_directed(&source, &target, 1_000); 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, 1_000, decay_params); + .as_directed(&target, &source, 1_000); 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, 1_000, decay_params) - .set_max_liquidity_msat(600); + .as_directed_mut(&target, &source, 1_000) + .set_max_liquidity_msat(600, Duration::ZERO); let liquidity = scorer.channel_liquidities.get(&42).unwrap() - .as_directed(&source, &target, 1_000, decay_params); + .as_directed(&source, &target, 1_000); 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, 1_000, decay_params); + .as_directed(&target, &source, 1_000); assert_eq!(liquidity.min_liquidity_msat(), 0); assert_eq!(liquidity.max_liquidity_msat(), 600); } @@ -2552,10 +2535,10 @@ mod tests { let network_graph = network_graph.read_only(); let channel = network_graph.channel(42).unwrap(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 0); let usage = ChannelUsage { amount_msat: 10_240, ..usage }; assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 0); @@ -2587,7 +2570,8 @@ mod tests { #[test] fn constant_penalty_outside_liquidity_bounds() { let logger = TestLogger::new(); - let last_updated = SinceEpoch::now(); + let last_updated = Duration::ZERO; + let offset_history_last_updated = Duration::ZERO; let network_graph = network_graph(&logger); let params = ProbabilisticScoringFeeParameters { liquidity_penalty_multiplier_msat: 1_000, @@ -2600,7 +2584,8 @@ mod tests { let scorer = ProbabilisticScorer::new(decay_params, &network_graph, &logger) .with_channel(42, ChannelLiquidity { - min_liquidity_offset_msat: 40, max_liquidity_offset_msat: 40, last_updated, + min_liquidity_offset_msat: 40, max_liquidity_offset_msat: 40, + last_updated, offset_history_last_updated, min_liquidity_offset_history: HistoricalBucketRangeTracker::new(), max_liquidity_offset_history: HistoricalBucketRangeTracker::new(), }); @@ -2613,10 +2598,10 @@ mod tests { }; let channel = network_graph.read_only().channel(42).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 0); let usage = ChannelUsage { amount_msat: 50, ..usage }; assert_ne!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 0); @@ -2644,17 +2629,17 @@ mod tests { let successful_path = payment_path_for_amount(200); let channel = &network_graph.read_only().channel(42).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 41, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 301); - scorer.payment_path_failed(&failed_path, 41); + scorer.payment_path_failed(&failed_path, 41, Duration::ZERO); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 301); - scorer.payment_path_successful(&successful_path); + scorer.payment_path_successful(&successful_path, Duration::ZERO); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 301); } @@ -2677,17 +2662,17 @@ mod tests { }; let channel = network_graph.read_only().channel(42).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 128); let usage = ChannelUsage { amount_msat: 500, ..usage }; assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 301); let usage = ChannelUsage { amount_msat: 750, ..usage }; assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 602); - scorer.payment_path_failed(&path, 43); + scorer.payment_path_failed(&path, 43, Duration::ZERO); let usage = ChannelUsage { amount_msat: 250, ..usage }; assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 0); @@ -2717,17 +2702,17 @@ mod tests { }; let channel = network_graph.read_only().channel(42).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 128); let usage = ChannelUsage { amount_msat: 500, ..usage }; assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 301); let usage = ChannelUsage { amount_msat: 750, ..usage }; assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 602); - scorer.payment_path_failed(&path, 42); + scorer.payment_path_failed(&path, 42, Duration::ZERO); let usage = ChannelUsage { amount_msat: 250, ..usage }; assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 300); @@ -2783,50 +2768,50 @@ mod tests { }; let channel = network_graph.read_only().channel(42).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&node_a).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 128); // Note that a default liquidity bound is used for B -> C as no channel exists let channel = network_graph.read_only().channel(42).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&node_b).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 43, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 128); let channel = network_graph.read_only().channel(44).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&node_c).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 44, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 128); - scorer.payment_path_failed(&Path { hops: path, blinded_tail: None }, 43); + scorer.payment_path_failed(&Path { hops: path, blinded_tail: None }, 43, Duration::ZERO); let channel = network_graph.read_only().channel(42).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&node_a).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 80); // Note that a default liquidity bound is used for B -> C as no channel exists let channel = network_graph.read_only().channel(42).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&node_b).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 43, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 128); let channel = network_graph.read_only().channel(44).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&node_c).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 44, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 128); } @@ -2849,25 +2834,25 @@ mod tests { let channel_42 = network_graph.get(&42).unwrap(); let channel_43 = network_graph.get(&43).unwrap(); let (info, _) = channel_42.as_directed_from(&source).unwrap(); - let candidate_41 = CandidateRouteHop::PublicHop { + let candidate_41 = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 41, - }; + }); let (info, target) = channel_42.as_directed_from(&source).unwrap(); - let candidate_42 = CandidateRouteHop::PublicHop { + let candidate_42 = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42, - }; + }); let (info, _) = channel_43.as_directed_from(&target).unwrap(); - let candidate_43 = CandidateRouteHop::PublicHop { + let candidate_43 = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 43, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate_41, usage, ¶ms), 128); assert_eq!(scorer.channel_penalty_msat(&candidate_42, usage, ¶ms), 128); assert_eq!(scorer.channel_penalty_msat(&candidate_43, usage, ¶ms), 128); - scorer.payment_path_successful(&payment_path_for_amount(500)); + scorer.payment_path_successful(&payment_path_for_amount(500), Duration::ZERO); assert_eq!(scorer.channel_penalty_msat(&candidate_41, usage, ¶ms), 128); assert_eq!(scorer.channel_penalty_msat(&candidate_42, usage, ¶ms), 300); @@ -2897,16 +2882,16 @@ mod tests { }; let channel = network_graph.read_only().channel(42).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 0); let usage = ChannelUsage { amount_msat: 1_023, ..usage }; assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 2_000); - scorer.payment_path_failed(&payment_path_for_amount(768), 42); - scorer.payment_path_failed(&payment_path_for_amount(128), 43); + scorer.payment_path_failed(&payment_path_for_amount(768), 42, Duration::ZERO); + scorer.payment_path_failed(&payment_path_for_amount(128), 43, Duration::ZERO); // Initial penalties let usage = ChannelUsage { amount_msat: 128, ..usage }; @@ -2918,19 +2903,8 @@ mod tests { let usage = ChannelUsage { amount_msat: 896, ..usage }; assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), u64::max_value()); - // No decay - SinceEpoch::advance(Duration::from_secs(4)); - let usage = ChannelUsage { amount_msat: 128, ..usage }; - assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 0); - let usage = ChannelUsage { amount_msat: 256, ..usage }; - assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 93); - let usage = ChannelUsage { amount_msat: 768, ..usage }; - assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 1_479); - let usage = ChannelUsage { amount_msat: 896, ..usage }; - assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), u64::max_value()); - // Half decay (i.e., three-quarter life) - SinceEpoch::advance(Duration::from_secs(1)); + scorer.time_passed(Duration::from_secs(5)); let usage = ChannelUsage { amount_msat: 128, ..usage }; assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 22); let usage = ChannelUsage { amount_msat: 256, ..usage }; @@ -2941,7 +2915,7 @@ mod tests { assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), u64::max_value()); // One decay (i.e., half life) - SinceEpoch::advance(Duration::from_secs(5)); + scorer.time_passed(Duration::from_secs(10)); let usage = ChannelUsage { amount_msat: 64, ..usage }; assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 0); let usage = ChannelUsage { amount_msat: 128, ..usage }; @@ -2952,7 +2926,7 @@ mod tests { assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), u64::max_value()); // Fully decay liquidity lower bound. - SinceEpoch::advance(Duration::from_secs(10 * 7)); + scorer.time_passed(Duration::from_secs(10 * 8)); let usage = ChannelUsage { amount_msat: 0, ..usage }; assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 0); let usage = ChannelUsage { amount_msat: 1, ..usage }; @@ -2963,58 +2937,19 @@ mod tests { assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), u64::max_value()); // Fully decay liquidity upper bound. - SinceEpoch::advance(Duration::from_secs(10)); + scorer.time_passed(Duration::from_secs(10 * 9)); let usage = ChannelUsage { amount_msat: 0, ..usage }; assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 0); let usage = ChannelUsage { amount_msat: 1_024, ..usage }; assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), u64::max_value()); - SinceEpoch::advance(Duration::from_secs(10)); + scorer.time_passed(Duration::from_secs(10 * 10)); let usage = ChannelUsage { amount_msat: 0, ..usage }; assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 0); let usage = ChannelUsage { amount_msat: 1_024, ..usage }; assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), u64::max_value()); } - #[test] - fn decays_liquidity_bounds_without_shift_overflow() { - let logger = TestLogger::new(); - let network_graph = network_graph(&logger); - let params = ProbabilisticScoringFeeParameters { - liquidity_penalty_multiplier_msat: 1_000, - ..ProbabilisticScoringFeeParameters::zero_penalty() - }; - let decay_params = ProbabilisticScoringDecayParameters { - liquidity_offset_half_life: Duration::from_secs(10), - ..ProbabilisticScoringDecayParameters::default() - }; - let mut scorer = ProbabilisticScorer::new(decay_params, &network_graph, &logger); - let source = source_node_id(); - let usage = ChannelUsage { - amount_msat: 256, - inflight_htlc_msat: 0, - effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_024, htlc_maximum_msat: 1_000 }, - }; - let channel = network_graph.read_only().channel(42).unwrap().to_owned(); - let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop { - info, - short_channel_id: 42, - }; - assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 125); - - scorer.payment_path_failed(&payment_path_for_amount(512), 42); - assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 281); - - // An unchecked right shift 64 bits or more in DirectedChannelLiquidity::decayed_offset_msat - // would cause an overflow. - SinceEpoch::advance(Duration::from_secs(10 * 64)); - assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 125); - - SinceEpoch::advance(Duration::from_secs(10)); - assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 125); - } - #[test] fn restricts_liquidity_bounds_after_decay() { let logger = TestLogger::new(); @@ -3036,34 +2971,34 @@ mod tests { }; let channel = network_graph.read_only().channel(42).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 300); // More knowledge gives higher confidence (256, 768), meaning a lower penalty. - scorer.payment_path_failed(&payment_path_for_amount(768), 42); - scorer.payment_path_failed(&payment_path_for_amount(256), 43); + scorer.payment_path_failed(&payment_path_for_amount(768), 42, Duration::ZERO); + scorer.payment_path_failed(&payment_path_for_amount(256), 43, Duration::ZERO); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 281); // Decaying knowledge gives less confidence (128, 896), meaning a higher penalty. - SinceEpoch::advance(Duration::from_secs(10)); + scorer.time_passed(Duration::from_secs(10)); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 291); // Reducing the upper bound gives more confidence (128, 832) that the payment amount (512) // is closer to the upper bound, meaning a higher penalty. - scorer.payment_path_successful(&payment_path_for_amount(64)); + scorer.payment_path_successful(&payment_path_for_amount(64), Duration::from_secs(10)); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 331); // Increasing the lower bound gives more confidence (256, 832) that the payment amount (512) // is closer to the lower bound, meaning a lower penalty. - scorer.payment_path_failed(&payment_path_for_amount(256), 43); + scorer.payment_path_failed(&payment_path_for_amount(256), 43, Duration::from_secs(10)); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 245); // Further decaying affects the lower bound more than the upper bound (128, 928). - SinceEpoch::advance(Duration::from_secs(10)); + scorer.time_passed(Duration::from_secs(20)); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 280); } @@ -3088,19 +3023,19 @@ mod tests { effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_000, htlc_maximum_msat: 1_000 }, }; - scorer.payment_path_failed(&payment_path_for_amount(500), 42); + scorer.payment_path_failed(&payment_path_for_amount(500), 42, Duration::ZERO); let channel = network_graph.read_only().channel(42).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), u64::max_value()); - SinceEpoch::advance(Duration::from_secs(10)); + scorer.time_passed(Duration::from_secs(10)); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 473); - scorer.payment_path_failed(&payment_path_for_amount(250), 43); + scorer.payment_path_failed(&payment_path_for_amount(250), 43, Duration::from_secs(10)); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 300); let mut serialized_scorer = Vec::new(); @@ -3108,12 +3043,11 @@ mod tests { let mut serialized_scorer = io::Cursor::new(&serialized_scorer); let deserialized_scorer = - ::read(&mut serialized_scorer, (decay_params, &network_graph, &logger)).unwrap(); + >::read(&mut serialized_scorer, (decay_params, &network_graph, &logger)).unwrap(); assert_eq!(deserialized_scorer.channel_penalty_msat(&candidate, usage, ¶ms), 300); } - #[test] - fn decays_persisted_liquidity_bounds() { + fn do_decays_persisted_liquidity_bounds(decay_before_reload: bool) { let logger = TestLogger::new(); let network_graph = network_graph(&logger); let params = ProbabilisticScoringFeeParameters { @@ -3133,32 +3067,44 @@ mod tests { effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_000, htlc_maximum_msat: 1_000 }, }; - scorer.payment_path_failed(&payment_path_for_amount(500), 42); + scorer.payment_path_failed(&payment_path_for_amount(500), 42, Duration::ZERO); let channel = network_graph.read_only().channel(42).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), u64::max_value()); + if decay_before_reload { + scorer.time_passed(Duration::from_secs(10)); + } + let mut serialized_scorer = Vec::new(); scorer.write(&mut serialized_scorer).unwrap(); - SinceEpoch::advance(Duration::from_secs(10)); - let mut serialized_scorer = io::Cursor::new(&serialized_scorer); - let deserialized_scorer = - ::read(&mut serialized_scorer, (decay_params, &network_graph, &logger)).unwrap(); + let mut deserialized_scorer = + >::read(&mut serialized_scorer, (decay_params, &network_graph, &logger)).unwrap(); + if !decay_before_reload { + scorer.time_passed(Duration::from_secs(10)); + deserialized_scorer.time_passed(Duration::from_secs(10)); + } assert_eq!(deserialized_scorer.channel_penalty_msat(&candidate, usage, ¶ms), 473); - scorer.payment_path_failed(&payment_path_for_amount(250), 43); + scorer.payment_path_failed(&payment_path_for_amount(250), 43, Duration::from_secs(10)); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 300); - SinceEpoch::advance(Duration::from_secs(10)); + deserialized_scorer.time_passed(Duration::from_secs(20)); assert_eq!(deserialized_scorer.channel_penalty_msat(&candidate, usage, ¶ms), 370); } + #[test] + fn decays_persisted_liquidity_bounds() { + do_decays_persisted_liquidity_bounds(false); + do_decays_persisted_liquidity_bounds(true); + } + #[test] fn scores_realistic_payments() { // Shows the scores of "realistic" sends of 100k sats over channels of 1-10m sats (with a @@ -3176,10 +3122,10 @@ mod tests { }; let channel = network_graph.read_only().channel(42).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 11497); let usage = ChannelUsage { effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_950_000_000, htlc_maximum_msat: 1_000 }, ..usage @@ -3241,10 +3187,10 @@ mod tests { let scorer = ProbabilisticScorer::new(ProbabilisticScoringDecayParameters::default(), &network_graph, &logger); let channel = network_graph.read_only().channel(42).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 58); let params = ProbabilisticScoringFeeParameters { @@ -3283,10 +3229,10 @@ mod tests { let scorer = ProbabilisticScorer::new(ProbabilisticScoringDecayParameters::default(), &network_graph, &logger); let channel = network_graph.read_only().channel(42).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 300); let params = ProbabilisticScoringFeeParameters { @@ -3315,10 +3261,10 @@ mod tests { let decay_params = ProbabilisticScoringDecayParameters::zero_penalty(); let channel = network_graph.read_only().channel(42).unwrap().to_owned(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42, - }; + }); let scorer = ProbabilisticScorer::new(decay_params, &network_graph, &logger); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 80_000); } @@ -3342,10 +3288,10 @@ mod tests { let network_graph = network_graph.read_only(); let channel = network_graph.channel(42).unwrap(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42, - }; + }); assert_ne!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), u64::max_value()); let usage = ChannelUsage { inflight_htlc_msat: 251, ..usage }; @@ -3369,10 +3315,10 @@ mod tests { let network_graph = network_graph.read_only(); let channel = network_graph.channel(42).unwrap(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), base_penalty_msat); let usage = ChannelUsage { amount_msat: 1_000, ..usage }; @@ -3414,10 +3360,10 @@ mod tests { let network_graph = network_graph.read_only(); let channel = network_graph.channel(42).unwrap(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42, - }; + }); // With no historical data the normal liquidity penalty calculation is used. assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 168); @@ -3427,15 +3373,15 @@ mod tests { assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, 42, ¶ms), None); - scorer.payment_path_failed(&payment_path_for_amount(1), 42); + scorer.payment_path_failed(&payment_path_for_amount(1), 42, Duration::ZERO); { let network_graph = network_graph.read_only(); let channel = network_graph.channel(42).unwrap(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 2048); assert_eq!(scorer.channel_penalty_msat(&candidate, usage_1, ¶ms), 249); @@ -3452,15 +3398,15 @@ mod tests { // 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); + scorer.payment_path_failed(&payment_path_for_amount(1000), 43, Duration::ZERO); { let network_graph = network_graph.read_only(); let channel = network_graph.channel(42).unwrap(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 105); } @@ -3482,42 +3428,40 @@ mod tests { // 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)); + scorer.time_passed(Duration::from_secs(10 * 16)); { let network_graph = network_graph.read_only(); let channel = network_graph.channel(42).unwrap(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate, 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), - None); + Some(([0; 32], [0; 32]))); assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, 1, ¶ms), None); - let mut usage = ChannelUsage { + let 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); + scorer.payment_path_failed(&payment_path_for_amount(1), 42, Duration::from_secs(10 * 16)); { let network_graph = network_graph.read_only(); let channel = network_graph.channel(42).unwrap(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 2050); - usage.inflight_htlc_msat = 0; - assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 866); let usage = ChannelUsage { amount_msat: 1, @@ -3528,7 +3472,12 @@ mod tests { } // Advance to decay all liquidity offsets to zero. - SinceEpoch::advance(Duration::from_secs(60 * 60 * 10)); + scorer.time_passed(Duration::from_secs(10 * (16 + 60 * 60))); + + // Once even the bounds have decayed information about the channel should be removed + // entirely. + assert_eq!(scorer.historical_estimated_channel_liquidity_probabilities(42, &target), + None); // Use a path in the opposite direction, which have zero for htlc_maximum_msat. This will // ensure that the effective capacity is zero to test division-by-zero edge cases. @@ -3537,7 +3486,7 @@ mod tests { path_hop(source_pubkey(), 42, 1), path_hop(sender_pubkey(), 41, 0), ]; - scorer.payment_path_failed(&Path { hops: path, blinded_tail: None }, 42); + scorer.payment_path_failed(&Path { hops: path, blinded_tail: None }, 42, Duration::from_secs(10 * (16 + 60 * 60))); } #[test] @@ -3560,10 +3509,10 @@ mod tests { let network_graph = network_graph.read_only(); let channel = network_graph.channel(42).unwrap(); let (info, _) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 0); // Check we receive anti-probing penalty for htlc_maximum_msat == channel_capacity. @@ -3610,10 +3559,10 @@ mod tests { }; let channel = network_graph.read_only().channel(42).unwrap().to_owned(); let (info, target) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42, - }; + }); assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 300); let mut path = payment_path_for_amount(768); @@ -3636,12 +3585,12 @@ mod tests { // final value is taken into account. assert!(scorer.channel_liquidities.get(&42).is_none()); - scorer.payment_path_failed(&path, 42); + scorer.payment_path_failed(&path, 42, Duration::ZERO); path.blinded_tail.as_mut().unwrap().final_value_msat = 256; - scorer.payment_path_failed(&path, 43); + scorer.payment_path_failed(&path, 43, Duration::ZERO); let liquidity = scorer.channel_liquidities.get(&42).unwrap() - .as_directed(&source, &target, 1_000, decay_params); + .as_directed(&source, &target, 1_000); assert_eq!(liquidity.min_liquidity_msat(), 256); assert_eq!(liquidity.max_liquidity_msat(), 768); } @@ -3679,10 +3628,10 @@ mod tests { }; let channel = network_graph.read_only().channel(42).unwrap().to_owned(); let (info, target) = channel.as_directed_from(&source).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info, short_channel_id: 42, - }; + }); // 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(&candidate, usage, ¶ms), 1269); @@ -3692,7 +3641,7 @@ mod tests { None); // Fail to pay once, and then check the buckets and penalty. - scorer.payment_path_failed(&payment_path_for_amount(amount_msat), 42); + scorer.payment_path_failed(&payment_path_for_amount(amount_msat), 42, Duration::ZERO); // 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(&candidate, usage, ¶ms), @@ -3716,7 +3665,7 @@ mod tests { // ...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); + scorer.payment_path_failed(&payment_path_for_amount(amount_msat), 42, Duration::ZERO); 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]))); @@ -3724,3 +3673,61 @@ mod tests { Some(0.0)); } } + +#[cfg(ldk_bench)] +pub mod benches { + use super::*; + use criterion::Criterion; + use crate::routing::router::{bench_utils, RouteHop}; + use crate::util::test_utils::TestLogger; + use crate::ln::features::{ChannelFeatures, NodeFeatures}; + + pub fn decay_100k_channel_bounds(bench: &mut Criterion) { + let logger = TestLogger::new(); + let network_graph = bench_utils::read_network_graph(&logger).unwrap(); + let mut scorer = ProbabilisticScorer::new(Default::default(), &network_graph, &logger); + // Score a number of random channels + let mut seed: u64 = 0xdeadbeef; + for _ in 0..100_000 { + seed = seed.overflowing_mul(6364136223846793005).0.overflowing_add(1).0; + let (victim, victim_dst, amt) = { + let rong = network_graph.read_only(); + let channels = rong.channels(); + let chan = channels.unordered_iter() + .skip((seed as usize) % channels.len()) + .next().unwrap(); + seed = seed.overflowing_mul(6364136223846793005).0.overflowing_add(1).0; + let amt = seed % chan.1.capacity_sats.map(|c| c * 1000) + .or(chan.1.one_to_two.as_ref().map(|info| info.htlc_maximum_msat)) + .or(chan.1.two_to_one.as_ref().map(|info| info.htlc_maximum_msat)) + .unwrap_or(1_000_000_000).saturating_add(1); + (*chan.0, chan.1.node_two, amt) + }; + let path = Path { + hops: vec![RouteHop { + pubkey: victim_dst.as_pubkey().unwrap(), + node_features: NodeFeatures::empty(), + short_channel_id: victim, + channel_features: ChannelFeatures::empty(), + fee_msat: amt, + cltv_expiry_delta: 42, + maybe_announced_channel: true, + }], + blinded_tail: None + }; + seed = seed.overflowing_mul(6364136223846793005).0.overflowing_add(1).0; + if seed % 1 == 0 { + scorer.probe_failed(&path, victim, Duration::ZERO); + } else { + scorer.probe_successful(&path, Duration::ZERO); + } + } + let mut cur_time = Duration::ZERO; + cur_time += Duration::from_millis(1); + scorer.time_passed(cur_time); + bench.bench_function("decay_100k_channel_bounds", |b| b.iter(|| { + cur_time += Duration::from_millis(1); + scorer.time_passed(cur_time); + })); + } +} diff --git a/lightning/src/routing/utxo.rs b/lightning/src/routing/utxo.rs index e190d4258ae..ada90345ee6 100644 --- a/lightning/src/routing/utxo.rs +++ b/lightning/src/routing/utxo.rs @@ -307,7 +307,7 @@ pub(super) struct PendingChecks { impl PendingChecks { pub(super) fn new() -> Self { PendingChecks { internal: Mutex::new(PendingChecksContext { - channels: HashMap::new(), nodes: HashMap::new(), + channels: new_hash_map(), nodes: new_hash_map(), }) } } diff --git a/lightning/src/sign/ecdsa.rs b/lightning/src/sign/ecdsa.rs index 5a1714c6022..592bc4cb697 100644 --- a/lightning/src/sign/ecdsa.rs +++ b/lightning/src/sign/ecdsa.rs @@ -52,6 +52,13 @@ pub trait EcdsaChannelSigner: ChannelSigner { /// This may be called multiple times for the same transaction. /// /// An external signer implementation should check that the commitment has not been revoked. + /// + /// An `Err` can be returned to signal that the signer is unavailable/cannot produce a valid + /// signature and should be retried later. Once the signer is ready to provide a signature after + /// previously returning an `Err`, [`ChannelMonitor::signer_unblocked`] must be called on its + /// monitor. + /// + /// [`ChannelMonitor::signer_unblocked`]: crate::chain::channelmonitor::ChannelMonitor::signer_unblocked // // TODO: Document the things someone using this interface should enforce before signing. fn sign_holder_commitment(&self, commitment_tx: &HolderCommitmentTransaction, @@ -77,6 +84,13 @@ pub trait EcdsaChannelSigner: ChannelSigner { /// revoked the state which they eventually broadcast. It's not a _holder_ secret key and does /// not allow the spending of any funds by itself (you need our holder `revocation_secret` to do /// so). + /// + /// An `Err` can be returned to signal that the signer is unavailable/cannot produce a valid + /// signature and should be retried later. Once the signer is ready to provide a signature after + /// previously returning an `Err`, [`ChannelMonitor::signer_unblocked`] must be called on its + /// monitor. + /// + /// [`ChannelMonitor::signer_unblocked`]: crate::chain::channelmonitor::ChannelMonitor::signer_unblocked fn sign_justice_revoked_output(&self, justice_tx: &Transaction, input: usize, amount: u64, per_commitment_key: &SecretKey, secp_ctx: &Secp256k1 ) -> Result; @@ -98,6 +112,13 @@ pub trait EcdsaChannelSigner: ChannelSigner { /// /// `htlc` holds HTLC elements (hash, timelock), thus changing the format of the witness script /// (which is committed to in the BIP 143 signatures). + /// + /// An `Err` can be returned to signal that the signer is unavailable/cannot produce a valid + /// signature and should be retried later. Once the signer is ready to provide a signature after + /// previously returning an `Err`, [`ChannelMonitor::signer_unblocked`] must be called on its + /// monitor. + /// + /// [`ChannelMonitor::signer_unblocked`]: crate::chain::channelmonitor::ChannelMonitor::signer_unblocked fn sign_justice_revoked_htlc(&self, justice_tx: &Transaction, input: usize, amount: u64, per_commitment_key: &SecretKey, htlc: &HTLCOutputInCommitment, secp_ctx: &Secp256k1) -> Result; @@ -109,8 +130,14 @@ pub trait EcdsaChannelSigner: ChannelSigner { /// [`ChannelMonitor`] [replica](https://github.com/lightningdevkit/rust-lightning/blob/main/GLOSSARY.md#monitor-replicas) /// broadcasts it before receiving the update for the latest commitment transaction. /// + /// An `Err` can be returned to signal that the signer is unavailable/cannot produce a valid + /// signature and should be retried later. Once the signer is ready to provide a signature after + /// previously returning an `Err`, [`ChannelMonitor::signer_unblocked`] must be called on its + /// monitor. + /// /// [`EcdsaSighashType::All`]: bitcoin::sighash::EcdsaSighashType::All /// [`ChannelMonitor`]: crate::chain::channelmonitor::ChannelMonitor + /// [`ChannelMonitor::signer_unblocked`]: crate::chain::channelmonitor::ChannelMonitor::signer_unblocked fn sign_holder_htlc_transaction(&self, htlc_tx: &Transaction, input: usize, htlc_descriptor: &HTLCDescriptor, secp_ctx: &Secp256k1 ) -> Result; @@ -131,6 +158,13 @@ pub trait EcdsaChannelSigner: ChannelSigner { /// detected onchain. It has been generated by our counterparty and is used to derive /// channel state keys, which are then included in the witness script and committed to in the /// BIP 143 signature. + /// + /// An `Err` can be returned to signal that the signer is unavailable/cannot produce a valid + /// signature and should be retried later. Once the signer is ready to provide a signature after + /// previously returning an `Err`, [`ChannelMonitor::signer_unblocked`] must be called on its + /// monitor. + /// + /// [`ChannelMonitor::signer_unblocked`]: crate::chain::channelmonitor::ChannelMonitor::signer_unblocked fn sign_counterparty_htlc_transaction(&self, htlc_tx: &Transaction, input: usize, amount: u64, per_commitment_point: &PublicKey, htlc: &HTLCOutputInCommitment, secp_ctx: &Secp256k1) -> Result; @@ -142,6 +176,13 @@ pub trait EcdsaChannelSigner: ChannelSigner { secp_ctx: &Secp256k1) -> Result; /// Computes the signature for a commitment transaction's anchor output used as an /// input within `anchor_tx`, which spends the commitment transaction, at index `input`. + /// + /// An `Err` can be returned to signal that the signer is unavailable/cannot produce a valid + /// signature and should be retried later. Once the signer is ready to provide a signature after + /// previously returning an `Err`, [`ChannelMonitor::signer_unblocked`] must be called on its + /// monitor. + /// + /// [`ChannelMonitor::signer_unblocked`]: crate::chain::channelmonitor::ChannelMonitor::signer_unblocked fn sign_holder_anchor_input( &self, anchor_tx: &Transaction, input: usize, secp_ctx: &Secp256k1, ) -> Result; diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs index 3913e0a3ef3..e78c8c33c36 100644 --- a/lightning/src/sign/mod.rs +++ b/lightning/src/sign/mod.rs @@ -38,7 +38,7 @@ use bitcoin::secp256k1::schnorr; use bitcoin::{secp256k1, Sequence, Witness, Txid}; use crate::util::transaction_utils; -use crate::util::crypto::{hkdf_extract_expand_twice, sign, sign_with_aux_rand}; +use crate::crypto::utils::{hkdf_extract_expand_twice, sign, sign_with_aux_rand}; use crate::util::ser::{Writeable, Writer, Readable, ReadableArgs}; use crate::chain::transaction::OutPoint; use crate::ln::channel::ANCHOR_OUTPUT_VALUE_SATOSHI; @@ -65,7 +65,7 @@ use crate::sign::ecdsa::{EcdsaChannelSigner, WriteableEcdsaChannelSigner}; #[cfg(taproot)] use crate::sign::taproot::TaprootChannelSigner; use crate::util::atomic_counter::AtomicCounter; -use crate::util::chacha20::ChaCha20; +use crate::crypto::chacha20::ChaCha20; use crate::util::invoice::construct_invoice_preimage; pub(crate) mod type_resolver; @@ -350,7 +350,7 @@ impl SpendableOutputDescriptor { let mut input = Vec::with_capacity(descriptors.len()); let mut input_value = 0; let mut witness_weight = 0; - let mut output_set = HashSet::with_capacity(descriptors.len()); + let mut output_set = hash_set_with_capacity(descriptors.len()); for outp in descriptors { match outp { SpendableOutputDescriptor::StaticPaymentOutput(descriptor) => { @@ -826,11 +826,8 @@ pub struct InMemorySigner { channel_value_satoshis: u64, /// Key derivation parameters. channel_keys_id: [u8; 32], - /// Seed from which all randomness produced is derived from. - rand_bytes_unique_start: [u8; 32], - /// Tracks the number of times we've produced randomness to ensure we don't return the same - /// bytes twice. - rand_bytes_index: AtomicCounter, + /// A source of random bytes. + entropy_source: RandomBytes, } impl PartialEq for InMemorySigner { @@ -861,8 +858,7 @@ impl Clone for InMemorySigner { channel_parameters: self.channel_parameters.clone(), channel_value_satoshis: self.channel_value_satoshis, channel_keys_id: self.channel_keys_id, - rand_bytes_unique_start: self.get_secure_random_bytes(), - rand_bytes_index: AtomicCounter::new(), + entropy_source: RandomBytes::new(self.get_secure_random_bytes()), } } } @@ -896,8 +892,7 @@ impl InMemorySigner { holder_channel_pubkeys, channel_parameters: None, channel_keys_id, - rand_bytes_unique_start, - rand_bytes_index: AtomicCounter::new(), + entropy_source: RandomBytes::new(rand_bytes_unique_start), } } @@ -1073,10 +1068,7 @@ impl InMemorySigner { impl EntropySource for InMemorySigner { fn get_secure_random_bytes(&self) -> [u8; 32] { - let index = self.rand_bytes_index.get_increment(); - let mut nonce = [0u8; 16]; - nonce[..8].copy_from_slice(&index.to_be_bytes()); - ChaCha20::get_single_block(&self.rand_bytes_unique_start, &nonce) + self.entropy_source.get_secure_random_bytes() } } @@ -1372,8 +1364,7 @@ impl ReadableArgs for InMemorySigner where ES::Target: EntropySou holder_channel_pubkeys, channel_parameters: counterparty_channel_data, channel_keys_id: keys_id, - rand_bytes_unique_start: entropy_source.get_secure_random_bytes(), - rand_bytes_index: AtomicCounter::new(), + entropy_source: RandomBytes::new(entropy_source.get_secure_random_bytes()), }) } } @@ -1401,8 +1392,7 @@ pub struct KeysManager { channel_master_key: ExtendedPrivKey, channel_child_index: AtomicUsize, - rand_bytes_unique_start: [u8; 32], - rand_bytes_index: AtomicCounter, + entropy_source: RandomBytes, seed: [u8; 32], starting_time_secs: u64, @@ -1471,8 +1461,7 @@ impl KeysManager { channel_master_key, channel_child_index: AtomicUsize::new(0), - rand_bytes_unique_start, - rand_bytes_index: AtomicCounter::new(), + entropy_source: RandomBytes::new(rand_bytes_unique_start), seed: *seed, starting_time_secs, @@ -1653,10 +1642,7 @@ impl KeysManager { impl EntropySource for KeysManager { fn get_secure_random_bytes(&self) -> [u8; 32] { - let index = self.rand_bytes_index.get_increment(); - let mut nonce = [0u8; 16]; - nonce[..8].copy_from_slice(&index.to_be_bytes()); - ChaCha20::get_single_block(&self.rand_bytes_unique_start, &nonce) + self.entropy_source.get_secure_random_bytes() } } @@ -1910,6 +1896,35 @@ impl PhantomKeysManager { } } +/// An implementation of [`EntropySource`] using ChaCha20. +#[derive(Debug)] +pub struct RandomBytes { + /// Seed from which all randomness produced is derived from. + seed: [u8; 32], + /// Tracks the number of times we've produced randomness to ensure we don't return the same + /// bytes twice. + index: AtomicCounter, +} + +impl RandomBytes { + /// Creates a new instance using the given seed. + pub fn new(seed: [u8; 32]) -> Self { + Self { + seed, + index: AtomicCounter::new(), + } + } +} + +impl EntropySource for RandomBytes { + fn get_secure_random_bytes(&self) -> [u8; 32] { + let index = self.index.get_increment(); + let mut nonce = [0u8; 16]; + nonce[..8].copy_from_slice(&index.to_be_bytes()); + ChaCha20::get_single_block(&self.seed, &nonce) + } +} + // Ensure that EcdsaChannelSigner can have a vtable #[test] pub fn dyn_sign() { diff --git a/lightning/src/sync/debug_sync.rs b/lightning/src/sync/debug_sync.rs index b9f015af656..2b75e095380 100644 --- a/lightning/src/sync/debug_sync.rs +++ b/lightning/src/sync/debug_sync.rs @@ -14,7 +14,7 @@ use std::sync::Condvar as StdCondvar; pub use std::sync::WaitTimeoutResult; -use crate::prelude::HashMap; +use crate::prelude::*; use super::{LockTestExt, LockHeldState}; @@ -57,7 +57,7 @@ impl Condvar { thread_local! { /// We track the set of locks currently held by a reference to their `LockMetadata` - static LOCKS_HELD: RefCell>> = RefCell::new(HashMap::new()); + static LOCKS_HELD: RefCell>> = RefCell::new(new_hash_map()); } static LOCK_IDX: AtomicUsize = AtomicUsize::new(0); @@ -113,7 +113,7 @@ impl LockMetadata { let lock_idx = LOCK_IDX.fetch_add(1, Ordering::Relaxed) as u64; let res = Arc::new(LockMetadata { - locked_before: StdMutex::new(HashMap::new()), + locked_before: StdMutex::new(new_hash_map()), lock_idx, _lock_construction_bt: backtrace, }); @@ -122,7 +122,7 @@ impl LockMetadata { { let (lock_constr_location, lock_constr_colno) = locate_call_symbol(&res._lock_construction_bt); - LOCKS_INIT.call_once(|| { unsafe { LOCKS = Some(StdMutex::new(HashMap::new())); } }); + LOCKS_INIT.call_once(|| { unsafe { LOCKS = Some(StdMutex::new(new_hash_map())); } }); let mut locks = unsafe { LOCKS.as_ref() }.unwrap().lock().unwrap(); match locks.entry(lock_constr_location) { hash_map::Entry::Occupied(e) => { diff --git a/lightning/src/sync/nostd_sync.rs b/lightning/src/sync/nostd_sync.rs index 27cfb9b8f78..0f92bd6caa3 100644 --- a/lightning/src/sync/nostd_sync.rs +++ b/lightning/src/sync/nostd_sync.rs @@ -37,10 +37,6 @@ impl Mutex { Ok(MutexGuard { lock: self.inner.borrow_mut() }) } - pub fn try_lock<'a>(&'a self) -> LockResult> { - Ok(MutexGuard { lock: self.inner.borrow_mut() }) - } - pub fn into_inner(self) -> LockResult { Ok(self.inner.into_inner()) } diff --git a/lightning/src/util/chacha20poly1305rfc.rs b/lightning/src/util/chacha20poly1305rfc.rs deleted file mode 100644 index d5792e0ac2b..00000000000 --- a/lightning/src/util/chacha20poly1305rfc.rs +++ /dev/null @@ -1,427 +0,0 @@ -// ring has a garbage API so its use is avoided, but rust-crypto doesn't have RFC-variant poly1305 -// Instead, we steal rust-crypto's implementation and tweak it to match the RFC. -// -// This file is licensed under the Apache License, Version 2.0 or the MIT license -// , at your option. -// You may not use this file except in accordance with one or both of these -// licenses. -// -// This is a port of Andrew Moons poly1305-donna -// https://github.com/floodyberry/poly1305-donna - -use crate::ln::msgs::DecodeError; -use crate::util::ser::{FixedLengthReader, LengthRead, LengthReadableArgs, Readable, Writeable, Writer}; -use crate::io::{self, Read, Write}; - -#[cfg(not(fuzzing))] -mod real_chachapoly { - use crate::util::chacha20::ChaCha20; - use crate::util::poly1305::Poly1305; - use bitcoin::hashes::cmp::fixed_time_eq; - - #[derive(Clone, Copy)] - pub struct ChaCha20Poly1305RFC { - cipher: ChaCha20, - mac: Poly1305, - finished: bool, - data_len: usize, - aad_len: u64, - } - - impl ChaCha20Poly1305RFC { - #[inline] - fn pad_mac_16(mac: &mut Poly1305, len: usize) { - if len % 16 != 0 { - mac.input(&[0; 16][0..16 - (len % 16)]); - } - } - pub fn new(key: &[u8], nonce: &[u8], aad: &[u8]) -> ChaCha20Poly1305RFC { - assert!(key.len() == 16 || key.len() == 32); - assert!(nonce.len() == 12); - - // Ehh, I'm too lazy to *also* tweak ChaCha20 to make it RFC-compliant - assert!(nonce[0] == 0 && nonce[1] == 0 && nonce[2] == 0 && nonce[3] == 0); - - let mut cipher = ChaCha20::new(key, &nonce[4..]); - let mut mac_key = [0u8; 64]; - let zero_key = [0u8; 64]; - cipher.process(&zero_key, &mut mac_key); - - let mut mac = Poly1305::new(&mac_key[..32]); - mac.input(aad); - ChaCha20Poly1305RFC::pad_mac_16(&mut mac, aad.len()); - - ChaCha20Poly1305RFC { - cipher, - mac, - finished: false, - data_len: 0, - aad_len: aad.len() as u64, - } - } - - pub fn encrypt(&mut self, input: &[u8], output: &mut [u8], out_tag: &mut [u8]) { - assert!(input.len() == output.len()); - assert!(self.finished == false); - self.cipher.process(input, output); - self.data_len += input.len(); - self.mac.input(output); - ChaCha20Poly1305RFC::pad_mac_16(&mut self.mac, self.data_len); - self.finished = true; - self.mac.input(&self.aad_len.to_le_bytes()); - self.mac.input(&(self.data_len as u64).to_le_bytes()); - self.mac.raw_result(out_tag); - } - - pub fn encrypt_full_message_in_place(&mut self, input_output: &mut [u8], out_tag: &mut [u8]) { - self.encrypt_in_place(input_output); - self.finish_and_get_tag(out_tag); - } - - // Encrypt `input_output` in-place. To finish and calculate the tag, use `finish_and_get_tag` - // below. - pub(super) fn encrypt_in_place(&mut self, input_output: &mut [u8]) { - debug_assert!(self.finished == false); - self.cipher.process_in_place(input_output); - self.data_len += input_output.len(); - self.mac.input(input_output); - } - - // If we were previously encrypting with `encrypt_in_place`, this method can be used to finish - // encrypting and calculate the tag. - pub(super) fn finish_and_get_tag(&mut self, out_tag: &mut [u8]) { - debug_assert!(self.finished == false); - ChaCha20Poly1305RFC::pad_mac_16(&mut self.mac, self.data_len); - self.finished = true; - self.mac.input(&self.aad_len.to_le_bytes()); - self.mac.input(&(self.data_len as u64).to_le_bytes()); - self.mac.raw_result(out_tag); - } - - pub fn decrypt(&mut self, input: &[u8], output: &mut [u8], tag: &[u8]) -> bool { - assert!(input.len() == output.len()); - assert!(self.finished == false); - - self.finished = true; - - self.mac.input(input); - - self.data_len += input.len(); - ChaCha20Poly1305RFC::pad_mac_16(&mut self.mac, self.data_len); - self.mac.input(&self.aad_len.to_le_bytes()); - self.mac.input(&(self.data_len as u64).to_le_bytes()); - - let mut calc_tag = [0u8; 16]; - self.mac.raw_result(&mut calc_tag); - if fixed_time_eq(&calc_tag, tag) { - self.cipher.process(input, output); - true - } else { - false - } - } - - pub fn check_decrypt_in_place(&mut self, input_output: &mut [u8], tag: &[u8]) -> Result<(), ()> { - self.decrypt_in_place(input_output); - if self.finish_and_check_tag(tag) { Ok(()) } else { Err(()) } - } - - /// Decrypt in place, without checking the tag. Use `finish_and_check_tag` to check it - /// later when decryption finishes. - /// - /// Should never be `pub` because the public API should always enforce tag checking. - pub(super) fn decrypt_in_place(&mut self, input_output: &mut [u8]) { - debug_assert!(self.finished == false); - self.mac.input(input_output); - self.data_len += input_output.len(); - self.cipher.process_in_place(input_output); - } - - /// If we were previously decrypting with `just_decrypt_in_place`, this method must be used - /// to check the tag. Returns whether or not the tag is valid. - pub(super) fn finish_and_check_tag(&mut self, tag: &[u8]) -> bool { - debug_assert!(self.finished == false); - self.finished = true; - ChaCha20Poly1305RFC::pad_mac_16(&mut self.mac, self.data_len); - self.mac.input(&self.aad_len.to_le_bytes()); - self.mac.input(&(self.data_len as u64).to_le_bytes()); - - let mut calc_tag = [0u8; 16]; - self.mac.raw_result(&mut calc_tag); - if fixed_time_eq(&calc_tag, tag) { - true - } else { - false - } - } - } -} -#[cfg(not(fuzzing))] -pub use self::real_chachapoly::ChaCha20Poly1305RFC; - -/// Enables simultaneously reading and decrypting a ChaCha20Poly1305RFC stream from a std::io::Read. -struct ChaChaPolyReader<'a, R: Read> { - pub chacha: &'a mut ChaCha20Poly1305RFC, - pub read: R, -} - -impl<'a, R: Read> Read for ChaChaPolyReader<'a, R> { - // Decrypt bytes from Self::read into `dest`. - // `ChaCha20Poly1305RFC::finish_and_check_tag` must be called to check the tag after all reads - // complete. - fn read(&mut self, dest: &mut [u8]) -> Result { - let res = self.read.read(dest)?; - if res > 0 { - self.chacha.decrypt_in_place(&mut dest[0..res]); - } - Ok(res) - } -} - -/// Enables simultaneously writing and encrypting a byte stream into a Writer. -struct ChaChaPolyWriter<'a, W: Writer> { - pub chacha: &'a mut ChaCha20Poly1305RFC, - pub write: &'a mut W, -} - -impl<'a, W: Writer> Writer for ChaChaPolyWriter<'a, W> { - // Encrypt then write bytes from `src` into Self::write. - // `ChaCha20Poly1305RFC::finish_and_get_tag` can be called to retrieve the tag after all writes - // complete. - fn write_all(&mut self, src: &[u8]) -> Result<(), io::Error> { - let mut src_idx = 0; - while src_idx < src.len() { - let mut write_buffer = [0; 8192]; - let bytes_written = (&mut write_buffer[..]).write(&src[src_idx..]).expect("In-memory writes can't fail"); - self.chacha.encrypt_in_place(&mut write_buffer[..bytes_written]); - self.write.write_all(&write_buffer[..bytes_written])?; - src_idx += bytes_written; - } - Ok(()) - } -} - -/// Enables the use of the serialization macros for objects that need to be simultaneously encrypted and -/// serialized. This allows us to avoid an intermediate Vec allocation. -pub(crate) struct ChaChaPolyWriteAdapter<'a, W: Writeable> { - pub rho: [u8; 32], - pub writeable: &'a W, -} - -impl<'a, W: Writeable> ChaChaPolyWriteAdapter<'a, W> { - #[allow(unused)] // This will be used for onion messages soon - pub fn new(rho: [u8; 32], writeable: &'a W) -> ChaChaPolyWriteAdapter<'a, W> { - Self { rho, writeable } - } -} - -impl<'a, T: Writeable> Writeable for ChaChaPolyWriteAdapter<'a, T> { - // Simultaneously write and encrypt Self::writeable. - fn write(&self, w: &mut W) -> Result<(), io::Error> { - let mut chacha = ChaCha20Poly1305RFC::new(&self.rho, &[0; 12], &[]); - let mut chacha_stream = ChaChaPolyWriter { chacha: &mut chacha, write: w }; - self.writeable.write(&mut chacha_stream)?; - let mut tag = [0 as u8; 16]; - chacha.finish_and_get_tag(&mut tag); - tag.write(w)?; - - Ok(()) - } -} - -/// Enables the use of the serialization macros for objects that need to be simultaneously decrypted and -/// deserialized. This allows us to avoid an intermediate Vec allocation. -pub(crate) struct ChaChaPolyReadAdapter { - pub readable: R, -} - -impl LengthReadableArgs<[u8; 32]> for ChaChaPolyReadAdapter { - // Simultaneously read and decrypt an object from a LengthRead, storing it in Self::readable. - // LengthRead must be used instead of std::io::Read because we need the total length to separate - // out the tag at the end. - fn read(mut r: &mut R, secret: [u8; 32]) -> Result { - if r.total_bytes() < 16 { return Err(DecodeError::InvalidValue) } - - let mut chacha = ChaCha20Poly1305RFC::new(&secret, &[0; 12], &[]); - let decrypted_len = r.total_bytes() - 16; - let s = FixedLengthReader::new(&mut r, decrypted_len); - let mut chacha_stream = ChaChaPolyReader { chacha: &mut chacha, read: s }; - let readable: T = Readable::read(&mut chacha_stream)?; - chacha_stream.read.eat_remaining()?; - - let mut tag = [0 as u8; 16]; - r.read_exact(&mut tag)?; - if !chacha.finish_and_check_tag(&tag) { - return Err(DecodeError::InvalidValue) - } - - Ok(Self { readable }) - } -} - -#[cfg(fuzzing)] -mod fuzzy_chachapoly { - #[derive(Clone, Copy)] - pub struct ChaCha20Poly1305RFC { - tag: [u8; 16], - finished: bool, - } - impl ChaCha20Poly1305RFC { - pub fn new(key: &[u8], nonce: &[u8], _aad: &[u8]) -> ChaCha20Poly1305RFC { - assert!(key.len() == 16 || key.len() == 32); - assert!(nonce.len() == 12); - - // Ehh, I'm too lazy to *also* tweak ChaCha20 to make it RFC-compliant - assert!(nonce[0] == 0 && nonce[1] == 0 && nonce[2] == 0 && nonce[3] == 0); - - let mut tag = [0; 16]; - tag.copy_from_slice(&key[0..16]); - - ChaCha20Poly1305RFC { - tag, - finished: false, - } - } - - pub fn encrypt(&mut self, input: &[u8], output: &mut [u8], out_tag: &mut [u8]) { - assert!(input.len() == output.len()); - assert!(self.finished == false); - - output.copy_from_slice(&input); - out_tag.copy_from_slice(&self.tag); - self.finished = true; - } - - pub fn encrypt_full_message_in_place(&mut self, input_output: &mut [u8], out_tag: &mut [u8]) { - self.encrypt_in_place(input_output); - self.finish_and_get_tag(out_tag); - } - - pub(super) fn encrypt_in_place(&mut self, _input_output: &mut [u8]) { - assert!(self.finished == false); - } - - pub(super) fn finish_and_get_tag(&mut self, out_tag: &mut [u8]) { - assert!(self.finished == false); - out_tag.copy_from_slice(&self.tag); - self.finished = true; - } - - pub fn decrypt(&mut self, input: &[u8], output: &mut [u8], tag: &[u8]) -> bool { - assert!(input.len() == output.len()); - assert!(self.finished == false); - - if tag[..] != self.tag[..] { return false; } - output.copy_from_slice(input); - self.finished = true; - true - } - - pub fn check_decrypt_in_place(&mut self, input_output: &mut [u8], tag: &[u8]) -> Result<(), ()> { - self.decrypt_in_place(input_output); - if self.finish_and_check_tag(tag) { Ok(()) } else { Err(()) } - } - - pub(super) fn decrypt_in_place(&mut self, _input: &mut [u8]) { - assert!(self.finished == false); - } - - pub(super) fn finish_and_check_tag(&mut self, tag: &[u8]) -> bool { - if tag[..] != self.tag[..] { return false; } - self.finished = true; - true - } - } -} -#[cfg(fuzzing)] -pub use self::fuzzy_chachapoly::ChaCha20Poly1305RFC; - -#[cfg(test)] -mod tests { - use crate::ln::msgs::DecodeError; - use super::{ChaChaPolyReadAdapter, ChaChaPolyWriteAdapter}; - use crate::util::ser::{self, FixedLengthReader, LengthReadableArgs, Writeable}; - - // Used for for testing various lengths of serialization. - #[derive(Debug, PartialEq, Eq)] - struct TestWriteable { - field1: Vec, - field2: Vec, - field3: Vec, - } - impl_writeable_tlv_based!(TestWriteable, { - (1, field1, required_vec), - (2, field2, required_vec), - (3, field3, required_vec), - }); - - #[test] - fn test_chacha_stream_adapters() { - // Check that ChaChaPolyReadAdapter and ChaChaPolyWriteAdapter correctly encode and decode an - // encrypted object. - macro_rules! check_object_read_write { - ($obj: expr) => { - // First, serialize the object, encrypted with ChaCha20Poly1305. - let rho = [42; 32]; - let writeable_len = $obj.serialized_length() as u64 + 16; - let write_adapter = ChaChaPolyWriteAdapter::new(rho, &$obj); - let encrypted_writeable_bytes = write_adapter.encode(); - let encrypted_writeable = &encrypted_writeable_bytes[..]; - - // Now deserialize the object back and make sure it matches the original. - let mut rd = FixedLengthReader::new(encrypted_writeable, writeable_len); - let read_adapter = >::read(&mut rd, rho).unwrap(); - assert_eq!($obj, read_adapter.readable); - }; - } - - // Try a big object that will require multiple write buffers. - let big_writeable = TestWriteable { - field1: vec![43], - field2: vec![44; 4192], - field3: vec![45; 4192 + 1], - }; - check_object_read_write!(big_writeable); - - // Try a small object that fits into one write buffer. - let small_writeable = TestWriteable { - field1: vec![43], - field2: vec![44], - field3: vec![45], - }; - check_object_read_write!(small_writeable); - } - - fn do_chacha_stream_adapters_ser_macros() -> Result<(), DecodeError> { - let writeable = TestWriteable { - field1: vec![43], - field2: vec![44; 4192], - field3: vec![45; 4192 + 1], - }; - - // First, serialize the object into a TLV stream, encrypted with ChaCha20Poly1305. - let rho = [42; 32]; - let write_adapter = ChaChaPolyWriteAdapter::new(rho, &writeable); - let mut writer = ser::VecWriter(Vec::new()); - encode_tlv_stream!(&mut writer, { - (1, write_adapter, required), - }); - - // Now deserialize the object back and make sure it matches the original. - let mut read_adapter: Option> = None; - decode_tlv_stream!(&writer.0[..], { - (1, read_adapter, (option: LengthReadableArgs, rho)), - }); - assert_eq!(writeable, read_adapter.unwrap().readable); - - Ok(()) - } - - #[test] - fn chacha_stream_adapters_ser_macros() { - // Test that our stream adapters work as expected with the TLV macros. - // This also serves to test the `option: $trait` variant of the `_decode_tlv` ser macro. - do_chacha_stream_adapters_ser_macros().unwrap() - } -} diff --git a/lightning/src/util/config.rs b/lightning/src/util/config.rs index 7922abf331e..bc536370bb3 100644 --- a/lightning/src/util/config.rs +++ b/lightning/src/util/config.rs @@ -13,6 +13,9 @@ use crate::ln::channel::MAX_FUNDING_SATOSHIS_NO_WUMBO; use crate::ln::channelmanager::{BREAKDOWN_TIMEOUT, MAX_LOCAL_BREAKDOWN_TIMEOUT}; +#[cfg(fuzzing)] +use crate::util::ser::Readable; + /// Configuration we set when applicable. /// /// Default::default() provides sane defaults. @@ -210,6 +213,27 @@ impl Default for ChannelHandshakeConfig { } } +// When fuzzing, we want to allow the fuzzer to pick any configuration parameters. Thus, we +// implement Readable here in a naive way (which is a bit easier for the fuzzer to handle). We +// don't really want to ever expose this to users (if we did we'd want to use TLVs). +#[cfg(fuzzing)] +impl Readable for ChannelHandshakeConfig { + fn read(reader: &mut R) -> Result { + Ok(Self { + minimum_depth: Readable::read(reader)?, + our_to_self_delay: Readable::read(reader)?, + our_htlc_minimum_msat: Readable::read(reader)?, + max_inbound_htlc_value_in_flight_percent_of_channel: Readable::read(reader)?, + negotiate_scid_privacy: Readable::read(reader)?, + announced_channel: Readable::read(reader)?, + commit_upfront_shutdown_pubkey: Readable::read(reader)?, + their_channel_reserve_proportional_millionths: Readable::read(reader)?, + negotiate_anchors_zero_fee_htlc_tx: Readable::read(reader)?, + our_max_accepted_htlcs: Readable::read(reader)?, + }) + } +} + /// Optional channel limits which are applied during channel creation. /// /// These limits are only applied to our counterparty's limits, not our own. @@ -315,6 +339,27 @@ impl Default for ChannelHandshakeLimits { } } +// When fuzzing, we want to allow the fuzzer to pick any configuration parameters. Thus, we +// implement Readable here in a naive way (which is a bit easier for the fuzzer to handle). We +// don't really want to ever expose this to users (if we did we'd want to use TLVs). +#[cfg(fuzzing)] +impl Readable for ChannelHandshakeLimits { + fn read(reader: &mut R) -> Result { + Ok(Self { + min_funding_satoshis: Readable::read(reader)?, + max_funding_satoshis: Readable::read(reader)?, + max_htlc_minimum_msat: Readable::read(reader)?, + min_max_htlc_value_in_flight_msat: Readable::read(reader)?, + max_channel_reserve_satoshis: Readable::read(reader)?, + min_max_accepted_htlcs: Readable::read(reader)?, + trust_own_funding_0conf: Readable::read(reader)?, + max_minimum_depth: Readable::read(reader)?, + force_announced_channel_preference: Readable::read(reader)?, + their_to_self_delay: Readable::read(reader)?, + }) + } +} + /// 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)] @@ -737,21 +782,23 @@ pub struct UserConfig { /// [`msgs::AcceptChannel`] message will not be sent back to the counterparty node unless the /// user explicitly chooses to accept the request. /// - /// To be able to contribute to inbound dual-funded channels, this field must be set to true. - /// In that case the analogous [`Event::OpenChannelV2Request`] will be triggered once a request - /// to open a new dual-funded channel is received through a [`msgs::OpenChannelV2`] message. - /// A corresponding [`msgs::AcceptChannelV2`] message will not be sent back to the counterparty - /// node until the user explicitly chooses to accept the request, optionally contributing funds - /// to it. + // TODO(dual_funding): Make these part of doc comments when #[cfg(dual_funding)] is dropped. + // To be able to contribute to inbound dual-funded channels, this field must be set to true. + // In that case the analogous [`Event::OpenChannelV2Request`] will be triggered once a request + // to open a new dual-funded channel is received through a [`msgs::OpenChannelV2`] message. + // A corresponding [`msgs::AcceptChannelV2`] message will not be sent back to the counterparty + // node until the user explicitly chooses to accept the request, optionally contributing funds + // to it. /// /// Default value: false. /// /// [`Event::OpenChannelRequest`]: crate::events::Event::OpenChannelRequest /// [`msgs::OpenChannel`]: crate::ln::msgs::OpenChannel /// [`msgs::AcceptChannel`]: crate::ln::msgs::AcceptChannel - /// [`Event::OpenChannelV2Request`]: crate::events::Event::OpenChannelV2Request - /// [`msgs::OpenChannelV2`]: crate::ln::msgs::OpenChannelV2 - /// [`msgs::AcceptChannelV2`]: crate::ln::msgs::AcceptChannelV2 + // TODO(dual_funding): Make these part of doc comments when #[cfg(dual_funding)] is dropped. + // [`Event::OpenChannelV2Request`]: crate::events::Event::OpenChannelV2Request + // [`msgs::OpenChannelV2`]: crate::ln::msgs::OpenChannelV2 + // [`msgs::AcceptChannelV2`]: crate::ln::msgs::AcceptChannelV2 pub manually_accept_inbound_channels: bool, /// If this is set to true, LDK will intercept HTLCs that are attempting to be forwarded over /// fake short channel ids generated via [`ChannelManager::get_intercept_scid`]. Upon HTLC @@ -791,3 +838,22 @@ impl Default for UserConfig { } } } + +// When fuzzing, we want to allow the fuzzer to pick any configuration parameters. Thus, we +// implement Readable here in a naive way (which is a bit easier for the fuzzer to handle). We +// don't really want to ever expose this to users (if we did we'd want to use TLVs). +#[cfg(fuzzing)] +impl Readable for UserConfig { + fn read(reader: &mut R) -> Result { + Ok(Self { + channel_handshake_config: Readable::read(reader)?, + channel_handshake_limits: Readable::read(reader)?, + channel_config: Readable::read(reader)?, + accept_forwards_to_priv_channels: Readable::read(reader)?, + accept_inbound_channels: Readable::read(reader)?, + manually_accept_inbound_channels: Readable::read(reader)?, + accept_intercept_htlcs: Readable::read(reader)?, + accept_mpp_keysend: Readable::read(reader)?, + }) + } +} diff --git a/lightning/src/util/hash_tables.rs b/lightning/src/util/hash_tables.rs new file mode 100644 index 00000000000..6c3d1ec42cb --- /dev/null +++ b/lightning/src/util/hash_tables.rs @@ -0,0 +1,174 @@ +//! Generally LDK uses `std`'s `HashMap`s, however when building for no-std, LDK uses `hashbrown`'s +//! `HashMap`s with the `std` `SipHasher` and uses `getrandom` to opportunistically randomize it, +//! if randomization is available. +//! +//! This module simply re-exports the `HashMap` used in LDK for public consumption. + +#[cfg(feature = "hashbrown")] +extern crate hashbrown; +#[cfg(feature = "possiblyrandom")] +extern crate possiblyrandom; + +// For no-std builds, we need to use hashbrown, however, by default, it doesn't randomize the +// hashing and is vulnerable to HashDoS attacks. Thus, we use the core SipHasher when not using +// std, but use `getrandom` to randomize it if its available. + +#[cfg(not(feature = "hashbrown"))] +mod std_hashtables { + pub use std::collections::hash_map::RandomState; + pub use std::collections::HashMap; + + pub(crate) use std::collections::{hash_map, HashSet}; + + pub(crate) type OccupiedHashMapEntry<'a, K, V> = + std::collections::hash_map::OccupiedEntry<'a, K, V>; + pub(crate) type VacantHashMapEntry<'a, K, V> = + std::collections::hash_map::VacantEntry<'a, K, V>; + + /// Builds a new [`HashMap`]. + pub fn new_hash_map() -> HashMap { + HashMap::new() + } + /// Builds a new [`HashMap`] with the given capacity. + pub fn hash_map_with_capacity(cap: usize) -> HashMap { + HashMap::with_capacity(cap) + } + pub(crate) fn hash_map_from_iter< + K: core::hash::Hash + Eq, + V, + I: IntoIterator, + >( + iter: I, + ) -> HashMap { + HashMap::from_iter(iter) + } + + pub(crate) fn new_hash_set() -> HashSet { + HashSet::new() + } + pub(crate) fn hash_set_with_capacity(cap: usize) -> HashSet { + HashSet::with_capacity(cap) + } + pub(crate) fn hash_set_from_iter>( + iter: I, + ) -> HashSet { + HashSet::from_iter(iter) + } +} +#[cfg(not(feature = "hashbrown"))] +pub use std_hashtables::*; + +#[cfg(feature = "hashbrown")] +pub(crate) use self::hashbrown::hash_map; + +#[cfg(feature = "hashbrown")] +mod hashbrown_tables { + #[cfg(feature = "std")] + mod hasher { + pub use std::collections::hash_map::RandomState; + } + #[cfg(not(feature = "std"))] + mod hasher { + #![allow(deprecated)] // hash::SipHasher was deprecated in favor of something only in std. + use core::hash::{BuildHasher, SipHasher}; + + #[derive(Clone, Copy)] + /// A simple implementation of [`BuildHasher`] that uses `getrandom` to opportunistically + /// randomize, if the platform supports it. + pub struct RandomState { + k0: u64, + k1: u64, + } + + impl RandomState { + /// Constructs a new [`RandomState`] which may or may not be random, depending on the + /// target platform. + pub fn new() -> RandomState { + let (k0, k1); + #[cfg(all(not(fuzzing), feature = "possiblyrandom"))] + { + let mut keys = [0; 16]; + possiblyrandom::getpossiblyrandom(&mut keys); + + let mut k0_bytes = [0; 8]; + let mut k1_bytes = [0; 8]; + k0_bytes.copy_from_slice(&keys[..8]); + k1_bytes.copy_from_slice(&keys[8..]); + k0 = u64::from_le_bytes(k0_bytes); + k1 = u64::from_le_bytes(k1_bytes); + } + #[cfg(any(fuzzing, not(feature = "possiblyrandom")))] + { + k0 = 0; + k1 = 0; + } + RandomState { k0, k1 } + } + } + + impl Default for RandomState { + fn default() -> RandomState { + RandomState::new() + } + } + + impl BuildHasher for RandomState { + type Hasher = SipHasher; + fn build_hasher(&self) -> SipHasher { + SipHasher::new_with_keys(self.k0, self.k1) + } + } + } + + use super::*; + pub use hasher::*; + + /// The HashMap type used in LDK. + pub type HashMap = hashbrown::HashMap; + pub(crate) type HashSet = hashbrown::HashSet; + + pub(crate) type OccupiedHashMapEntry<'a, K, V> = + hashbrown::hash_map::OccupiedEntry<'a, K, V, RandomState>; + pub(crate) type VacantHashMapEntry<'a, K, V> = + hashbrown::hash_map::VacantEntry<'a, K, V, RandomState>; + + /// Builds a new [`HashMap`]. + pub fn new_hash_map() -> HashMap { + HashMap::with_hasher(RandomState::new()) + } + /// Builds a new [`HashMap`] with the given capacity. + pub fn hash_map_with_capacity(cap: usize) -> HashMap { + HashMap::with_capacity_and_hasher(cap, RandomState::new()) + } + pub(crate) fn hash_map_from_iter< + K: core::hash::Hash + Eq, + V, + I: IntoIterator, + >( + iter: I, + ) -> HashMap { + let iter = iter.into_iter(); + let min_size = iter.size_hint().0; + let mut res = HashMap::with_capacity_and_hasher(min_size, RandomState::new()); + res.extend(iter); + res + } + + pub(crate) fn new_hash_set() -> HashSet { + HashSet::with_hasher(RandomState::new()) + } + pub(crate) fn hash_set_with_capacity(cap: usize) -> HashSet { + HashSet::with_capacity_and_hasher(cap, RandomState::new()) + } + pub(crate) fn hash_set_from_iter>( + iter: I, + ) -> HashSet { + let iter = iter.into_iter(); + let min_size = iter.size_hint().0; + let mut res = HashSet::with_capacity_and_hasher(min_size, RandomState::new()); + res.extend(iter); + res + } +} +#[cfg(feature = "hashbrown")] +pub use hashbrown_tables::*; diff --git a/lightning/src/util/indexed_map.rs b/lightning/src/util/indexed_map.rs index 39565f048c0..d4c20f72215 100644 --- a/lightning/src/util/indexed_map.rs +++ b/lightning/src/util/indexed_map.rs @@ -1,6 +1,6 @@ //! This module has a map which can be iterated in a deterministic order. See the [`IndexedMap`]. -use crate::prelude::{HashMap, hash_map}; +use crate::prelude::*; use alloc::vec::Vec; use alloc::slice::Iter; use core::hash::Hash; @@ -34,7 +34,7 @@ impl IndexedMap { /// Constructs a new, empty map pub fn new() -> Self { Self { - map: HashMap::new(), + map: new_hash_map(), keys: Vec::new(), } } @@ -42,7 +42,7 @@ impl IndexedMap { /// Constructs a new, empty map with the given capacity pre-allocated pub fn with_capacity(capacity: usize) -> Self { Self { - map: HashMap::with_capacity(capacity), + map: hash_map_with_capacity(capacity), keys: Vec::with_capacity(capacity), } } @@ -176,10 +176,7 @@ impl<'a, K: Hash + Ord, V: 'a> Iterator for Range<'a, K, V> { /// /// This is not exported to bindings users as bindings provide alternate accessors rather than exposing maps directly. pub struct VacantEntry<'a, K: Hash + Ord, V> { - #[cfg(feature = "hashbrown")] - underlying_entry: hash_map::VacantEntry<'a, K, V, hash_map::DefaultHashBuilder>, - #[cfg(not(feature = "hashbrown"))] - underlying_entry: hash_map::VacantEntry<'a, K, V>, + underlying_entry: VacantHashMapEntry<'a, K, V>, key: K, keys: &'a mut Vec, } @@ -188,10 +185,7 @@ pub struct VacantEntry<'a, K: Hash + Ord, V> { /// /// This is not exported to bindings users as bindings provide alternate accessors rather than exposing maps directly. pub struct OccupiedEntry<'a, K: Hash + Ord, V> { - #[cfg(feature = "hashbrown")] - underlying_entry: hash_map::OccupiedEntry<'a, K, V, hash_map::DefaultHashBuilder>, - #[cfg(not(feature = "hashbrown"))] - underlying_entry: hash_map::OccupiedEntry<'a, K, V>, + underlying_entry: OccupiedHashMapEntry<'a, K, V>, keys: &'a mut Vec, } diff --git a/lightning/src/util/logger.rs b/lightning/src/util/logger.rs index 8ca5333f63d..e48cefaa044 100644 --- a/lightning/src/util/logger.rs +++ b/lightning/src/util/logger.rs @@ -91,10 +91,12 @@ impl Level { } } +macro_rules! impl_record { + ($($args: lifetime)?, $($nonstruct_args: lifetime)?) => { /// A Record, unit of logging output with Metadata to enable filtering /// Module_path, file, line to inform on log's source #[derive(Clone, Debug)] -pub struct Record<'a> { +pub struct Record<$($args)?> { /// The verbosity level of the message. pub level: Level, /// The node id of the peer pertaining to the logged record. @@ -118,22 +120,17 @@ pub struct Record<'a> { pub file: &'static str, /// The line containing the message. pub line: u32, - - #[cfg(c_bindings)] - /// We don't actually use the lifetime parameter in C bindings (as there is no good way to - /// communicate a lifetime to a C, or worse, Java user). - _phantom: core::marker::PhantomData<&'a ()>, } -impl<'a> Record<'a> { +impl<$($args)?> Record<$($args)?> { /// Returns a new Record. /// /// This is not exported to bindings users as fmt can't be used in C #[inline] - pub fn new( + pub fn new<$($nonstruct_args)?>( level: Level, peer_id: Option, channel_id: Option, args: fmt::Arguments<'a>, module_path: &'static str, file: &'static str, line: u32 - ) -> Record<'a> { + ) -> Record<$($args)?> { Record { level, peer_id, @@ -145,11 +142,14 @@ impl<'a> Record<'a> { module_path, file, line, - #[cfg(c_bindings)] - _phantom: core::marker::PhantomData, } } } +} } +#[cfg(not(c_bindings))] +impl_record!('a, ); +#[cfg(c_bindings)] +impl_record!(, 'a); /// A trait encapsulating the operations required of a logger. pub trait Logger { @@ -158,6 +158,9 @@ pub trait Logger { } /// Adds relevant context to a [`Record`] before passing it to the wrapped [`Logger`]. +/// +/// This is not exported to bindings users as lifetimes are problematic and there's little reason +/// for this to be used downstream anyway. pub struct WithContext<'a, L: Deref> where L::Target: Logger { /// The logger to delegate to after adding context to the record. logger: &'a L, @@ -254,11 +257,11 @@ mod tests { } struct WrapperLog { - logger: Arc + logger: Arc } impl WrapperLog { - fn new(logger: Arc) -> WrapperLog { + fn new(logger: Arc) -> WrapperLog { WrapperLog { logger, } @@ -278,7 +281,7 @@ mod tests { fn test_logging_macros() { let mut logger = TestLogger::new(); logger.enable(Level::Gossip); - let logger : Arc = Arc::new(logger); + let logger : Arc = Arc::new(logger); let wrapper = WrapperLog::new(Arc::clone(&logger)); wrapper.call_macros(); } diff --git a/lightning/src/util/macro_logger.rs b/lightning/src/util/macro_logger.rs index 203c544e009..f962251cd65 100644 --- a/lightning/src/util/macro_logger.rs +++ b/lightning/src/util/macro_logger.rs @@ -7,10 +7,9 @@ // You may not use this file except in accordance with one or both of these // licenses. -use crate::chain::transaction::OutPoint; +use crate::ln::ChannelId; use crate::sign::SpendableOutputDescriptor; -use bitcoin::hash_types::Txid; use bitcoin::blockdata::transaction::Transaction; use crate::routing::router::Route; @@ -38,27 +37,17 @@ macro_rules! log_bytes { } } -pub(crate) struct DebugFundingChannelId<'a>(pub &'a Txid, pub u16); -impl<'a> core::fmt::Display for DebugFundingChannelId<'a> { +pub(crate) struct DebugFundingInfo<'a>(pub &'a ChannelId); +impl<'a> core::fmt::Display for DebugFundingInfo<'a> { fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> { - (OutPoint { txid: self.0.clone(), index: self.1 }).to_channel_id().fmt(f) - } -} -macro_rules! log_funding_channel_id { - ($funding_txid: expr, $funding_txo: expr) => { - $crate::util::macro_logger::DebugFundingChannelId(&$funding_txid, $funding_txo) - } -} - -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> { - (self.0).0.to_channel_id().fmt(f) + self.0.fmt(f) } } macro_rules! log_funding_info { ($key_storage: expr) => { - $crate::util::macro_logger::DebugFundingInfo(&$key_storage.get_funding_txo()) + $crate::util::macro_logger::DebugFundingInfo( + &$key_storage.channel_id() + ) } } diff --git a/lightning/src/util/mod.rs b/lightning/src/util/mod.rs index e86885a83db..31bdf1ca53c 100644 --- a/lightning/src/util/mod.rs +++ b/lightning/src/util/mod.rs @@ -20,6 +20,7 @@ pub mod ser; pub mod message_signing; pub mod invoice; pub mod persist; +pub mod scid_utils; pub mod string; pub mod wakers; #[cfg(fuzzing)] @@ -29,13 +30,9 @@ pub(crate) mod base32; pub(crate) mod atomic_counter; pub(crate) mod byte_utils; -pub(crate) mod chacha20; -#[cfg(not(fuzzing))] -pub(crate) mod poly1305; -pub(crate) mod chacha20poly1305rfc; pub(crate) mod transaction_utils; -pub(crate) mod scid_utils; pub(crate) mod time; +pub mod hash_tables; pub mod indexed_map; @@ -43,9 +40,6 @@ pub mod indexed_map; #[macro_use] pub(crate) mod macro_logger; -/// Cryptography utilities. -pub(crate) mod crypto; - // These have to come after macro_logger to build pub mod logger; pub mod config; diff --git a/lightning/src/util/persist.rs b/lightning/src/util/persist.rs index a9f534ee4d3..2f418a8efc2 100644 --- a/lightning/src/util/persist.rs +++ b/lightning/src/util/persist.rs @@ -187,6 +187,41 @@ impl<'a, A: KVStore, M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Der } } +impl<'a, 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 dyn KVStore + Send + Sync + where M::Target: 'static + chain::Watch<::EcdsaSigner>, + T::Target: 'static + BroadcasterInterface, + ES::Target: 'static + EntropySource, + NS::Target: 'static + NodeSigner, + SP::Target: 'static + SignerProvider, + F::Target: 'static + FeeEstimator, + R::Target: 'static + Router, + L::Target: 'static + Logger, +{ + /// Persist the given [`ChannelManager`] to disk, returning an error if persistence failed. + fn persist_manager(&self, channel_manager: &ChannelManager) -> Result<(), io::Error> { + self.write(CHANNEL_MANAGER_PERSISTENCE_PRIMARY_NAMESPACE, + CHANNEL_MANAGER_PERSISTENCE_SECONDARY_NAMESPACE, + CHANNEL_MANAGER_PERSISTENCE_KEY, + &channel_manager.encode()) + } + + /// Persist the given [`NetworkGraph`] to disk, returning an error if persistence failed. + fn persist_graph(&self, network_graph: &NetworkGraph) -> Result<(), io::Error> { + self.write(NETWORK_GRAPH_PERSISTENCE_PRIMARY_NAMESPACE, + NETWORK_GRAPH_PERSISTENCE_SECONDARY_NAMESPACE, + NETWORK_GRAPH_PERSISTENCE_KEY, + &network_graph.encode()) + } + + /// Persist the given [`WriteableScore`] to disk, returning an error if persistence failed. + fn persist_scorer(&self, scorer: &S) -> Result<(), io::Error> { + self.write(SCORER_PERSISTENCE_PRIMARY_NAMESPACE, + SCORER_PERSISTENCE_SECONDARY_NAMESPACE, + SCORER_PERSISTENCE_KEY, + &scorer.encode()) + } +} + impl Persist 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. @@ -218,6 +253,37 @@ impl Persist Persist for dyn KVStore + Send + Sync { + // 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. + // 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, _update_id: MonitorUpdateId) -> chain::ChannelMonitorUpdateStatus { + let key = format!("{}_{}", funding_txo.txid.to_string(), funding_txo.index); + match self.write( + CHANNEL_MONITOR_PERSISTENCE_PRIMARY_NAMESPACE, + CHANNEL_MONITOR_PERSISTENCE_SECONDARY_NAMESPACE, + &key, &monitor.encode()) + { + Ok(()) => chain::ChannelMonitorUpdateStatus::Completed, + Err(_) => chain::ChannelMonitorUpdateStatus::UnrecoverableError + } + } + + fn update_persisted_channel(&self, funding_txo: OutPoint, _update: Option<&ChannelMonitorUpdate>, monitor: &ChannelMonitor, _update_id: MonitorUpdateId) -> chain::ChannelMonitorUpdateStatus { + let key = format!("{}_{}", funding_txo.txid.to_string(), funding_txo.index); + match self.write( + CHANNEL_MONITOR_PERSISTENCE_PRIMARY_NAMESPACE, + CHANNEL_MONITOR_PERSISTENCE_SECONDARY_NAMESPACE, + &key, &monitor.encode()) + { + Ok(()) => chain::ChannelMonitorUpdateStatus::Completed, + Err(_) => chain::ChannelMonitorUpdateStatus::UnrecoverableError + } + } +} + /// Read previously persisted [`ChannelMonitor`]s from the store. pub fn read_channel_monitors( kv_store: K, entropy_source: ES, signer_provider: SP, @@ -346,9 +412,10 @@ where /// /// # 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 +/// Stale updates are pruned when the consolidation threshold is reached according to `maximum_pending_updates`. +/// Monitor updates in the range between the latest `update_id` and `update_id - maximum_pending_updates` +/// 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`. /// @@ -610,24 +677,6 @@ where ) -> 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(), @@ -641,65 +690,12 @@ where &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_PRIMARY_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: {}", + "Failed to write ChannelMonitor {}/{}/{} reason: {}", CHANNEL_MONITOR_PERSISTENCE_PRIMARY_NAMESPACE, CHANNEL_MONITOR_PERSISTENCE_SECONDARY_NAMESPACE, monitor_name.as_str(), @@ -741,7 +737,7 @@ where Err(e) => { log_error!( self.logger, - "error writing channel monitor update {}/{}/{} reason: {}", + "Failed to write ChannelMonitorUpdate {}/{}/{} reason: {}", CHANNEL_MONITOR_UPDATE_PERSISTENCE_PRIMARY_NAMESPACE, monitor_name.as_str(), update_name.as_str(), @@ -751,8 +747,41 @@ where } } } 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) + let monitor_name = MonitorName::from(funding_txo); + // In case of channel-close monitor update, we need to read old monitor before persisting + // the new one in order to determine the cleanup range. + let maybe_old_monitor = match monitor.get_latest_update_id() { + CLOSED_CHANNEL_UPDATE_ID => self.read_monitor(&monitor_name).ok(), + _ => None + }; + + // We could write this update, but it meets criteria of our design that calls for a full monitor write. + let monitor_update_status = self.persist_new_channel(funding_txo, monitor, monitor_update_call_id); + + if let chain::ChannelMonitorUpdateStatus::Completed = monitor_update_status { + let cleanup_range = if monitor.get_latest_update_id() == CLOSED_CHANNEL_UPDATE_ID { + // If there is an error while reading old monitor, we skip clean up. + maybe_old_monitor.map(|(_, ref old_monitor)| { + let start = old_monitor.get_latest_update_id(); + // We never persist an update with update_id = CLOSED_CHANNEL_UPDATE_ID + let end = cmp::min( + start.saturating_add(self.maximum_pending_updates), + CLOSED_CHANNEL_UPDATE_ID - 1, + ); + (start, end) + }) + } else { + let end = monitor.get_latest_update_id(); + let start = end.saturating_sub(self.maximum_pending_updates); + Some((start, end)) + }; + + if let Some((start, end)) = cleanup_range { + self.cleanup_in_range(monitor_name, start, end); + } + } + + monitor_update_status } } else { // There is no update given, so we must persist a new monitor. @@ -761,6 +790,34 @@ where } } +impl MonitorUpdatingPersister +where + ES::Target: EntropySource + Sized, + K::Target: KVStore, + L::Target: Logger, + SP::Target: SignerProvider + Sized +{ + // Cleans up monitor updates for given monitor in range `start..=end`. + fn cleanup_in_range(&self, monitor_name: MonitorName, start: u64, end: u64) { + for update_id in start..=end { + let update_name = UpdateName::from(update_id); + if let Err(e) = self.kv_store.remove( + CHANNEL_MONITOR_UPDATE_PERSISTENCE_PRIMARY_NAMESPACE, + monitor_name.as_str(), + update_name.as_str(), + true, + ) { + log_error!( + self.logger, + "Failed to clean up channel monitor updates for monitor {}, reason: {}", + monitor_name.as_str(), + e + ); + }; + } + } +} + /// A struct representing a name for a monitor. #[derive(Debug)] struct MonitorName(String); @@ -896,20 +953,21 @@ mod tests { #[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 persister_0_max_pending_updates = 7; + // Intentionally set this to a smaller value to test a different alignment. + let persister_1_max_pending_updates = 3; 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, + maximum_pending_updates: persister_0_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, + maximum_pending_updates: persister_1_max_pending_updates, entropy_source: &chanmon_cfgs[1].keys_manager, signer_provider: &chanmon_cfgs[1].keys_manager, }; @@ -934,7 +992,6 @@ mod tests { 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; @@ -957,10 +1014,11 @@ mod tests { 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 + + // if the CM is at consolidation threshold, 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 { + if mon.get_latest_update_id() % persister_0_max_pending_updates == 0 + || mon.get_latest_update_id() == CLOSED_CHANNEL_UPDATE_ID { assert_eq!( persister_0.kv_store.list(CHANNEL_MONITOR_UPDATE_PERSISTENCE_PRIMARY_NAMESPACE, monitor_name.as_str()).unwrap().len(), @@ -975,8 +1033,9 @@ mod tests { 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 { + // if the CM is at consolidation threshold, ensure no updates are stored. + if mon.get_latest_update_id() % persister_1_max_pending_updates == 0 + || mon.get_latest_update_id() == CLOSED_CHANNEL_UPDATE_ID { assert_eq!( persister_1.kv_store.list(CHANNEL_MONITOR_UPDATE_PERSISTENCE_PRIMARY_NAMESPACE, monitor_name.as_str()).unwrap().len(), @@ -1001,7 +1060,7 @@ mod tests { // 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 { + for i in 3..=persister_0_max_pending_updates * 2 { let receiver; if sender == 0 { sender = 1; @@ -1059,9 +1118,9 @@ mod tests { { 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 update_id = update_map.get(&added_monitors[0].1.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 cmu = &cmu_map.get(&added_monitors[0].1.channel_id()).unwrap()[0]; let test_txo = OutPoint { txid: Txid::from_str("8984484a580b825b9972d7adb15050b3ab624ccd731946b3eeddb92f4e7ef6be").unwrap(), index: 0 }; let ro_persister = MonitorUpdatingPersister { diff --git a/lightning/src/util/scid_utils.rs b/lightning/src/util/scid_utils.rs index fbbcc69a133..38be0eb88fc 100644 --- a/lightning/src/util/scid_utils.rs +++ b/lightning/src/util/scid_utils.rs @@ -7,6 +7,8 @@ // You may not use this file except in accordance with one or both of these // licenses. +//! Utilities for creating and parsing short channel ids. + /// Maximum block height that can be used in a `short_channel_id`. This /// value is based on the 3-bytes available for block height. pub const MAX_SCID_BLOCK: u64 = 0x00ffffff; @@ -22,23 +24,26 @@ pub const MAX_SCID_VOUT_INDEX: u64 = 0xffff; /// A `short_channel_id` construction error #[derive(Debug, PartialEq, Eq)] pub enum ShortChannelIdError { + /// Block height too high BlockOverflow, + /// Tx index too high TxIndexOverflow, + /// Vout index too high VoutIndexOverflow, } /// Extracts the block height (most significant 3-bytes) from the `short_channel_id` -pub fn block_from_scid(short_channel_id: &u64) -> u32 { +pub fn block_from_scid(short_channel_id: u64) -> u32 { return (short_channel_id >> 40) as u32; } /// Extracts the tx index (bytes [2..4]) from the `short_channel_id` -pub fn tx_index_from_scid(short_channel_id: &u64) -> u32 { +pub fn tx_index_from_scid(short_channel_id: u64) -> u32 { return ((short_channel_id >> 16) & MAX_SCID_TX_INDEX) as u32; } /// Extracts the vout (bytes [0..2]) from the `short_channel_id` -pub fn vout_from_scid(short_channel_id: &u64) -> u16 { +pub fn vout_from_scid(short_channel_id: u64) -> u16 { return ((short_channel_id) & MAX_SCID_VOUT_INDEX) as u16; } @@ -69,7 +74,7 @@ pub(crate) mod fake_scid { use bitcoin::blockdata::constants::ChainHash; use bitcoin::network::constants::Network; use crate::sign::EntropySource; - use crate::util::chacha20::ChaCha20; + use crate::crypto::chacha20::ChaCha20; use crate::util::scid_utils; use core::convert::TryInto; @@ -91,8 +96,11 @@ pub(crate) mod fake_scid { /// into the fake scid. #[derive(Copy, Clone)] pub(crate) enum Namespace { + /// Phantom nodes namespace Phantom, + /// SCID aliases for outbound private channels OutboundAlias, + /// Payment interception namespace Intercept } @@ -154,22 +162,22 @@ pub(crate) mod fake_scid { /// Returns whether the given fake scid falls into the phantom namespace. pub fn is_valid_phantom(fake_scid_rand_bytes: &[u8; 32], scid: u64, chain_hash: &ChainHash) -> bool { - let block_height = scid_utils::block_from_scid(&scid); - let tx_index = scid_utils::tx_index_from_scid(&scid); + let block_height = scid_utils::block_from_scid(scid); + let tx_index = scid_utils::tx_index_from_scid(scid); let namespace = Namespace::Phantom; let valid_vout = namespace.get_encrypted_vout(block_height, tx_index, fake_scid_rand_bytes); block_height >= segwit_activation_height(chain_hash) - && valid_vout == scid_utils::vout_from_scid(&scid) as u8 + && valid_vout == scid_utils::vout_from_scid(scid) as u8 } /// Returns whether the given fake scid falls into the intercept namespace. pub fn is_valid_intercept(fake_scid_rand_bytes: &[u8; 32], scid: u64, chain_hash: &ChainHash) -> bool { - let block_height = scid_utils::block_from_scid(&scid); - let tx_index = scid_utils::tx_index_from_scid(&scid); + let block_height = scid_utils::block_from_scid(scid); + let tx_index = scid_utils::tx_index_from_scid(scid); let namespace = Namespace::Intercept; let valid_vout = namespace.get_encrypted_vout(block_height, tx_index, fake_scid_rand_bytes); block_height >= segwit_activation_height(chain_hash) - && valid_vout == scid_utils::vout_from_scid(&scid) as u8 + && valid_vout == scid_utils::vout_from_scid(scid) as u8 } #[cfg(test)] @@ -240,14 +248,14 @@ pub(crate) mod fake_scid { let namespace = Namespace::Phantom; let fake_scid = namespace.get_fake_scid(500_000, &mainnet_genesis, &fake_scid_rand_bytes, &keys_manager); - let fake_height = scid_utils::block_from_scid(&fake_scid); + let fake_height = scid_utils::block_from_scid(fake_scid); assert!(fake_height >= MAINNET_SEGWIT_ACTIVATION_HEIGHT); assert!(fake_height <= 500_000); - let fake_tx_index = scid_utils::tx_index_from_scid(&fake_scid); + let fake_tx_index = scid_utils::tx_index_from_scid(fake_scid); assert!(fake_tx_index <= MAX_TX_INDEX); - let fake_vout = scid_utils::vout_from_scid(&fake_scid); + let fake_vout = scid_utils::vout_from_scid(fake_scid); assert!(fake_vout < MAX_NAMESPACES as u16); } } @@ -259,29 +267,29 @@ mod tests { #[test] fn test_block_from_scid() { - assert_eq!(block_from_scid(&0x000000_000000_0000), 0); - assert_eq!(block_from_scid(&0x000001_000000_0000), 1); - assert_eq!(block_from_scid(&0x000001_ffffff_ffff), 1); - assert_eq!(block_from_scid(&0x800000_ffffff_ffff), 0x800000); - assert_eq!(block_from_scid(&0xffffff_ffffff_ffff), 0xffffff); + assert_eq!(block_from_scid(0x000000_000000_0000), 0); + assert_eq!(block_from_scid(0x000001_000000_0000), 1); + assert_eq!(block_from_scid(0x000001_ffffff_ffff), 1); + assert_eq!(block_from_scid(0x800000_ffffff_ffff), 0x800000); + assert_eq!(block_from_scid(0xffffff_ffffff_ffff), 0xffffff); } #[test] fn test_tx_index_from_scid() { - assert_eq!(tx_index_from_scid(&0x000000_000000_0000), 0); - assert_eq!(tx_index_from_scid(&0x000000_000001_0000), 1); - assert_eq!(tx_index_from_scid(&0xffffff_000001_ffff), 1); - assert_eq!(tx_index_from_scid(&0xffffff_800000_ffff), 0x800000); - assert_eq!(tx_index_from_scid(&0xffffff_ffffff_ffff), 0xffffff); + assert_eq!(tx_index_from_scid(0x000000_000000_0000), 0); + assert_eq!(tx_index_from_scid(0x000000_000001_0000), 1); + assert_eq!(tx_index_from_scid(0xffffff_000001_ffff), 1); + assert_eq!(tx_index_from_scid(0xffffff_800000_ffff), 0x800000); + assert_eq!(tx_index_from_scid(0xffffff_ffffff_ffff), 0xffffff); } #[test] fn test_vout_from_scid() { - assert_eq!(vout_from_scid(&0x000000_000000_0000), 0); - assert_eq!(vout_from_scid(&0x000000_000000_0001), 1); - assert_eq!(vout_from_scid(&0xffffff_ffffff_0001), 1); - assert_eq!(vout_from_scid(&0xffffff_ffffff_8000), 0x8000); - assert_eq!(vout_from_scid(&0xffffff_ffffff_ffff), 0xffff); + assert_eq!(vout_from_scid(0x000000_000000_0000), 0); + assert_eq!(vout_from_scid(0x000000_000000_0001), 1); + assert_eq!(vout_from_scid(0xffffff_ffffff_0001), 1); + assert_eq!(vout_from_scid(0xffffff_ffffff_8000), 0x8000); + assert_eq!(vout_from_scid(0xffffff_ffffff_ffff), 0xffff); } #[test] diff --git a/lightning/src/util/ser.rs b/lightning/src/util/ser.rs index 1589329e7b6..43e3a40e6f3 100644 --- a/lightning/src/util/ser.rs +++ b/lightning/src/util/ser.rs @@ -371,14 +371,14 @@ impl Writeable for BigSize { #[inline] fn write(&self, writer: &mut W) -> Result<(), io::Error> { match self.0 { - 0...0xFC => { + 0..=0xFC => { (self.0 as u8).write(writer) }, - 0xFD...0xFFFF => { + 0xFD..=0xFFFF => { 0xFDu8.write(writer)?; (self.0 as u16).write(writer) }, - 0x10000...0xFFFFFFFF => { + 0x10000..=0xFFFFFFFF => { 0xFEu8.write(writer)?; (self.0 as u32).write(writer) }, @@ -749,7 +749,7 @@ macro_rules! impl_for_map { } impl_for_map!(BTreeMap, Ord, |_| BTreeMap::new()); -impl_for_map!(HashMap, Hash, |len| HashMap::with_capacity(len)); +impl_for_map!(HashMap, Hash, |len| hash_map_with_capacity(len)); // HashSet impl Writeable for HashSet @@ -771,7 +771,7 @@ where T: Readable + Eq + Hash #[inline] fn read(r: &mut R) -> Result { let len: CollectionLength = Readable::read(r)?; - let mut ret = HashSet::with_capacity(cmp::min(len.0 as usize, MAX_BUF_SIZE / core::mem::size_of::())); + let mut ret = hash_set_with_capacity(cmp::min(len.0 as usize, MAX_BUF_SIZE / core::mem::size_of::())); for _ in 0..len.0 { if !ret.insert(T::read(r)?) { return Err(DecodeError::InvalidValue) @@ -847,6 +847,7 @@ impl Readable for Vec { impl_for_vec!(ecdsa::Signature); impl_for_vec!(crate::chain::channelmonitor::ChannelMonitorUpdate); impl_for_vec!(crate::ln::channelmanager::MonitorUpdateCompletionAction); +impl_for_vec!(crate::ln::msgs::SocketAddress); impl_for_vec!((A, B), A, B); impl_writeable_for_vec!(&crate::routing::router::BlindedTail); impl_readable_for_vec!(crate::routing::router::BlindedTail); @@ -1407,6 +1408,11 @@ impl TransactionU16LenLimited { pub fn into_transaction(self) -> Transaction { self.0 } + + /// Returns a reference to the contained `Transaction` + pub fn as_transaction(&self) -> &Transaction { + &self.0 + } } impl Writeable for TransactionU16LenLimited { diff --git a/lightning/src/util/test_channel_signer.rs b/lightning/src/util/test_channel_signer.rs index fdfcdea0a50..81b8f13d18a 100644 --- a/lightning/src/util/test_channel_signer.rs +++ b/lightning/src/util/test_channel_signer.rs @@ -118,7 +118,6 @@ impl TestChannelSigner { /// When `true`, methods are forwarded to the underlying signer as normal. When `false`, some /// methods will return `Err` indicating that the signer is unavailable. Intended to be used for /// testing asynchronous signing. - #[cfg(test)] pub fn set_available(&self, available: bool) { *self.available.lock().unwrap() = available; } @@ -216,10 +215,16 @@ impl EcdsaChannelSigner for TestChannelSigner { } fn sign_justice_revoked_output(&self, justice_tx: &Transaction, input: usize, amount: u64, per_commitment_key: &SecretKey, secp_ctx: &Secp256k1) -> Result { + if !*self.available.lock().unwrap() { + return Err(()); + } Ok(EcdsaChannelSigner::sign_justice_revoked_output(&self.inner, 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) -> Result { + if !*self.available.lock().unwrap() { + return Err(()); + } Ok(EcdsaChannelSigner::sign_justice_revoked_htlc(&self.inner, justice_tx, input, amount, per_commitment_key, htlc, secp_ctx).unwrap()) } @@ -227,6 +232,9 @@ impl EcdsaChannelSigner for TestChannelSigner { &self, htlc_tx: &Transaction, input: usize, htlc_descriptor: &HTLCDescriptor, secp_ctx: &Secp256k1 ) -> Result { + if !*self.available.lock().unwrap() { + return Err(()); + } let state = self.state.lock().unwrap(); if state.last_holder_revoked_commitment - 1 != htlc_descriptor.per_commitment_number && state.last_holder_revoked_commitment - 2 != htlc_descriptor.per_commitment_number @@ -260,6 +268,9 @@ impl EcdsaChannelSigner for TestChannelSigner { } fn sign_counterparty_htlc_transaction(&self, htlc_tx: &Transaction, input: usize, amount: u64, per_commitment_point: &PublicKey, htlc: &HTLCOutputInCommitment, secp_ctx: &Secp256k1) -> Result { + if !*self.available.lock().unwrap() { + return Err(()); + } Ok(EcdsaChannelSigner::sign_counterparty_htlc_transaction(&self.inner, htlc_tx, input, amount, per_commitment_point, htlc, secp_ctx).unwrap()) } @@ -276,6 +287,9 @@ impl EcdsaChannelSigner for TestChannelSigner { // 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); + if !*self.available.lock().unwrap() { + return Err(()); + } EcdsaChannelSigner::sign_holder_anchor_input(&self.inner, anchor_tx, input, secp_ctx) } diff --git a/lightning/src/util/test_utils.rs b/lightning/src/util/test_utils.rs index 3a8dd24ab33..6d558f143a2 100644 --- a/lightning/src/util/test_utils.rs +++ b/lightning/src/util/test_utils.rs @@ -7,6 +7,8 @@ // You may not use this file except in accordance with one or both of these // licenses. +use crate::blinded_path::BlindedPath; +use crate::blinded_path::payment::ReceiveTlvs; use crate::chain; use crate::chain::WatchedOutput; use crate::chain::chaininterface; @@ -17,22 +19,23 @@ use crate::chain::chainmonitor::{MonitorUpdateId, UpdateOrigin}; use crate::chain::channelmonitor; use crate::chain::channelmonitor::MonitorEvent; use crate::chain::transaction::OutPoint; -use crate::routing::router::CandidateRouteHop; +use crate::routing::router::{CandidateRouteHop, FirstHopCandidate, PublicHopCandidate, PrivateHopCandidate}; use crate::sign; use crate::events; use crate::events::bump_transaction::{WalletSource, Utxo}; use crate::ln::ChannelId; -use crate::ln::channelmanager; +use crate::ln::channelmanager::{ChannelDetails, self}; 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::{BlindedPayInfo, UnsignedBolt12Invoice}; use crate::offers::invoice_request::UnsignedInvoiceRequest; +use crate::onion_message::messenger::{DefaultMessageRouter, Destination, MessageRouter, OnionMessagePath}; use crate::routing::gossip::{EffectiveCapacity, NetworkGraph, NodeId, RoutingFees}; use crate::routing::utxo::{UtxoLookup, UtxoLookupError, UtxoResult}; -use crate::routing::router::{find_route, InFlightHtlcs, Path, Route, RouteParameters, RouteHintHop, Router, ScorerAccountingForInFlightHtlcs}; +use crate::routing::router::{DefaultRouter, InFlightHtlcs, Path, Route, RouteParameters, RouteHintHop, Router, ScorerAccountingForInFlightHtlcs}; use crate::routing::scoring::{ChannelUsage, ScoreUpdate, ScoreLookUp}; use crate::sync::RwLock; use crate::util::config::UserConfig; @@ -51,7 +54,7 @@ use bitcoin::network::constants::Network; use bitcoin::hash_types::{BlockHash, Txid}; use bitcoin::sighash::{SighashCache, EcdsaSighashType}; -use bitcoin::secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey}; +use bitcoin::secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey, self}; use bitcoin::secp256k1::ecdh::SharedSecret; use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature}; use bitcoin::secp256k1::schnorr; @@ -67,10 +70,11 @@ use crate::sync::{Mutex, Arc}; use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use core::mem; use bitcoin::bech32::u5; -use crate::sign::{InMemorySigner, Recipient, EntropySource, NodeSigner, SignerProvider}; +use crate::sign::{InMemorySigner, RandomBytes, Recipient, EntropySource, NodeSigner, SignerProvider}; #[cfg(feature = "std")] use std::time::{SystemTime, UNIX_EPOCH}; +use bitcoin::psbt::PartiallySignedTransaction; use bitcoin::Sequence; pub fn pubkey(byte: u8) -> PublicKey { @@ -100,14 +104,32 @@ impl chaininterface::FeeEstimator for TestFeeEstimator { } pub struct TestRouter<'a> { + pub router: DefaultRouter< + Arc>, + &'a TestLogger, + Arc, + &'a RwLock, + (), + TestScorer, + >, + //pub entropy_source: &'a RandomBytes, pub network_graph: Arc>, pub next_routes: Mutex)>>, pub scorer: &'a RwLock, } impl<'a> TestRouter<'a> { - pub fn new(network_graph: Arc>, scorer: &'a RwLock) -> Self { - Self { network_graph, next_routes: Mutex::new(VecDeque::new()), scorer } + pub fn new( + network_graph: Arc>, logger: &'a TestLogger, + scorer: &'a RwLock, + ) -> Self { + let entropy_source = Arc::new(RandomBytes::new([42; 32])); + Self { + router: DefaultRouter::new(network_graph.clone(), logger, entropy_source, scorer, ()), + network_graph, + next_routes: Mutex::new(VecDeque::new()), + scorer, + } } pub fn expect_find_route(&self, query: RouteParameters, result: Result) { @@ -118,10 +140,12 @@ impl<'a> TestRouter<'a> { impl<'a> Router for TestRouter<'a> { fn find_route( - &self, payer: &PublicKey, params: &RouteParameters, first_hops: Option<&[&channelmanager::ChannelDetails]>, + &self, payer: &PublicKey, params: &RouteParameters, first_hops: Option<&[&ChannelDetails]>, inflight_htlcs: InFlightHtlcs ) -> Result { - if let Some((find_route_query, find_route_res)) = self.next_routes.lock().unwrap().pop_front() { + let route_res; + let next_route_opt = self.next_routes.lock().unwrap().pop_front(); + if let Some((find_route_query, find_route_res)) = next_route_opt { assert_eq!(find_route_query, *params); if let Ok(ref route) = find_route_res { assert_eq!(route.route_params, Some(find_route_query)); @@ -142,11 +166,11 @@ impl<'a> Router for TestRouter<'a> { if let Some(first_hops) = first_hops { if let Some(idx) = first_hops.iter().position(|h| h.get_outbound_payment_scid() == Some(hop.short_channel_id)) { let node_id = NodeId::from_pubkey(payer); - let candidate = CandidateRouteHop::FirstHop { + let candidate = CandidateRouteHop::FirstHop(FirstHopCandidate { details: first_hops[idx], payer_node_id: &node_id, - }; - scorer.channel_penalty_msat(&candidate, usage, &()); + }); + scorer.channel_penalty_msat(&candidate, usage, &Default::default()); continue; } } @@ -154,11 +178,11 @@ impl<'a> Router for TestRouter<'a> { let network_graph = self.network_graph.read_only(); if let Some(channel) = network_graph.channel(hop.short_channel_id) { let (directed, _) = channel.as_directed_to(&NodeId::from_pubkey(&hop.pubkey)).unwrap(); - let candidate = CandidateRouteHop::PublicHop { + let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate { info: directed, short_channel_id: hop.short_channel_id, - }; - scorer.channel_penalty_msat(&candidate, usage, &()); + }); + scorer.channel_penalty_msat(&candidate, usage, &Default::default()); } else { let target_node_id = NodeId::from_pubkey(&hop.pubkey); let route_hint = RouteHintHop { @@ -169,27 +193,58 @@ impl<'a> Router for TestRouter<'a> { htlc_minimum_msat: None, htlc_maximum_msat: None, }; - let candidate = CandidateRouteHop::PrivateHop { + let candidate = CandidateRouteHop::PrivateHop(PrivateHopCandidate { hint: &route_hint, target_node_id: &target_node_id, - }; - scorer.channel_penalty_msat(&candidate, usage, &()); + }); + scorer.channel_penalty_msat(&candidate, usage, &Default::default()); } prev_hop_node = &hop.pubkey; } } } - return find_route_res; + route_res = find_route_res; + } else { + route_res = self.router.find_route(payer, params, first_hops, inflight_htlcs); + }; + + if let Ok(route) = &route_res { + // Previously, `Route`s failed to round-trip through serialization due to a write/read + // mismatch. Thus, here we test all test-generated routes round-trip: + let ser = route.encode(); + assert_eq!(Route::read(&mut &ser[..]).unwrap(), *route); } - let logger = TestLogger::new(); - find_route( - payer, params, &self.network_graph, first_hops, &logger, - &ScorerAccountingForInFlightHtlcs::new(self.scorer.read().unwrap(), &inflight_htlcs), &Default::default(), - &[42; 32] + route_res + } + + fn create_blinded_payment_paths< + T: secp256k1::Signing + secp256k1::Verification + >( + &self, recipient: PublicKey, first_hops: Vec, tlvs: ReceiveTlvs, + amount_msats: u64, secp_ctx: &Secp256k1, + ) -> Result, ()> { + self.router.create_blinded_payment_paths( + recipient, first_hops, tlvs, amount_msats, secp_ctx ) } } +impl<'a> MessageRouter for TestRouter<'a> { + fn find_path( + &self, sender: PublicKey, peers: Vec, destination: Destination + ) -> Result { + self.router.find_path(sender, peers, destination) + } + + fn create_blinded_paths< + T: secp256k1::Signing + secp256k1::Verification + >( + &self, recipient: PublicKey, peers: Vec, secp_ctx: &Secp256k1, + ) -> Result, ()> { + self.router.create_blinded_paths(recipient, peers, secp_ctx) + } +} + impl<'a> Drop for TestRouter<'a> { fn drop(&mut self) { #[cfg(feature = "std")] { @@ -201,6 +256,30 @@ impl<'a> Drop for TestRouter<'a> { } } +pub struct TestMessageRouter<'a> { + inner: DefaultMessageRouter>, &'a TestLogger, &'a TestKeysInterface>, +} + +impl<'a> TestMessageRouter<'a> { + pub fn new(network_graph: Arc>, entropy_source: &'a TestKeysInterface) -> Self { + Self { inner: DefaultMessageRouter::new(network_graph, entropy_source) } + } +} + +impl<'a> MessageRouter for TestMessageRouter<'a> { + fn find_path( + &self, sender: PublicKey, peers: Vec, destination: Destination + ) -> Result { + self.inner.find_path(sender, peers, destination) + } + + fn create_blinded_paths( + &self, recipient: PublicKey, peers: Vec, secp_ctx: &Secp256k1, + ) -> Result, ()> { + self.inner.create_blinded_paths(recipient, peers, secp_ctx) + } +} + pub struct OnlyReadsKeysInterface {} impl EntropySource for OnlyReadsKeysInterface { @@ -234,7 +313,7 @@ pub struct TestChainMonitor<'a> { pub added_monitors: Mutex)>>, pub monitor_updates: Mutex>>, pub latest_monitor_update_id: Mutex>, - pub chain_monitor: chainmonitor::ChainMonitor>, + pub chain_monitor: chainmonitor::ChainMonitor>, 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 @@ -245,11 +324,11 @@ pub struct TestChainMonitor<'a> { pub expect_monitor_round_trip_fail: Mutex>, } 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, keys_manager: &'a TestKeysInterface) -> Self { + pub fn new(chain_source: Option<&'a TestChainSource>, broadcaster: &'a dyn chaininterface::BroadcasterInterface, logger: &'a TestLogger, fee_estimator: &'a TestFeeEstimator, persister: &'a dyn chainmonitor::Persist, keys_manager: &'a TestKeysInterface) -> Self { Self { added_monitors: Mutex::new(Vec::new()), - monitor_updates: Mutex::new(HashMap::new()), - latest_monitor_update_id: Mutex::new(HashMap::new()), + monitor_updates: Mutex::new(new_hash_map()), + latest_monitor_update_id: Mutex::new(new_hash_map()), chain_monitor: chainmonitor::ChainMonitor::new(chain_source, broadcaster, logger, fee_estimator, persister), keys_manager, expect_channel_force_closed: Mutex::new(None), @@ -271,7 +350,7 @@ impl<'a> chain::Watch for TestChainMonitor<'a> { let new_monitor = <(BlockHash, channelmonitor::ChannelMonitor)>::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(), + self.latest_monitor_update_id.lock().unwrap().insert(monitor.channel_id(), (funding_txo, monitor.get_latest_update_id(), MonitorUpdateId::from_new_monitor(&monitor))); self.added_monitors.lock().unwrap().push((funding_txo, monitor)); self.chain_monitor.watch_channel(funding_txo, new_monitor) @@ -283,18 +362,19 @@ impl<'a> chain::Watch for TestChainMonitor<'a> { update.write(&mut w).unwrap(); assert!(channelmonitor::ChannelMonitorUpdate::read( &mut io::Cursor::new(&w.0)).unwrap() == *update); + let channel_id = update.channel_id.unwrap_or(ChannelId::v1_from_funding_outpoint(funding_txo)); - self.monitor_updates.lock().unwrap().entry(funding_txo.to_channel_id()).or_insert(Vec::new()).push(update.clone()); + self.monitor_updates.lock().unwrap().entry(channel_id).or_insert(Vec::new()).push(update.clone()); if let Some(exp) = self.expect_channel_force_closed.lock().unwrap().take() { - assert_eq!(funding_txo.to_channel_id(), exp.0); + assert_eq!(channel_id, exp.0); assert_eq!(update.updates.len(), 1); if let channelmonitor::ChannelMonitorUpdateStep::ChannelForceClosed { should_broadcast } = update.updates[0] { assert_eq!(should_broadcast, exp.1); } else { panic!(); } } - self.latest_monitor_update_id.lock().unwrap().insert(funding_txo.to_channel_id(), + self.latest_monitor_update_id.lock().unwrap().insert(channel_id, (funding_txo, update.update_id, MonitorUpdateId::from_monitor_update(update))); let update_res = self.chain_monitor.update_channel(funding_txo, update); // At every point where we get a monitor update, we should be able to send a useful monitor @@ -305,7 +385,7 @@ impl<'a> chain::Watch for TestChainMonitor<'a> { let new_monitor = <(BlockHash, channelmonitor::ChannelMonitor)>::read( &mut io::Cursor::new(&w.0), (self.keys_manager, self.keys_manager)).unwrap().1; if let Some(chan_id) = self.expect_monitor_round_trip_fail.lock().unwrap().take() { - assert_eq!(chan_id, funding_txo.to_channel_id()); + assert_eq!(chan_id, channel_id); assert!(new_monitor != *monitor); } else { assert!(new_monitor == *monitor); @@ -314,7 +394,7 @@ impl<'a> chain::Watch for TestChainMonitor<'a> { update_res } - fn release_pending_monitor_events(&self) -> Vec<(OutPoint, Vec, Option)> { + fn release_pending_monitor_events(&self) -> Vec<(OutPoint, ChannelId, Vec, Option)> { return self.chain_monitor.release_pending_monitor_events(); } } @@ -343,8 +423,8 @@ impl WatchtowerPersister { pub(crate) fn new(destination_script: ScriptBuf) -> Self { WatchtowerPersister { persister: TestPersister::new(), - unsigned_justice_tx_data: Mutex::new(HashMap::new()), - watchtower_state: Mutex::new(HashMap::new()), + unsigned_justice_tx_data: Mutex::new(new_hash_map()), + watchtower_state: Mutex::new(new_hash_map()), destination_script, } } @@ -377,7 +457,7 @@ impl chainmonitor::Persist Self { Self { update_rets: Mutex::new(VecDeque::new()), - chain_sync_monitor_persistences: Mutex::new(HashMap::new()), - offchain_monitor_updates: Mutex::new(HashMap::new()), + chain_sync_monitor_persistences: Mutex::new(new_hash_map()), + offchain_monitor_updates: Mutex::new(new_hash_map()), } } @@ -463,9 +543,9 @@ impl chainmonitor::Persist Self { - let persisted_bytes = Mutex::new(HashMap::new()); + let persisted_bytes = Mutex::new(new_hash_map()); Self { persisted_bytes, read_only } } } @@ -518,7 +598,7 @@ impl KVStore for TestStore { } else { format!("{}/{}", primary_namespace, secondary_namespace) }; - let outer_e = persisted_lock.entry(prefixed).or_insert(HashMap::new()); + let outer_e = persisted_lock.entry(prefixed).or_insert(new_hash_map()); let mut bytes = Vec::new(); bytes.write_all(buf)?; outer_e.insert(key.to_string(), bytes); @@ -562,6 +642,9 @@ impl KVStore for TestStore { } } +unsafe impl Sync for TestStore {} +unsafe impl Send for TestStore {} + pub struct TestBroadcaster { pub txn_broadcasted: Mutex>, pub blocks: Arc>>, @@ -585,7 +668,7 @@ impl TestBroadcaster { pub fn unique_txn_broadcast(&self) -> Vec { let mut txn = self.txn_broadcasted.lock().unwrap().split_off(0); - let mut seen = HashSet::new(); + let mut seen = new_hash_set(); txn.retain(|tx| seen.insert(tx.txid())); txn } @@ -622,7 +705,7 @@ impl TestChannelMessageHandler { TestChannelMessageHandler { pending_events: Mutex::new(Vec::new()), expected_recv_msgs: Mutex::new(None), - connected_peers: Mutex::new(HashSet::new()), + connected_peers: Mutex::new(new_hash_set()), message_fetch_counter: AtomicUsize::new(0), chain_hash, } @@ -988,8 +1071,8 @@ impl TestLogger { TestLogger { level: Level::Trace, id, - lines: Mutex::new(HashMap::new()), - context: Mutex::new(HashMap::new()), + lines: Mutex::new(new_hash_map()), + context: Mutex::new(new_hash_map()), } } pub fn enable(&mut self, level: Level) { @@ -1108,6 +1191,7 @@ pub struct TestKeysInterface { pub disable_revocation_policy_check: bool, enforcement_states: Mutex>>>, expectations: Mutex>>, + pub unavailable_signers: Mutex>, } impl EntropySource for TestKeysInterface { @@ -1166,7 +1250,11 @@ impl SignerProvider for TestKeysInterface { 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); - TestChannelSigner::new_with_revoked(keys, state, self.disable_revocation_policy_check) + let signer = TestChannelSigner::new_with_revoked(keys, state, self.disable_revocation_policy_check); + if self.unavailable_signers.lock().unwrap().contains(&channel_keys_id) { + signer.set_available(false); + } + signer } fn read_chan_signer(&self, buffer: &[u8]) -> Result { @@ -1202,8 +1290,9 @@ impl TestKeysInterface { backing: sign::PhantomKeysManager::new(seed, now.as_secs(), now.subsec_nanos(), seed), override_random_bytes: Mutex::new(None), disable_revocation_policy_check: false, - enforcement_states: Mutex::new(HashMap::new()), + enforcement_states: Mutex::new(new_hash_map()), expectations: Mutex::new(None), + unavailable_signers: Mutex::new(new_hash_set()), } } @@ -1217,9 +1306,7 @@ impl TestKeysInterface { } 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); - TestChannelSigner::new_with_revoked(keys, state, self.disable_revocation_policy_check) + self.derive_channel_signer(channel_value_satoshis, *id) } fn make_enforcement_state_cell(&self, commitment_seed: [u8; 32]) -> Arc> { @@ -1283,8 +1370,8 @@ impl TestChainSource { chain_hash: ChainHash::using_genesis_block(network), utxo_ret: Mutex::new(UtxoResult::Sync(Ok(TxOut { value: u64::max_value(), script_pubkey }))), get_utxo_call_count: AtomicUsize::new(0), - watched_txn: Mutex::new(HashSet::new()), - watched_outputs: Mutex::new(HashSet::new()), + watched_txn: Mutex::new(new_hash_set()), + watched_outputs: Mutex::new(new_hash_set()), } } } @@ -1363,15 +1450,20 @@ impl ScoreLookUp for TestScorer { } impl ScoreUpdate for TestScorer { - fn payment_path_failed(&mut self, _actual_path: &Path, _actual_short_channel_id: u64) {} + fn payment_path_failed(&mut self, _actual_path: &Path, _actual_short_channel_id: u64, _duration_since_epoch: Duration) {} - fn payment_path_successful(&mut self, _actual_path: &Path) {} + fn payment_path_successful(&mut self, _actual_path: &Path, _duration_since_epoch: Duration) {} - fn probe_failed(&mut self, _actual_path: &Path, _: u64) {} + fn probe_failed(&mut self, _actual_path: &Path, _: u64, _duration_since_epoch: Duration) {} - fn probe_successful(&mut self, _actual_path: &Path) {} + fn probe_successful(&mut self, _actual_path: &Path, _duration_since_epoch: Duration) {} + + fn time_passed(&mut self, _duration_since_epoch: Duration) {} } +#[cfg(c_bindings)] +impl crate::routing::scoring::Score for TestScorer {} + impl Drop for TestScorer { fn drop(&mut self) { #[cfg(feature = "std")] { @@ -1431,7 +1523,8 @@ impl WalletSource for TestWalletSource { Ok(ScriptBuf::new_p2pkh(&public_key.pubkey_hash())) } - fn sign_tx(&self, mut tx: Transaction) -> Result { + fn sign_psbt(&self, psbt: PartiallySignedTransaction) -> Result { + let mut tx = psbt.extract_tx(); 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) { diff --git a/lightning/src/util/time.rs b/lightning/src/util/time.rs index 0d969e74709..a6e6f4d1fda 100644 --- a/lightning/src/util/time.rs +++ b/lightning/src/util/time.rs @@ -59,15 +59,15 @@ impl Sub for Eternity { } #[derive(Clone, Copy, Debug, PartialEq, Eq)] -#[cfg(not(feature = "no-std"))] +#[cfg(feature = "std")] pub struct MonotonicTime(std::time::Instant); /// The amount of time to shift `Instant` forward to prevent overflow when subtracting a `Duration` /// from `Instant::now` on some operating systems (e.g., iOS representing `Instance` as `u64`). -#[cfg(not(feature = "no-std"))] +#[cfg(feature = "std")] const SHIFT: Duration = Duration::from_secs(10 * 365 * 24 * 60 * 60); // 10 years. -#[cfg(not(feature = "no-std"))] +#[cfg(feature = "std")] impl Time for MonotonicTime { fn now() -> Self { let instant = std::time::Instant::now().checked_add(SHIFT).expect("Overflow on MonotonicTime instantiation"); @@ -93,7 +93,7 @@ impl Time for MonotonicTime { } } -#[cfg(not(feature = "no-std"))] +#[cfg(feature = "std")] impl Sub for MonotonicTime { type Output = Self; @@ -177,7 +177,7 @@ pub mod tests { } #[test] - #[cfg(not(feature = "no-std"))] + #[cfg(feature = "std")] fn monotonic_time_subtracts() { let now = super::MonotonicTime::now(); assert!(now.elapsed() < Duration::from_secs(10)); diff --git a/lightning/src/util/wakers.rs b/lightning/src/util/wakers.rs index 37c036da959..b2c9d21b998 100644 --- a/lightning/src/util/wakers.rs +++ b/lightning/src/util/wakers.rs @@ -56,25 +56,33 @@ impl Notifier { /// Gets a [`Future`] that will get woken up with any waiters pub(crate) fn get_future(&self) -> Future { let mut lock = self.notify_pending.lock().unwrap(); + let mut self_idx = 0; if let Some(existing_state) = &lock.1 { - if existing_state.lock().unwrap().callbacks_made { + let mut locked = existing_state.lock().unwrap(); + if locked.callbacks_made { // If the existing `FutureState` has completed and actually made callbacks, // consider the notification flag to have been cleared and reset the future state. + mem::drop(locked); lock.1.take(); lock.0 = false; + } else { + self_idx = locked.next_idx; + locked.next_idx += 1; } } if let Some(existing_state) = &lock.1 { - Future { state: Arc::clone(&existing_state) } + Future { state: Arc::clone(&existing_state), self_idx } } else { let state = Arc::new(Mutex::new(FutureState { callbacks: Vec::new(), + std_future_callbacks: Vec::new(), callbacks_with_state: Vec::new(), complete: lock.0, callbacks_made: false, + next_idx: 1, })); lock.1 = Some(Arc::clone(&state)); - Future { state } + Future { state, self_idx: 0 } } } @@ -109,36 +117,39 @@ define_callback!(Send); define_callback!(); pub(crate) struct FutureState { - // When we're tracking whether a callback counts as having woken the user's code, we check the - // first bool - set to false if we're just calling a Waker, and true if we're calling an actual - // user-provided function. - callbacks: Vec<(bool, Box)>, - callbacks_with_state: Vec<(bool, Box>) -> () + Send>)>, + // `callbacks` count as having woken the users' code (as they go direct to the user), but + // `std_future_callbacks` and `callbacks_with_state` do not (as the first just wakes a future, + // we only count it after another `poll()` and the second wakes a `Sleeper` which handles + // setting `callbacks_made` itself). + callbacks: Vec>, + std_future_callbacks: Vec<(usize, StdWaker)>, + callbacks_with_state: Vec>) -> () + Send>>, complete: bool, callbacks_made: bool, + next_idx: usize, } fn complete_future(this: &Arc>) -> bool { let mut state_lock = this.lock().unwrap(); let state = &mut *state_lock; - for (counts_as_call, callback) in state.callbacks.drain(..) { + for callback in state.callbacks.drain(..) { callback.call(); - state.callbacks_made |= counts_as_call; + state.callbacks_made = true; } - for (counts_as_call, callback) in state.callbacks_with_state.drain(..) { + for (_, waker) in state.std_future_callbacks.drain(..) { + waker.0.wake_by_ref(); + } + for callback in state.callbacks_with_state.drain(..) { (callback)(this); - state.callbacks_made |= counts_as_call; } state.complete = true; state.callbacks_made } /// A simple future which can complete once, and calls some callback(s) when it does so. -/// -/// Clones can be made and all futures cloned from the same source will complete at the same time. -#[derive(Clone)] pub struct Future { state: Arc>, + self_idx: usize, } impl Future { @@ -153,7 +164,7 @@ impl Future { mem::drop(state); callback.call(); } else { - state.callbacks.push((true, callback)); + state.callbacks.push(callback); } } @@ -169,16 +180,16 @@ impl Future { /// Waits until this [`Future`] completes. #[cfg(feature = "std")] - pub fn wait(self) { - Sleeper::from_single_future(self).wait(); + pub fn wait(&self) { + Sleeper::from_single_future(&self).wait(); } /// Waits until this [`Future`] completes or the given amount of time has elapsed. /// /// Returns true if the [`Future`] completed, false if the time elapsed. #[cfg(feature = "std")] - pub fn wait_timeout(self, max_wait: Duration) -> bool { - Sleeper::from_single_future(self).wait_timeout(max_wait) + pub fn wait_timeout(&self, max_wait: Duration) -> bool { + Sleeper::from_single_future(&self).wait_timeout(max_wait) } #[cfg(test)] @@ -191,11 +202,14 @@ impl Future { } } +impl Drop for Future { + fn drop(&mut self) { + self.state.lock().unwrap().std_future_callbacks.retain(|(idx, _)| *idx != self.self_idx); + } +} + use core::task::Waker; struct StdWaker(pub Waker); -impl FutureCallback for StdWaker { - fn call(&self) { self.0.wake_by_ref() } -} /// This is not exported to bindings users as Rust Futures aren't usable in language bindings. impl<'a> StdFuture for Future { @@ -208,7 +222,8 @@ impl<'a> StdFuture for Future { Poll::Ready(()) } else { let waker = cx.waker().clone(); - state.callbacks.push((false, Box::new(StdWaker(waker)))); + state.std_future_callbacks.retain(|(idx, _)| *idx != self.self_idx); + state.std_future_callbacks.push((self.self_idx, StdWaker(waker))); Poll::Pending } } @@ -224,17 +239,17 @@ pub struct Sleeper { #[cfg(feature = "std")] impl Sleeper { /// Constructs a new sleeper from one future, allowing blocking on it. - pub fn from_single_future(future: Future) -> Self { - Self { notifiers: vec![future.state] } + pub fn from_single_future(future: &Future) -> Self { + Self { notifiers: vec![Arc::clone(&future.state)] } } /// Constructs a new sleeper from two futures, allowing blocking on both at once. // Note that this is the common case - a ChannelManager and ChainMonitor. - pub fn from_two_futures(fut_a: Future, fut_b: Future) -> Self { - Self { notifiers: vec![fut_a.state, fut_b.state] } + pub fn from_two_futures(fut_a: &Future, fut_b: &Future) -> Self { + Self { notifiers: vec![Arc::clone(&fut_a.state), Arc::clone(&fut_b.state)] } } /// Constructs a new sleeper on many futures, allowing blocking on all at once. pub fn new(futures: Vec) -> Self { - Self { notifiers: futures.into_iter().map(|f| f.state).collect() } + Self { notifiers: futures.into_iter().map(|f| Arc::clone(&f.state)).collect() } } /// Prepares to go into a wait loop body, creating a condition variable which we can block on /// and an `Arc>>` which gets set to the waking `Future`'s state prior to the @@ -251,10 +266,10 @@ impl Sleeper { *notified_fut_mtx.lock().unwrap() = Some(Arc::clone(¬ifier_mtx)); break; } - notifier.callbacks_with_state.push((false, Box::new(move |notifier_ref| { + notifier.callbacks_with_state.push(Box::new(move |notifier_ref| { *notified_fut_ref.lock().unwrap() = Some(Arc::clone(notifier_ref)); cv_ref.notify_all(); - }))); + })); } } (cv, notified_fut_mtx) @@ -439,13 +454,15 @@ mod tests { // Wait on the other thread to finish its sleep, note that the leak only happened if we // actually have to sleep here, not if we immediately return. - Sleeper::from_two_futures(future_a, future_b).wait(); + Sleeper::from_two_futures(&future_a, &future_b).wait(); join_handle.join().unwrap(); // then drop the notifiers and make sure the future states are gone. mem::drop(notifier_a); mem::drop(notifier_b); + mem::drop(future_a); + mem::drop(future_b); assert!(future_state_a.upgrade().is_none() && future_state_b.upgrade().is_none()); } @@ -455,10 +472,13 @@ mod tests { let future = Future { state: Arc::new(Mutex::new(FutureState { callbacks: Vec::new(), + std_future_callbacks: Vec::new(), callbacks_with_state: Vec::new(), complete: false, callbacks_made: false, - })) + next_idx: 1, + })), + self_idx: 0, }; let callback = Arc::new(AtomicBool::new(false)); let callback_ref = Arc::clone(&callback); @@ -475,10 +495,13 @@ mod tests { let future = Future { state: Arc::new(Mutex::new(FutureState { callbacks: Vec::new(), + std_future_callbacks: Vec::new(), callbacks_with_state: Vec::new(), complete: false, callbacks_made: false, - })) + next_idx: 1, + })), + self_idx: 0, }; complete_future(&future.state); @@ -491,7 +514,7 @@ mod tests { } // Rather annoyingly, there's no safe way in Rust std to construct a Waker despite it being - // totally possible to construct from a trait implementation (though somewhat less effecient + // totally possible to construct from a trait implementation (though somewhat less efficient // compared to a raw VTable). Instead, we have to write out a lot of boilerplate to build a // waker, which we do here with a trivial Arc data element to track woke-ness. const WAKER_V_TABLE: RawWakerVTable = RawWakerVTable::new(waker_clone, wake, wake_by_ref, drop); @@ -514,12 +537,15 @@ mod tests { let mut future = Future { state: Arc::new(Mutex::new(FutureState { callbacks: Vec::new(), + std_future_callbacks: Vec::new(), callbacks_with_state: Vec::new(), complete: false, callbacks_made: false, - })) + next_idx: 2, + })), + self_idx: 0, }; - let mut second_future = Future { state: Arc::clone(&future.state) }; + let mut second_future = Future { state: Arc::clone(&future.state), self_idx: 1 }; let (woken, waker) = create_waker(); assert_eq!(Pin::new(&mut future).poll(&mut Context::from_waker(&waker)), Poll::Pending); @@ -638,18 +664,18 @@ mod tests { // Set both notifiers as woken without sleeping yet. notifier_a.notify(); notifier_b.notify(); - Sleeper::from_two_futures(notifier_a.get_future(), notifier_b.get_future()).wait(); + Sleeper::from_two_futures(¬ifier_a.get_future(), ¬ifier_b.get_future()).wait(); // One future has woken us up, but the other should still have a pending notification. - Sleeper::from_two_futures(notifier_a.get_future(), notifier_b.get_future()).wait(); + Sleeper::from_two_futures(¬ifier_a.get_future(), ¬ifier_b.get_future()).wait(); // However once we've slept twice, we should no longer have any pending notifications - assert!(!Sleeper::from_two_futures(notifier_a.get_future(), notifier_b.get_future()) + assert!(!Sleeper::from_two_futures(¬ifier_a.get_future(), ¬ifier_b.get_future()) .wait_timeout(Duration::from_millis(10))); // Test ordering somewhat more. notifier_a.notify(); - Sleeper::from_two_futures(notifier_a.get_future(), notifier_b.get_future()).wait(); + Sleeper::from_two_futures(¬ifier_a.get_future(), ¬ifier_b.get_future()).wait(); } #[test] @@ -667,7 +693,7 @@ mod tests { // After sleeping one future (not guaranteed which one, however) will have its notification // bit cleared. - Sleeper::from_two_futures(notifier_a.get_future(), notifier_b.get_future()).wait(); + Sleeper::from_two_futures(¬ifier_a.get_future(), ¬ifier_b.get_future()).wait(); // By registering a callback on the futures for both notifiers, one will complete // immediately, but one will remain tied to the notifier, and will complete once the @@ -686,8 +712,48 @@ mod tests { notifier_b.notify(); assert!(callback_a.load(Ordering::SeqCst) && callback_b.load(Ordering::SeqCst)); - Sleeper::from_two_futures(notifier_a.get_future(), notifier_b.get_future()).wait(); - assert!(!Sleeper::from_two_futures(notifier_a.get_future(), notifier_b.get_future()) + Sleeper::from_two_futures(¬ifier_a.get_future(), ¬ifier_b.get_future()).wait(); + assert!(!Sleeper::from_two_futures(¬ifier_a.get_future(), ¬ifier_b.get_future()) .wait_timeout(Duration::from_millis(10))); } + + #[test] + #[cfg(feature = "std")] + fn multi_poll_stores_single_waker() { + // When a `Future` is `poll()`ed multiple times, only the last `Waker` should be called, + // but previously we'd store all `Waker`s until they're all woken at once. This tests a few + // cases to ensure `Future`s avoid storing an endless set of `Waker`s. + let notifier = Notifier::new(); + let future_state = Arc::clone(¬ifier.get_future().state); + assert_eq!(future_state.lock().unwrap().std_future_callbacks.len(), 0); + + // Test that simply polling a future twice doesn't result in two pending `Waker`s. + let mut future_a = notifier.get_future(); + assert_eq!(Pin::new(&mut future_a).poll(&mut Context::from_waker(&create_waker().1)), Poll::Pending); + assert_eq!(future_state.lock().unwrap().std_future_callbacks.len(), 1); + assert_eq!(Pin::new(&mut future_a).poll(&mut Context::from_waker(&create_waker().1)), Poll::Pending); + assert_eq!(future_state.lock().unwrap().std_future_callbacks.len(), 1); + + // If we poll a second future, however, that will store a second `Waker`. + let mut future_b = notifier.get_future(); + assert_eq!(Pin::new(&mut future_b).poll(&mut Context::from_waker(&create_waker().1)), Poll::Pending); + assert_eq!(future_state.lock().unwrap().std_future_callbacks.len(), 2); + + // but when we drop the `Future`s, the pending Wakers will also be dropped. + mem::drop(future_a); + assert_eq!(future_state.lock().unwrap().std_future_callbacks.len(), 1); + mem::drop(future_b); + assert_eq!(future_state.lock().unwrap().std_future_callbacks.len(), 0); + + // Further, after polling a future twice, if the notifier is woken all Wakers are dropped. + let mut future_a = notifier.get_future(); + assert_eq!(Pin::new(&mut future_a).poll(&mut Context::from_waker(&create_waker().1)), Poll::Pending); + assert_eq!(future_state.lock().unwrap().std_future_callbacks.len(), 1); + assert_eq!(Pin::new(&mut future_a).poll(&mut Context::from_waker(&create_waker().1)), Poll::Pending); + assert_eq!(future_state.lock().unwrap().std_future_callbacks.len(), 1); + notifier.notify(); + assert_eq!(future_state.lock().unwrap().std_future_callbacks.len(), 0); + assert_eq!(Pin::new(&mut future_a).poll(&mut Context::from_waker(&create_waker().1)), Poll::Ready(())); + assert_eq!(future_state.lock().unwrap().std_future_callbacks.len(), 0); + } } diff --git a/msrv-no-dev-deps-check/Cargo.toml b/msrv-no-dev-deps-check/Cargo.toml index d73360749df..3a4acc675e6 100644 --- a/msrv-no-dev-deps-check/Cargo.toml +++ b/msrv-no-dev-deps-check/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "msrv-check" version = "0.1.0" -edition = "2018" +edition = "2021" [dependencies] lightning = { path = "../lightning" } @@ -11,3 +11,4 @@ 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" } +lightning-custom-message = { path = "../lightning-custom-message" } diff --git a/no-std-check/Cargo.toml b/no-std-check/Cargo.toml index 16d2fc110e2..45a70c2a6d1 100644 --- a/no-std-check/Cargo.toml +++ b/no-std-check/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "no-std-check" version = "0.1.0" -edition = "2018" +edition = "2021" [features] default = ["lightning/no-std", "lightning-invoice/no-std", "lightning-rapid-gossip-sync/no-std"] @@ -15,3 +15,6 @@ lightning-background-processor = { path = "../lightning-background-processor", f # Obviously lightning-transaction-sync doesn't support no-std, but it should build # even if lightning is built with no-std. lightning-transaction-sync = { path = "../lightning-transaction-sync", optional = true } + +[patch.crates-io] +possiblyrandom = { path = "../possiblyrandom" } diff --git a/pending_changelog/113-channel-ser-compat.txt b/pending_changelog/113-channel-ser-compat.txt deleted file mode 100644 index 9bba9fd1c55..00000000000 --- a/pending_changelog/113-channel-ser-compat.txt +++ /dev/null @@ -1,4 +0,0 @@ - * `ChannelManager`s written with LDK 0.0.119 are no longer readable by versions - of LDK prior to 0.0.113. Users wishing to downgrade to LDK 0.0.112 or before - can read an 0.0.119-serialized `ChannelManager` with a version of LDK from - 0.0.113 to 0.0.118, re-serialize it, and then downgrade. diff --git a/pending_changelog/electrum.txt b/pending_changelog/electrum.txt deleted file mode 100644 index 5171f5ea082..00000000000 --- a/pending_changelog/electrum.txt +++ /dev/null @@ -1,3 +0,0 @@ -## API Updates - -- The `Confirm::get_relevant_txids()` call now also returns the height under which LDK expects the respective transaction to be confirmed. diff --git a/pending_changelog/route-blinding-intro-node.txt b/pending_changelog/route-blinding-intro-node.txt deleted file mode 100644 index 3f31d3760e6..00000000000 --- a/pending_changelog/route-blinding-intro-node.txt +++ /dev/null @@ -1,4 +0,0 @@ -## Backwards Compat - -* Forwarding a blinded HTLC and subsequently downgrading to an LDK version prior to 0.0.119 may - result in a forwarding failure or an HTLC being failed backwards with an unblinded error. diff --git a/pending_changelog/skimmed_fee_ser.txt b/pending_changelog/skimmed_fee_ser.txt deleted file mode 100644 index d3e68746ee0..00000000000 --- a/pending_changelog/skimmed_fee_ser.txt +++ /dev/null @@ -1,4 +0,0 @@ -## Bug fixes - -* In LDK versions 0.0.116 through 0.0.118, in rare cases where skimmed fees are present on shutdown - the `ChannelManager` may fail to deserialize on startup. diff --git a/possiblyrandom/Cargo.toml b/possiblyrandom/Cargo.toml new file mode 100644 index 00000000000..e02b59669b1 --- /dev/null +++ b/possiblyrandom/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "possiblyrandom" +version = "0.1.0" +authors = ["Matt Corallo"] +license = "MIT OR Apache-2.0" +repository = "https://github.com/lightningdevkit/rust-lightning/" +description = """ +A crate that wraps getrandom and always compiles, returning 0s when no randomness is available. +""" +edition = "2021" + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] + +[dependencies] +getrandom = { version = "0.2", optional = true, default-features = false } + +# Enable getrandom if we are on a platform that (likely) supports it +[target.'cfg(not(any(target_os = "unknown", target_os = "none")))'.dependencies] +getrandom = { version = "0.2", default-features = false } diff --git a/possiblyrandom/src/lib.rs b/possiblyrandom/src/lib.rs new file mode 100644 index 00000000000..9cbbad7f13d --- /dev/null +++ b/possiblyrandom/src/lib.rs @@ -0,0 +1,35 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +//! [`getrandom`] provides access to OS randomness, but will fail to compile on platforms that do +//! not support fetching OS randomness. This is exactly what you want when you're doing +//! cryptographic operations, but when you're just opportunistically randomizing, we're fine with +//! compiling and simply disabling randomization. +//! +//! This crate does that, returning only possibly-random data. +//! +//! Note that this crate only enables getrandom on a subset of platforms it supports. As getrandom +//! evolves this crate is unlikely to carefully track all getrandom-supported platforms, however +//! will use random data on popular platforms. + +#![no_std] + +#[cfg(feature = "getrandom")] +extern crate getrandom; + +/// Possibly fills `dest` with random data. May fill it with zeros. +#[inline] +pub fn getpossiblyrandom(dest: &mut [u8]) { + #[cfg(feature = "getrandom")] + if getrandom::getrandom(dest).is_err() { + dest.fill(0); + } + #[cfg(not(feature = "getrandom"))] + dest.fill(0); +} diff --git a/rustfmt.toml b/rustfmt.toml index 91b80232866..027fcfc651b 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1 +1,12 @@ -disable_all_formatting = true \ No newline at end of file +use_small_heuristics = "Max" +fn_args_layout = "Compressed" +hard_tabs = true +use_field_init_shorthand = true +max_width = 100 +match_block_trailing_comma = true +# UNSTABLE: format_code_in_doc_comments = true +# UNSTABLE: overflow_delimited_expr = true +# UNSTABLE: comment_width = 100 +# UNSTABLE: format_macro_matchers = true +# UNSTABLE: format_strings = true +# UNSTABLE: group_imports = "StdExternalCrate"